yeg
2026-09-17 dd22571b369970b35ca6c48f32010e2876b43d25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package com.talentcard.server.business.merchant;
 
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
 
import com.talentcard.server.business.merchant.auth.MerchantPrincipal;
import com.talentcard.server.common.BusinessException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
 
class MerchantVerificationFreeStayTests {
 
    private JdbcTemplate jdbc;
    private MerchantVerificationService service;
    private MerchantPrincipal principal;
 
    @BeforeEach
    void setUp() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource(
                "jdbc:h2:mem:free-stay-verification-" + System.nanoTime()
                        + ";MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", "sa", "");
        jdbc = new JdbcTemplate(dataSource);
        jdbc.execute("""
                create table merchant_verification_attempt (
                    id bigint auto_increment primary key,
                    verification_id varchar(64) not null,
                    credential_type varchar(32) not null,
                    dynamic_code_id bigint,
                    order_verification_code_id bigint,
                    free_stay_verification_code_id bigint,
                    merchant_id bigint not null,
                    store_id bigint not null,
                    operator_id bigint not null,
                    expires_at timestamp,
                    status tinyint not null default 0,
                    verify_no varchar(64),
                    confirmed_at timestamp
                )
                """);
        jdbc.execute("""
                create table talent_free_stay_voucher (
                    id bigint primary key,
                    talent_id bigint not null,
                    merchant_id bigint not null,
                    card_id bigint not null,
                    total_count int not null,
                    used_count int not null,
                    valid_end date not null,
                    status tinyint not null
                )
                """);
        jdbc.execute("""
                create table free_stay_verification_code (
                    id bigint primary key,
                    voucher_id bigint not null,
                    code_token varchar(128),
                    expires_at timestamp not null,
                    status tinyint not null
                )
                """);
        jdbc.execute("""
                create table talent_user (
                    id bigint primary key,
                    name varchar(64),
                    mobile varchar(32),
                    status tinyint not null default 1,
                    source_area_type varchar(32),
                    deleted tinyint not null default 0
                )
                """);
        jdbc.execute("""
                create table talent_card (
                    id bigint primary key,
                    card_level varchar(64),
                    card_no varchar(64),
                    status tinyint not null default 1,
                    valid_end date,
                    deleted tinyint not null default 0
                )
                """);
        jdbc.execute("""
                create table talent_dynamic_code (
                    id bigint primary key,
                    expected_merchant_id bigint,
                    expires_at timestamp,
                    card_id bigint,
                    talent_id bigint,
                    code_token varchar(128),
                    status tinyint not null default 0
                )
                """);
        jdbc.execute("""
                create table benefit_order (
                    id bigint primary key,
                    order_no varchar(64),
                    order_status tinyint,
                    verify_status tinyint,
                    merchant_id bigint,
                    store_id bigint,
                    product_name_snapshot varchar(255),
                    benefit_id bigint,
                    talent_id bigint,
                    card_id bigint,
                    deleted tinyint not null default 0
                )
                """);
        jdbc.execute("create table benefit (id bigint primary key, benefit_name varchar(255))");
        jdbc.execute("""
                create table order_dynamic_verification_code (
                    id bigint primary key,
                    order_id bigint,
                    code_token varchar(128),
                    expires_at timestamp,
                    status tinyint not null default 0
                )
                """);
        jdbc.execute("""
                create table verify_record (
                    id bigint auto_increment primary key,
                    verify_no varchar(64) not null,
                    order_id bigint,
                    talent_id bigint,
                    card_id bigint,
                    benefit_id bigint,
                    merchant_id bigint,
                    store_id bigint,
                    staff_id bigint,
                    verify_type varchar(32),
                    verify_amount bigint,
                    subsidy_amount bigint,
                    remark varchar(255)
                )
                """);
        jdbc.execute("create table admin_setting (setting_key varchar(64) primary key, setting_value varchar(1000))");
        jdbc.update("insert into admin_setting(setting_key, setting_value) values ('hotelSubsidy', '268.50')");
        jdbc.execute("create table payment_transaction (id bigint primary key)");
        // 这些用例只验证核销本身,资金确认用替身:确认逻辑由 OrderVerificationService 的用例覆盖。
        service = new MerchantVerificationService(jdbc, new ManualConsumptionService(jdbc, null),
                org.mockito.Mockito.mock(com.talentcard.server.business.order.OrderVerificationService.class),
                new MerchantNumericCodeAttemptGuard(jdbc));
        principal = new MerchantPrincipal(9L, 88L, 7L, "operator");
    }
 
    @Test
    void confirmsFreeStayAfterScannedCodeExpiresAndRefreshes() {
        insertFreeStay("free-stay-expired-code", LocalDate.now().plusDays(1));
 
        MapResult confirmed = confirm("free-stay-expired-code");
 
        assertThat(confirmed.verified()).isTrue();
        assertThat(jdbc.queryForObject("select status from free_stay_verification_code where id = 30", Integer.class)).isEqualTo(1);
        assertThat(jdbc.queryForObject("select used_count from talent_free_stay_voucher where id = 20", Integer.class)).isEqualTo(1);
        assertThat(jdbc.queryForObject("select status from merchant_verification_attempt where verification_id = ?", Integer.class,
                "free-stay-expired-code")).isEqualTo(1);
        assertThat(jdbc.queryForObject("select count(*) from verify_record where verify_type = 'HOTEL_FREE'", Integer.class)).isEqualTo(1);
        assertThat(jdbc.queryForObject("select subsidy_amount from verify_record where verify_type = 'HOTEL_FREE'", Long.class)).isEqualTo(26850L);
        assertThat(jdbc.queryForObject("select count(*) from benefit_order", Integer.class)).isZero();
        assertThat(jdbc.queryForObject("select count(*) from payment_transaction", Integer.class)).isZero();
    }
 
    @Test
    void scanReturnsConfiguredHotelSubsidyAmount() {
        jdbc.update("insert into talent_card(id, card_level, deleted) values (10, '卓越人才卡', 0)");
        jdbc.update("insert into talent_user(id, name, mobile, deleted) values (1015, '测试人才', '13800000000', 0)");
        jdbc.update("""
                insert into talent_free_stay_voucher(id, talent_id, merchant_id, card_id, total_count, used_count, valid_end, status)
                values (20, 1015, 88, 10, 2, 0, ?, 1)
                """, LocalDate.now().plusDays(1));
        jdbc.update("""
                insert into free_stay_verification_code(id, voucher_id, code_token, expires_at, status)
                values (30, 20, 'free-stay-scan-code', ?, 0)
                """, Timestamp.valueOf(LocalDateTime.now().plusMinutes(1)));
 
        java.util.Map<String, Object> scanned = service.scan(principal, new VerificationScanRequest("free-stay-scan-code"));
 
        assertThat(scanned.get("credentialType")).isEqualTo("FREE_STAY_CODE");
        assertThat(scanned.get("subsidyAmount")).isEqualTo(26850L);
    }
 
    @Test
    void stillRejectsExpiredFreeStayVoucher() {
        insertFreeStay("free-stay-expired-voucher", LocalDate.now().minusDays(1));
 
        assertThatThrownBy(() -> confirm("free-stay-expired-voucher"))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("当前不可核销");
    }
 
    private void insertFreeStay(String verificationId, LocalDate voucherValidEnd) {
        jdbc.update("insert into talent_card(id, card_level, deleted) values (10, '卓越人才卡', 0)");
        jdbc.update("""
                insert into talent_free_stay_voucher(id, talent_id, merchant_id, card_id, total_count, used_count, valid_end, status)
                values (20, 1015, 88, 10, 2, 0, ?, 1)
                """, voucherValidEnd);
        jdbc.update("""
                insert into free_stay_verification_code(id, voucher_id, code_token, expires_at, status)
                values (30, 20, ?, ?, 2)
                """, verificationId, Timestamp.valueOf(LocalDateTime.now().minusMinutes(1)));
        jdbc.update("""
                insert into merchant_verification_attempt(verification_id, credential_type, free_stay_verification_code_id,
                                                          merchant_id, store_id, operator_id, expires_at, status)
                values (?, 'FREE_STAY_CODE', 30, 88, 7, 9, ?, 0)
                """, verificationId, Timestamp.valueOf(LocalDateTime.now().minusMinutes(1)));
    }
 
    private MapResult confirm(String verificationId) {
        java.util.Map<String, Object> result = service.confirmFreeStay(principal, new VerificationConfirmRequest(verificationId),
                "free-stay-confirm-" + verificationId);
        return new MapResult(Boolean.TRUE.equals(result.get("verified")));
    }
 
    private record MapResult(boolean verified) {}
}