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)"); service = new MerchantVerificationService(jdbc, new ManualConsumptionService(jdbc, null)); 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 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 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) {} }