From a058f6109834e29435f5ce4aa19a43d67d07c060 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期五, 18 九月 2026 15:52:59 +0800
Subject: [PATCH] fix(onboarding): 结算银行卡卡号面从未落库,历史进件重提时补齐
---
talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantExistingOnboardingTests.java | 89 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 88 insertions(+), 1 deletions(-)
diff --git a/talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantExistingOnboardingTests.java b/talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantExistingOnboardingTests.java
index e2561cf..20a67f8 100644
--- a/talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantExistingOnboardingTests.java
+++ b/talent-card-server/src/test/java/com/talentcard/server/business/merchant/MerchantExistingOnboardingTests.java
@@ -23,6 +23,9 @@
// 否则测试会真的打到汇付。
@MockitoBean com.talentcard.server.business.payment.huifu.HuifuMerchantWechatConfigGateway wechatConfigGateway;
@MockitoBean com.talentcard.server.business.payment.huifu.HuifuMerchantBusinessOpenGateway businessOpenGateway;
+ // 提交进件会真的上传图片、并向汇付递交申请,这两个也一并挡住——测试不该打网络。
+ @MockitoBean com.talentcard.server.business.payment.huifu.HuifuPictureUploadGateway pictureUploadGateway;
+ @MockitoBean com.talentcard.server.business.payment.huifu.HuifuIndividualOnboardingGateway individualOnboardingGateway;
@Autowired org.springframework.web.context.WebApplicationContext context;
@Autowired com.talentcard.server.business.admin.auth.AdminAuthService adminAuth;
@@ -501,6 +504,45 @@
assertThat(statusOf("store_indoor_url",applicationNo)).isEqualTo("https://cdn.example.com/store-indoor.jpg");
}
+ /**
+ * 存量进件缺「结算银行卡卡号面」时,重新提交要能补上。
+ *
+ * <p>这一列过去从来没落过库(2026-09-18 工单),库里也没有别的地方能还原它;而分阶段模型下
+ * 重复提交不会新建进件,重提就是已提交进件唯一的补救入口,所以这条必须补得住。
+ */
+ @Test
+ void resubmittingSuppliesTheMissingSettlementCardFront() {
+ Map<String,Object> fixture = individualProvisioningFixture();
+ MerchantPrincipal principal = (MerchantPrincipal) fixture.get("principal");
+ String applicationNo = String.valueOf(fixture.get("applicationNo"));
+ assertThat(statusOf("settlement_card_front_url",applicationNo)).isNull();
+
+ service.submitOnboarding(principal, request(
+ "https://cdn.example.com/store-header.jpg",
+ "https://cdn.example.com/store-indoor.jpg",
+ "https://cdn.example.com/card-front.jpg"), "T02-card-"+System.nanoTime());
+
+ assertThat(statusOf("settlement_card_front_url",applicationNo)).isEqualTo("https://cdn.example.com/card-front.jpg");
+ // 没有重新进件,原汇付商户号与银行卡关系都保持。
+ assertThat(jdbc.queryForObject("select count(*) from merchant_onboarding_application where application_no=?",Integer.class,applicationNo)).isEqualTo(1);
+ }
+
+ /** 已经有值的卡号面不会被重新提交覆盖——存档的那张才是当时提交的。 */
+ @Test
+ void resubmittingDoesNotOverwriteAnExistingSettlementCardFront() {
+ Map<String,Object> fixture = individualProvisioningFixture();
+ MerchantPrincipal principal = (MerchantPrincipal) fixture.get("principal");
+ String applicationNo = String.valueOf(fixture.get("applicationNo"));
+ jdbc.update("update merchant_onboarding_application set settlement_card_front_url='https://cdn.example.com/original-card.jpg' where application_no=?", applicationNo);
+
+ service.submitOnboarding(principal, request(
+ "https://cdn.example.com/store-header.jpg",
+ "https://cdn.example.com/store-indoor.jpg",
+ "https://cdn.example.com/other-card.jpg"), "T02-card-"+System.nanoTime());
+
+ assertThat(statusOf("settlement_card_front_url",applicationNo)).isEqualTo("https://cdn.example.com/original-card.jpg");
+ }
+
private Map<String,Object> individualProvisioningFixture() {
String key="T02I-"+System.nanoTime();
jdbc.update("insert into merchant(merchant_name,status,deleted) values (?,1,0)",key);
@@ -515,9 +557,54 @@
/** 补齐门店照片这一步只读这两项,其余字段留空即可。 */
private static MerchantOnboardingRequest request(String storeHeaderUrl,String storeIndoorUrl) {
+ return request(storeHeaderUrl, storeIndoorUrl, null);
+ }
+
+ private static MerchantOnboardingRequest request(String storeHeaderUrl,String storeIndoorUrl,String settlementCardFrontUrl) {
return new MerchantOnboardingRequest("individual","Owner",null,null,null,null,null,null,null,null,
null,null,"510100",null,null,null,null,null,null,null,null,null,null,null,null,null,
- "Owner","13700000000",null,null,"510100",null,null,null,storeHeaderUrl,storeIndoorUrl);
+ "Owner","13700000000",null,null,"510100",null,settlementCardFrontUrl,null,storeHeaderUrl,storeIndoorUrl);
+ }
+
+ /**
+ * 新提交的个人进件要把「结算银行卡卡号面」真的写进库。
+ *
+ * <p>在这之前那条 INSERT 的列清单里没有 {@code settlement_card_front_url}:商户端传了值、后端也
+ * 拿它上传了汇付图片(F13),但写库时被静默丢掉,于是后台详情里这一格永远是 {@code -}。
+ * 断言直接盯数据库那一列,不看接口返回值——返回值在修复前也是「成功」的。
+ */
+ @Test
+ void aNewIndividualSubmissionPersistsTheSettlementCardFront() {
+ String key = "T02C-" + System.nanoTime();
+ jdbc.update("insert into merchant(merchant_name,merchant_type,status,deleted) values (?, '餐饮服务', 1, 0)", key);
+ Long merchant = jdbc.queryForObject("select id from merchant where merchant_name=?", Long.class, key);
+ jdbc.update("insert into merchant_store(merchant_id,store_name,status,deleted) values (?,?,1,0)", merchant, key);
+ Long store = jdbc.queryForObject("select id from merchant_store where merchant_id=?", Long.class, merchant);
+ String clearingCode = String.format("%012d", Math.abs(System.nanoTime() % 1_000_000_000_000L));
+ jdbc.update("insert into bank_branch_directory(clearing_code,branch_name,bank_name,head_bank_code) values (?,?,?,?)",
+ clearingCode, "测试支行", "测试银行", "HB" + clearingCode);
+ when(pictureUploadGateway.uploadUrl(anyString(),anyString())).thenReturn("pic-id");
+ // 通道递交刻意回「驳回」:写库发生在递交**之前**,而驳回会让流程就此收口,
+ // 不再触发后面的开通链路——这条测试只关心那一列有没有落库,其余都是噪音。
+ when(individualOnboardingGateway.create(any())).thenReturn(new com.talentcard.server.business.payment.huifu.HuifuIndividualOnboardingGateway.Result(false,"","","","00000001","参数非法"));
+
+ service.submitOnboarding(new MerchantPrincipal(1L,merchant,store,"owner"), individualRequest(clearingCode), "T02-card-"+System.nanoTime());
+
+ assertThat(jdbc.queryForObject("select settlement_card_front_url from merchant_onboarding_application where merchant_id=?",String.class,merchant))
+ .as("上传了结算卡卡号面就必须落库,否则后台详情只能显示 -")
+ .isEqualTo("https://cdn.example.com/card-front.jpg");
+ }
+
+ /** 个人进件的一次完整提交(除门店照片与结算卡卡号面外的必填项也都给上)。 */
+ private static MerchantOnboardingRequest individualRequest(String clearingCode) {
+ return new MerchantOnboardingRequest(
+ "individual","测试负责人",null,"511623200211245724","fixed","2022-02-14","2032-02-14",
+ null,null,null,"四川省 成都市 武侯区","510000","510100","510107","四川省邻水县丰禾镇9组108号",
+ null,null,"其它",null,null,null,
+ "https://cdn.example.com/id-front.jpg","https://cdn.example.com/id-back.jpg","四川省邻水县丰禾镇9组108号","13700000000","owner@example.com",
+ "测试负责人","13700000000","6222000012345678","510000","510100",clearingCode,
+ "https://cdn.example.com/card-front.jpg",null,
+ "https://cdn.example.com/store-header.jpg","https://cdn.example.com/store-indoor.jpg");
}
/** 建一张还停在 not_started 的进件,用来观察刷新会不会去推进开通步骤。 */
--
Gitblit v1.7.1