yeg
2026-09-12 6af5b0ccfcfdee62c53d2edcac97c92ede876692
fix(merchant): 进件审核中不再被误判为拒绝

斗拱业务返回码表里,商户进件类接口的成功码除了 00000000「处理成功」,还有
90000000「审核中」——它表示进件单已被受理、正在审核,不是失败。原来的成功码白名单
没有它,于是一次成功的提交会被 Result.from 解析为 accepted=false,进而让
applyOnboardingResult 把进件单和结算银行卡都置为 rejected:商户在小程序里看到「进件被拒」,
而斗拱其实正在审核。

- 企业和个人两个进件网关的成功码补上 90000000;审核中的进件单不带 token_no,
经既有分支自然落在 pending,等审核回调带回 huifu_id/token_no 后再推进到 approved
- 新增 HuifuOnboardingResultTests,直接锁住 Result.from 的解析:审核中仍为 accepted
且不误标状态、00000000 的正常路径不受影响、商户冻结等失败码仍为 accepted=false、
外层网关码 10000 不参与业务判定

图片上传接口用 00000000 成功码、商户详细信息查询用普通业务返回码,均不含 90000000,
经核对官方文档后未改动。
1个文件已添加
2个文件已修改
110 ■■■■■ 已修改文件
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingGateway.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingGateway.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuOnboardingResultTests.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingGateway.java
@@ -139,7 +139,13 @@
            String code = value(data, "sub_resp_code", "resp_code", "code");
            return new Result(success(code), value(data, "huifu_id", "huifuId"), value(data, "apply_no", "applyNo"), value(data, "token_no", "tokenNo"), code, value(data, "sub_resp_desc", "resp_desc", "message"));
        }
        private static boolean success(String code) { return "00000000".equals(code) || "00000100".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code); }
        /**
         * Merchant onboarding return codes: {@code 00000000} means processed, {@code 90000000} means
         * the application was accepted and is under provider review. Treating the review status as a
         * failure would reject an application the provider actually accepted, so both are accepted
         * here; an accepted application without a merchant number or card token stays pending.
         */
        private static boolean success(String code) { return "00000000".equals(code) || "00000100".equals(code) || "90000000".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code); }
        private static String value(Map<String, Object> data, String... keys) { for (String key : keys) if (data.get(key) != null) return String.valueOf(data.get(key)).trim(); return ""; }
    }
}
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingGateway.java
@@ -116,7 +116,13 @@
            String code = value(data, "sub_resp_code", "resp_code", "code");
            return new Result(success(code), value(data, "huifu_id", "huifuId"), value(data, "apply_no", "applyNo"), value(data, "token_no", "tokenNo"), code, value(data, "sub_resp_desc", "resp_desc", "message"));
        }
        private static boolean success(String code) { return "00000000".equals(code) || "00000100".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code); }
        /**
         * Merchant onboarding return codes: {@code 00000000} means processed, {@code 90000000} means
         * the application was accepted and is under provider review. Treating the review status as a
         * failure would reject an application the provider actually accepted, so both are accepted
         * here; an accepted application without a merchant number or card token stays pending.
         */
        private static boolean success(String code) { return "00000000".equals(code) || "00000100".equals(code) || "90000000".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code); }
        private static String value(Map<String, Object> data, String... keys) { for (String key : keys) if (data.get(key) != null) return String.valueOf(data.get(key)).trim(); return ""; }
    }
}
talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuOnboardingResultTests.java
New file
@@ -0,0 +1,94 @@
package com.talentcard.server.business.payment.huifu;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
/**
 * A merchant onboarding application that the provider accepts for review must not be treated as a
 * failure. The provider answers such a submission with business return code {@code 90000000}
 * ("审核中") in the merchant onboarding return code table, which is neither {@code 00000000} nor a
 * rejection. Parsing it as a rejection would mark a submitted application, and its settlement bank
 * card, as rejected while the provider is still reviewing them.
 */
class HuifuOnboardingResultTests {
    @Test
    void anAcceptedApplicationUnderReviewIsNotAFailure() {
        Map<String, Object> response = response("90000000", "审核中");
        HuifuEnterpriseOnboardingGateway.Result enterprise = HuifuEnterpriseOnboardingGateway.Result.from(response);
        assertThat(enterprise.accepted()).isTrue();
        assertThat(enterprise.code()).isEqualTo("90000000");
        assertThat(enterprise.huifuId()).isEqualTo("6666000123123123");
        assertThat(enterprise.applyNo()).isEqualTo("2022012500073423");
        // The provider issues the card token only when the application is approved.
        assertThat(enterprise.tokenNo()).isEmpty();
        Map<String, Object> individualResponse = response("90000000", "审核中");
        HuifuIndividualOnboardingGateway.Result individual = HuifuIndividualOnboardingGateway.Result.from(individualResponse);
        assertThat(individual.accepted()).isTrue();
        assertThat(individual.huifuId()).isEqualTo("6666000123123123");
        assertThat(individual.tokenNo()).isEmpty();
    }
    @Test
    void anApprovedApplicationKeepsReturningTheMerchantNumberAndCardToken() {
        Map<String, Object> response = response("00000000", "成功");
        businessData(response).put("token_no", "10000713406");
        HuifuEnterpriseOnboardingGateway.Result result = HuifuEnterpriseOnboardingGateway.Result.from(response);
        assertThat(result.accepted()).isTrue();
        assertThat(result.huifuId()).isEqualTo("6666000123123123");
        assertThat(result.applyNo()).isEqualTo("2022012500073423");
        assertThat(result.tokenNo()).isEqualTo("10000713406");
    }
    @Test
    void aProviderRejectionAndAnUnknownOutcomeStayUnaccepted() {
        HuifuEnterpriseOnboardingGateway.Result frozenSubMerchant = HuifuEnterpriseOnboardingGateway.Result.from(response("20000006", "商户状态异常"));
        assertThat(frozenSubMerchant.accepted()).isFalse();
        assertThat(frozenSubMerchant.code()).isEqualTo("20000006");
        assertThat(frozenSubMerchant.message()).isEqualTo("商户状态异常");
        // A business response without a business code must never be read as acceptance.
        assertThat(HuifuIndividualOnboardingGateway.Result.from(response("", "")).accepted()).isFalse();
    }
    @Test
    void theBusinessCodeIsReadFromTheNestedDataObject() {
        // The mock tool and the SDK both nest the business fields under "data"; the outer gateway
        // code is 10000 and must not decide the business outcome.
        Map<String, Object> response = response("90000000", "审核中");
        Map<String, Object> envelope = new HashMap<>(response);
        envelope.put("resp_code", "10000");
        envelope.put("resp_desc", "成功调用");
        assertThat(HuifuEnterpriseOnboardingGateway.Result.from(envelope).accepted()).isTrue();
        assertThat(HuifuEnterpriseOnboardingGateway.Result.from(envelope).code()).isEqualTo("90000000");
    }
    private Map<String, Object> response(String code, String message) {
        Map<String, Object> response = new HashMap<>();
        response.put("data", businessData(code, message));
        return response;
    }
    private Map<String, Object> businessData(String code, String message) {
        Map<String, Object> data = new HashMap<>();
        data.put("resp_code", code);
        data.put("resp_desc", message);
        data.put("huifu_id", "6666000123123123");
        data.put("apply_no", "2022012500073423");
        return data;
    }
    /** The business fields live inside the nested {@code data} object of the provider response. */
    @SuppressWarnings("unchecked")
    private Map<String, Object> businessData(Map<String, Object> response) {
        return (Map<String, Object>) response.get("data");
    }
}