yeg
2026-09-15 c3922ab8f016b3664738c908a34eb049bcdfd080
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMerchantBusinessOpenGateway.java
@@ -23,13 +23,53 @@
    public HuifuMerchantBusinessOpenGateway(HuifuPaymentProperties properties) { this.properties = properties; }
    public Result open(String requestNo, String huifuId, String shortName, String mcc) {
    public Result open(String requestNo, String huifuId, String shortName, String mcc, boolean individualMerchant) {
        ensureConfigured();
        Map<String, Object> request = buildRequest(requestNo, huifuId, shortName, mcc, individualMerchant);
        try {
            synchronized (SDK_LOCK) {
                initializeSdk();
                Map<String, Object> response = BasePayClient.request(request, "v2.merchant.busi.open", false);
                log.info("Huifu merchant business open response: requestNo={}, huifuId={}, response={}", requestNo, huifuId, JSON.toJSONString(response));
                return Result.from(response);
            }
        } catch (BusinessException exception) {
            throw exception;
        } catch (Exception exception) {
            log.error("Huifu merchant business open request error: requestNo={}, huifuId={}", requestNo, huifuId, exception);
            throw new HuifuPaymentGatewayException("Huifu merchant business open request failed", outcomeUnknown(exception), exception);
        }
    }
    /**
     * 组装「商户业务开通」请求体。与发送解耦,便于直接断言字段与层级
     * (见 {@code HuifuBusinessOpenRequiredFieldsTests})。
     *
     * <p>{@code individualMerchant} 为真时把 {@code micro_biz_type} 塞进
     * {@code wx_conf_list} 元素里的 {@code wx_real_name_info}——文档写明个人商户需要填写,
     * 而该字段在文档中属于 wx_real_name_info 子表(父字段列 + 同名子表的写法约定即为嵌套对象)。
     */
    static Map<String, Object> buildRequestForTest(HuifuPaymentProperties properties, String requestNo, String huifuId, String shortName, String mcc, boolean individualMerchant) {
        return new HuifuMerchantBusinessOpenGateway(properties).buildRequest(requestNo, huifuId, shortName, mcc, individualMerchant);
    }
    private Map<String, Object> buildRequest(String requestNo, String huifuId, String shortName, String mcc, boolean individualMerchant) {
        Map<String, Object> wechat = new LinkedHashMap<>();
        wechat.put("pay_scene", "5");
        // mcc:文档注明「个体工商户、小微商户必填」——个人主体属于小微商户,所以必须带上。
        wechat.put("mcc", mcc);
        wechat.put("fee_rate", properties.getMerchantWechatFeeRate().trim());
        // 实名认证由通道在业务开通成功后另行发起;取 N 表示本次不发起。
        // 改成 Y 之前要先确认实名所需的材料(含 F22/F24 照片)已就绪。
        wechat.put("is_check_real_name", "N");
        if (individualMerchant) {
            String microBizType = properties.getMerchantMicroBusinessType();
            if (!microBizType.isBlank()) {
                Map<String, Object> realName = new LinkedHashMap<>();
                realName.put("micro_biz_type", microBizType);
                wechat.put("wx_real_name_info", JSON.toJSONString(realName));
            }
        }
        Map<String, Object> media = new LinkedHashMap<>();
        media.put("media_type", "S3");
@@ -48,6 +88,9 @@
        request.put("wx_conf_list", JSON.toJSONString(List.of(wechat)));
        request.put("online_busi_type", properties.getMerchantOnlineBusinessType().trim());
        request.put("online_media_info_list", JSON.toJSONString(List.of(media)));
        // 运营媒介主体与商户主体是否一致。平台用一个运营媒介为众多子商户开通,主体天然不一致,
        // 而且它是顶层必填——不传会被通道拒(实测报「media_merCommon_flag…不能为空,或格式非法」)。
        putIfPresent(request, "media_mer_common_flag", properties.getMerchantMediaSubjectSame());
        request.put("agreement_info", JSON.toJSONString(agreement));
        // 进件时就把延迟入账能力配好:先付钱后核销的订单必须把钱留在通道「待确认」,
        // 核销确认后才分配给商户。forced_delay_flag 保持关闭——支付即核销的到店消费订单不该被延迟。
@@ -59,19 +102,7 @@
        if (properties.isOnboardingUnderParentMerchant()) request.put("use_chains_flag", "3");
        putIfPresent(request, "async_return_url", properties.getMerchantBusinessOpenNotifyUrl());
        putIfPresent(request, "busi_async_return_url", properties.getMerchantBusinessOpenNotifyUrl());
        try {
            synchronized (SDK_LOCK) {
                initializeSdk();
                Map<String, Object> response = BasePayClient.request(request, "v2.merchant.busi.open", false);
                log.info("Huifu merchant business open response: requestNo={}, huifuId={}, response={}", requestNo, huifuId, JSON.toJSONString(response));
                return Result.from(response);
            }
        } catch (BusinessException exception) {
            throw exception;
        } catch (Exception exception) {
            log.error("Huifu merchant business open request error: requestNo={}, huifuId={}", requestNo, huifuId, exception);
            throw new HuifuPaymentGatewayException("Huifu merchant business open request failed", outcomeUnknown(exception), exception);
        }
        return request;
    }
    private void ensureConfigured() {