package com.talentcard.server.business.merchant; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.Map; import com.talentcard.server.business.merchant.auth.MerchantPrincipal; import com.talentcard.server.business.payment.huifu.HuifuOnboardingProgressGateway; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; /** * 通道侧配置失败的进件**不再关单重来**:保留原商户号和银行卡,失败的步骤由刷新重试。 * *
原先的做法是把进件和银行卡一起标成 rejected,好让商户用同一张卡整单重提;规格已明确改成 * 「基本进件成功后的局部失败保留原商户号和银行卡」。这类申请现在走分阶段模型, * {@code closeRetryableApplication} 对它只刷新进度、不释放任何东西。 * *
例外是被通道**明确驳回基本资料**的申请:它交回旧的单状态模型({@code supports()} 对
* {@code rejected} 返回 false),商户端才拿得到「重新提交」入口。
*/
@SpringBootTest
@ActiveProfiles("test")
class MerchantOnboardingRetryReleaseTests {
@Autowired
private JdbcTemplate jdbc;
@Autowired
private MerchantSettlementService settlementService;
// 分阶段刷新会先推进开通步骤,这几个网关都要挡住,否则测试会真的打到汇付。
@MockitoBean private HuifuOnboardingProgressGateway progressGateway;
@MockitoBean private com.talentcard.server.business.payment.huifu.HuifuMerchantWechatConfigGateway wechatConfigGateway;
@MockitoBean private com.talentcard.server.business.payment.huifu.HuifuMerchantBusinessOpenGateway businessOpenGateway;
private Long merchantId;
private Long storeId;
private Long applicationId;
private Long bankCardId;
private String suffix;
@BeforeEach
void prepare() {
suffix = String.valueOf(System.nanoTime());
jdbc.update("insert into merchant(merchant_name, status, deleted) values (?, 1, 0)", "Retry merchant " + suffix);
merchantId = jdbc.queryForObject("select max(id) from merchant", Long.class);
jdbc.update("insert into merchant_store(merchant_id, store_name, status, deleted) values (?, ?, 1, 0)", merchantId, "Retry store " + suffix);
storeId = jdbc.queryForObject("select max(id) from merchant_store", Long.class);
}
@Test
void aFailedProvisioningKeepsTheApplicationAndItsBankCard() {
seedApplication("failed", "not_started");
stubProvider();
settlementService.closeRetryableApplication(principal());
// 关键变化:不再释放。进件与银行卡都留着,失败的开通步骤由刷新去重试——
// 释放掉就等于让商户整单重来,与「保留原商户号和银行卡」相悖。
assertThat(status("merchant_onboarding_application", applicationId)).isEqualTo("pending");
assertThat(status("merchant_bank_card", bankCardId)).isEqualTo("pending");
}
@Test
void aFailedBusinessOpenKeepsTheCardForRetry() {
seedApplication("approved", "failed");
stubProvider();
settlementService.closeRetryableApplication(principal());
assertThat(status("merchant_onboarding_application", applicationId)).isEqualTo("pending");
assertThat(status("merchant_bank_card", bankCardId)).isEqualTo("pending");
}
@Test
void provisioningStillInFlightIsLeftAlone() {
seedApplication("processing", "not_started");
settlementService.closeRetryableApplication(principal());
assertThat(status("merchant_onboarding_application", applicationId)).isEqualTo("pending");
assertThat(status("merchant_bank_card", bankCardId)).isEqualTo("pending");
}
/** 被驳回的申请回到旧的单状态模型,刷新不再接管它——否则会被翻回 pending,重提入口消失。 */
@Test
void aRejectedApplicationIsLeftToTheSingleStatusModel() {
seedApplication("failed", "failed");
jdbc.update("update merchant_onboarding_application set status='rejected', reject_reason='基本资料审核未通过' where id=?", applicationId);
Map