yeg
2026-09-14 39a51637f5857c79fded78e7f63ef6162592f1e0
fix(merchant): 进件后续配置不再被银行卡序列号挡住

applyOnboardingResult 原来把「拿到银行卡序列号 token_no」当成推进后续配置的前置条件:

boolean completed = !blank(huifuId) && !blank(tokenNo);
if (completed) continuePostOnboardingProvisioning(applicationNo);

但 token_no 是结算银行卡的绑卡结果,跟「微信小程序支付配置」「商户业务开通」没有依赖关系。
企业进件的同步响应常常只回 huifu_id 和 apply_no(本次身份 6666000226048687 进件
6666000241335656 时 token_no 就是空的),于是后续两步一次都不会被触发,
商户会一直停在 wechat_config_status/business_open_status = not_started。

改成只要通道给了 huifu_id(即进件已被受理)就推进后续配置;银行卡状态仍按 token_no
是否存在单独判断,不受影响。

同时把 continuePostOnboardingProvisioning 改成 public:审核结果回调、业务开通回调之外的
第三个入口要复用它——主动轮询(本地开发没有公网 IP,汇付的异步通知推不进来)。
方法自身依旧不开事务,注释里写明了原因(能力快照 REQUIRES_NEW 与回调路径的行锁冲突)。

回归:MerchantSettlementCallbackIdempotencyTests、PaymentSettlementTests、
HuifuSettlementRecordGatewayTests 共 27 项通过。
1个文件已修改
20 ■■■■■ 已修改文件
talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java
@@ -176,10 +176,13 @@
    private void applyOnboardingResult(String applicationNo, Long bankCardId, boolean accepted, String huifuId, String applyNo, String tokenNo, String code, String message) {
        if (accepted) {
            boolean completed = !blank(huifuId) && !blank(tokenNo);
            // 只要通道给了商户号就可以往下走后续配置:商户号是「这笔进件已被受理」的标志。
            // token_no 是结算银行卡序列号,属于绑卡结果,不该当成后续配置的前置条件——
            // 用它做闸门会让银行卡审核慢的商户连微信支付配置和业务开通都一起卡住。
            boolean acceptedByProvider = !blank(huifuId);
            jdbc.update("update merchant_onboarding_application set status='pending', provider_huifu_id=nullif(?,''), provider_apply_no=nullif(?,''), provider_response_code=?, provider_response_message=?, reviewed_at=null, reject_reason=null where application_no=?", clean(huifuId), clean(applyNo), clean(code), clean(message), applicationNo);
            jdbc.update("update merchant_bank_card set status=?, provider_token_no=nullif(?,''), provider_apply_no=nullif(?,''), provider_response_code=?, provider_response_message=?, reviewed_at=?, reject_reason=null where id=?", completed ? "approved" : "pending", clean(tokenNo), clean(applyNo), clean(code), clean(message), completed ? LocalDateTime.now() : null, bankCardId);
            if (completed) continuePostOnboardingProvisioning(applicationNo);
            jdbc.update("update merchant_bank_card set status=?, provider_token_no=nullif(?,''), provider_apply_no=nullif(?,''), provider_response_code=?, provider_response_message=?, reviewed_at=?, reject_reason=null where id=?", blank(tokenNo) ? "pending" : "approved", clean(tokenNo), clean(applyNo), clean(code), clean(message), blank(tokenNo) ? null : LocalDateTime.now(), bankCardId);
            if (acceptedByProvider) continuePostOnboardingProvisioning(applicationNo);
        } else {
            String reason = providerReason(code, message);
            jdbc.update("update merchant_onboarding_application set status='rejected', provider_apply_no=nullif(?,''), provider_response_code=?, provider_response_message=?, reject_reason=?, reviewed_at=now() where application_no=?", clean(applyNo), clean(code), clean(message), reason, applicationNo);
@@ -387,7 +390,16 @@
        return true;
    }
    private void continuePostOnboardingProvisioning(String applicationNo) {
    /**
     * 推进进件之后的配置:先微信小程序支付配置,再商户业务开通,两步都通过才算 approved。
     *
     * <p>由三种入口共用,所以是公开方法:审核结果回调、业务开通回调,以及主动轮询
     * (本地开发没有公网 IP 时汇付的异步通知推不进来,只能主动查)。
     *
     * <p>本方法自身不开事务:能力快照用 {@code REQUIRES_NEW} 写,而回调路径是先
     * {@code select ... for update} 锁行再走到这里,在事务内刷新快照会抢同一把行锁。
     */
    public void continuePostOnboardingProvisioning(String applicationNo) {
        List<Map<String, Object>> applications = rows("select * from merchant_onboarding_application where application_no=? limit 1 for update", applicationNo);
        if (applications.isEmpty()) return;
        Map<String, Object> application = applications.get(0);