shinnosuke
2026-08-28 0b064045b0af20040b6ad976a7ce33cdc03d91f7
fix: allow scanned benefit codes to finish verification
2个文件已添加
1个文件已修改
294 ■■■■■ 已修改文件
talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantVerificationService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantVerificationFreeStayTests.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantVerificationOrderCodeTests.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantVerificationService.java
@@ -69,11 +69,12 @@
        if (number(attempt.get("attemptStatus")) == 1) {
            return confirmed(attempt.get("verifyNo"), null);
        }
        if (number(attempt.get("attemptStatus")) != 0 || expired(attempt.get("expiresAt"))) {
        String credentialType = String.valueOf(attempt.get("credentialType"));
        boolean codeExpiryRequired = !ORDER_CODE.equals(credentialType) && !FREE_STAY_CODE.equals(credentialType);
        if (number(attempt.get("attemptStatus")) != 0 || (codeExpiryRequired && expired(attempt.get("expiresAt")))) {
            jdbc.update("update merchant_verification_attempt set status = 2 where id = ? and status = 0", number(attempt.get("attemptId")));
            throw new BusinessException(ErrorCode.CONFLICT, "核销凭证已失效,请重新扫码");
        }
        String credentialType = String.valueOf(attempt.get("credentialType"));
        return switch (credentialType) {
            case TALENT_CODE -> throw new BusinessException(ErrorCode.CONFLICT, "人才码需要先录入消费金额并发送订单");
            case ORDER_CODE -> confirmOrderCode(principal, attempt);
@@ -159,19 +160,19 @@
                from order_dynamic_verification_code c
                join benefit_order o on o.id = c.order_id and o.deleted = 0
                left join talent_card tc on tc.id = o.card_id and tc.deleted = 0
                where c.id = ? and c.status = 0
                where c.id = ? and c.status in (0, 2)
                limit 1 for update
                """, number(attempt.get("orderVerificationCodeId")));
        if (codes.isEmpty()) {
            throw new BusinessException(ErrorCode.CONFLICT, "订单核销码已被使用或已失效");
        }
        Map<String, Object> code = codes.get(0);
        if (!belongsToPrincipal(code, principal) || number(code.get("orderStatus")) != 1 || number(code.get("verifyStatus")) != 0 || expired(code.get("expiresAt"))) {
        if (!belongsToPrincipal(code, principal) || number(code.get("orderStatus")) != 1 || number(code.get("verifyStatus")) != 0) {
            throw new BusinessException(ErrorCode.CONFLICT, "订单当前不可核销");
        }
        int codeUpdated = jdbc.update("""
                update order_dynamic_verification_code set status = 1
                where id = ? and status = 0 and expires_at > now()
                where id = ? and status in (0, 2)
                """, number(code.get("orderVerificationCodeId")));
        int orderUpdated = jdbc.update("""
                update benefit_order set verify_status = 1
@@ -194,7 +195,7 @@
                from free_stay_verification_code c
                join talent_free_stay_voucher v on v.id = c.voucher_id
                left join talent_card tc on tc.id = v.card_id and tc.deleted = 0
                where c.id = ? and c.status = 0
                where c.id = ? and c.status in (0, 2)
                limit 1 for update
                """, number(attempt.get("freeStayVerificationCodeId")));
        if (codes.isEmpty()) {
@@ -202,12 +203,12 @@
        }
        Map<String, Object> code = codes.get(0);
        if (!principal.merchantId().equals(number(code.get("merchantId"))) || number(code.get("voucherStatus")) != 1
                || expiredDate(code.get("validEnd")) || number(code.get("usedCount")) >= number(code.get("totalCount")) || expired(code.get("expiresAt"))) {
                || expiredDate(code.get("validEnd")) || number(code.get("usedCount")) >= number(code.get("totalCount"))) {
            throw new BusinessException(ErrorCode.CONFLICT, "免费入住券当前不可核销");
        }
        int codeUpdated = jdbc.update("""
                update free_stay_verification_code set status = 1
                where id = ? and status = 0 and expires_at > now()
                where id = ? and status in (0, 2)
                """, number(code.get("freeStayVerificationCodeId")));
        int voucherUpdated = jdbc.update("""
                update talent_free_stay_voucher set used_count = used_count + 1
talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantVerificationFreeStayTests.java
New file
@@ -0,0 +1,141 @@
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,
                    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 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));
        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);
    }
    @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, expires_at, status)
                values (30, 20, ?, 2)
                """, 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.confirm(principal, new VerificationConfirmRequest(verificationId),
                "free-stay-confirm-" + verificationId);
        return new MapResult(Boolean.TRUE.equals(result.get("verified")));
    }
    private record MapResult(boolean verified) {}
}
talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantVerificationOrderCodeTests.java
New file
@@ -0,0 +1,136 @@
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.DriverManagerDataSource;
class MerchantVerificationOrderCodeTests {
    private JdbcTemplate jdbc;
    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);
        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,
                    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));
        principal = new MerchantPrincipal(9L, 88L, 7L, "operator");
    }
    @Test
    void confirmsGroupOrderAfterScannedCodeExpiresAndRefreshes() {
        insertOrder("expired-group-order", 1);
        Map<String, Object> confirmed = service.confirm(principal,
                new VerificationConfirmRequest("expired-group-order"), "order-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(() -> service.confirm(principal, new VerificationConfirmRequest("unpaid-group-order"),
                "order-confirm-unpaid-group-order"))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("当前不可核销");
    }
    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)));
    }
}