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
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.LocalDateTime;
import java.util.Map;
 
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.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.support.TransactionTemplate;
 
class MerchantVerificationOrderCodeTests {
 
    private JdbcTemplate jdbc;
    private TransactionTemplate transactions;
    private MerchantVerificationService service;
    private MerchantPrincipal principal;
 
    @BeforeEach
    void setUp() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource(
                "jdbc:h2:mem:order-code-verification-" + System.nanoTime()
                        + ";MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", "sa", "");
        jdbc = new JdbcTemplate(dataSource);
        transactions = new TransactionTemplate(new DataSourceTransactionManager(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 benefit_order (
                    id bigint primary key,
                    talent_id bigint not null,
                    card_id bigint,
                    benefit_id bigint,
                    order_no varchar(64) not null,
                    order_amount bigint not null,
                    subsidy_amount bigint not null,
                    pay_amount bigint not null,
                    order_status tinyint not null,
                    verify_status tinyint not null,
                    refund_status tinyint not null default 0,
                    merchant_id bigint not null,
                    store_id bigint,
                    deleted tinyint not null default 0
                )
                """);
        jdbc.execute("""
                create table order_dynamic_verification_code (
                    id bigint primary key,
                    order_id bigint not null,
                    expires_at timestamp not null,
                    status tinyint not null
                )
                """);
        jdbc.execute("create table talent_card (id bigint primary key, card_level varchar(64), deleted tinyint not null default 0)");
        jdbc.execute("create table benefit (id bigint primary key, benefit_name varchar(128))");
        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)
                )
                """);
        service = new MerchantVerificationService(jdbc, new ManualConsumptionService(jdbc, null),
                new com.talentcard.server.business.order.OrderFundBoundaryService(jdbc));
        principal = new MerchantPrincipal(9L, 88L, 7L, "operator");
    }
 
    @Test
    void confirmsGroupOrderAfterScannedCodeExpiresAndRefreshes() {
        insertOrder("expired-group-order", 1);
 
        Map<String, Object> confirmed = confirm("expired-group-order");
 
        assertThat(confirmed.get("verified")).isEqualTo(true);
        assertThat(jdbc.queryForObject("select status from order_dynamic_verification_code where id = 30", Integer.class)).isEqualTo(1);
        assertThat(jdbc.queryForObject("select verify_status from benefit_order where id = 20", Integer.class)).isEqualTo(1);
        assertThat(jdbc.queryForObject("select status from merchant_verification_attempt where verification_id = ?", Integer.class,
                "expired-group-order")).isEqualTo(1);
        assertThat(jdbc.queryForObject("select count(*) from verify_record where verify_type = 'ORDER_CODE'", Integer.class)).isEqualTo(1);
    }
 
    @Test
    void stillRejectsUnpaidGroupOrder() {
        insertOrder("unpaid-group-order", 0);
 
        assertThatThrownBy(() -> confirm("unpaid-group-order"))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("未支付");
    }
 
    @Test
    void rejectsGroupOrderThatWasRefundedWhileTheCodeWasOpen() {
        insertOrder("refunded-group-order", 1);
        jdbc.update("update benefit_order set refund_status = 2 where id = 20");
 
        assertThatThrownBy(() -> confirm("refunded-group-order"))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("退款");
        assertThat(jdbc.queryForObject("select verify_status from benefit_order where id = 20", Integer.class)).isZero();
    }
 
    /** 核销确认按订单行锁串行化,所以测试也必须在一个真实事务里调用它。 */
    private Map<String, Object> confirm(String verificationId) {
        return transactions.execute(status -> service.confirm(principal,
                new VerificationConfirmRequest(verificationId), "order-confirm-" + verificationId));
    }
 
    private void insertOrder(String verificationId, int orderStatus) {
        jdbc.update("insert into talent_card(id, card_level, deleted) values (10, '卓越人才卡', 0)");
        jdbc.update("""
                insert into benefit_order(id, talent_id, card_id, benefit_id, order_no, order_amount, subsidy_amount,
                                          pay_amount, order_status, verify_status, merchant_id, store_id, deleted)
                values (20, 1015, 10, null, ?, 10000, 3000, 7000, ?, 0, 88, 7, 0)
                """, "GROUP-" + verificationId, orderStatus);
        jdbc.update("""
                insert into order_dynamic_verification_code(id, order_id, expires_at, status)
                values (30, 20, ?, 2)
                """, Timestamp.valueOf(LocalDateTime.now().minusMinutes(1)));
        jdbc.update("""
                insert into merchant_verification_attempt(verification_id, credential_type, order_verification_code_id,
                                                          merchant_id, store_id, operator_id, expires_at, status)
                values (?, 'ORDER_CODE', 30, 88, 7, 9, ?, 0)
                """, verificationId, Timestamp.valueOf(LocalDateTime.now().minusMinutes(1)));
    }
}