From c18b9fac0edf5ebe3aecd74c93ec3f841fe5bf9e Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 16 九月 2026 00:13:18 +0800
Subject: [PATCH] fix(merchant): 修正存量商户微信补开的请求组装与审核中判定

---
 talent-card-server/src/main/resources/application.yml                                                                   |   24 +++-
 talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenResultTests.java         |   36 +++++++
 talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenRequiredFieldsTests.java |   76 ++++++++++++--
 talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuOnboardingMerchantLevelTests.java    |   17 ++-
 talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMerchantBusinessOpenGateway.java     |   88 +++++++++++------
 talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuPaymentProperties.java               |    9 +
 6 files changed, 186 insertions(+), 64 deletions(-)

diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMerchantBusinessOpenGateway.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMerchantBusinessOpenGateway.java
index 22fcd73..84b5493 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMerchantBusinessOpenGateway.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMerchantBusinessOpenGateway.java
@@ -8,6 +8,7 @@
 import com.talentcard.server.common.ErrorCode;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -53,33 +54,45 @@
         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");
-        // 企业微信业务使用汇付确认的 fee_rule_id;仅在未配置规则 ID 时兼容旧的 mcc 路径。
-        // 不得把参考商户的规则 ID 猜测套用到其它行业。
-        if (!properties.getMerchantWechatFeeRuleId().isBlank()) {
-            wechat.put("fee_rule_id", properties.getMerchantWechatFeeRuleId());
-        } else {
-            // 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));
-            }
-        }
+    /**
+     * 线上微信业务场景:4=线上公众号、5=线上小程序、12=线上反扫。
+     *
+     * <p>三个场景一起开:平台的小程序、H5 公众号与反扫共用同一个子商户号,只开小程序会让另外两条
+     * 支付路径在通道侧没有配置。汇付《微信费率规则ID》表里场景 12 没有独立行,它与 4/5 共用同一条
+     * 线上规则——参考商户 {@code 6666000226048687} 线上三场景费率同为 0.70% 且真实生效。
+     */
+    private static final List<String> ONLINE_WECHAT_SCENES = List.of("4", "5", "12");
 
-        Map<String, Object> media = new LinkedHashMap<>();
-        media.put("media_type", "S3");
-        media.put("media_name", properties.getMerchantOnlineMediaName().trim());
+    private Map<String, Object> buildRequest(String requestNo, String huifuId, String shortName, String mcc, boolean individualMerchant) {
+        String feeRuleId = properties.getMerchantWechatFeeRuleId();
+        List<Map<String, Object>> wechatList = new ArrayList<>();
+        for (String scene : ONLINE_WECHAT_SCENES) {
+            Map<String, Object> wechat = new LinkedHashMap<>();
+            wechat.put("pay_scene", scene);
+            // 企业微信业务使用汇付确认的 fee_rule_id;仅在未配置规则 ID 时兼容旧的 mcc 路径。
+            // 不得把参考商户的规则 ID 猜测套用到其它行业。
+            if (!feeRuleId.isBlank()) {
+                wechat.put("fee_rule_id", feeRuleId);
+            } else {
+                // mcc:文档注明「个体工商户、小微商户必填」——个人主体属于小微商户,所以必须带上。
+                wechat.put("mcc", mcc);
+            }
+            wechat.put("fee_rate", properties.getMerchantWechatFeeRate().trim());
+            // 最低收取手续费留空:通道按不设最低收费处理,详情查询读回 fee_min_amt=0.00
+            // (参考商户线上三场景实测就是 0.00)。该字段可选且写明「大于 0」,不要主动送 0。
+            // 实名认证由通道在业务开通成功后另行发起;取 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));
+                }
+            }
+            wechatList.add(wechat);
+        }
 
         Map<String, Object> agreement = new LinkedHashMap<>();
         agreement.put("agreement_type", "3");
@@ -91,12 +104,19 @@
         request.put("huifu_id", huifuId);
         request.put("upper_huifu_id", properties.getOnboardingUpperHuifuId().trim());
         request.put("short_name", shortName);
-        request.put("wx_conf_list", JSON.toJSONString(List.of(wechat)));
+        request.put("wx_conf_list", JSON.toJSONString(wechatList));
         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());
+        // 运营媒介只在开通快捷/网银/余额支付Pro/分账/大额转账/代扣时才需要,本网关只开线上微信,
+        // 因此默认不下发。要下发就把 merchant-online-media-name 配上;注意声明
+        // media_mer_common_flag=N(媒介主体与商户主体不一致)时,通道会一并索要授权或开发证明材料。
+        String mediaName = properties.getMerchantOnlineMediaName().trim();
+        if (!mediaName.isBlank()) {
+            Map<String, Object> media = new LinkedHashMap<>();
+            media.put("media_type", "S3");
+            media.put("media_name", mediaName);
+            putIfPresent(media, "media_mer_common_flag", properties.getMerchantMediaSubjectSame());
+            request.put("online_media_info_list", JSON.toJSONString(List.of(media)));
+        }
         request.put("agreement_info", JSON.toJSONString(agreement));
         // 进件时就把延迟入账能力配好:先付钱后核销的订单必须把钱留在通道「待确认」,
         // 核销确认后才分配给商户。forced_delay_flag 保持关闭——支付即核销的到店消费订单不该被延迟。
@@ -112,7 +132,7 @@
 
     private void ensureConfigured() {
         if (!properties.isEnabled()) throw new BusinessException(ErrorCode.CONFLICT, "Huifu payment is disabled");
-        if (blank(properties.getSystemId()) || blank(properties.getProductId()) || blank(properties.getOnboardingUpperHuifuId()) || blank(properties.getMerchantWechatFeeRate()) || blank(properties.getMerchantOnlineBusinessType()) || blank(properties.getMerchantOnlineMediaName()) || blank(properties.getMerchantAgreementUrl()) || blank(properties.getPrivateKey()) || blank(properties.getPlatformPublicKey())) throw new BusinessException(ErrorCode.CONFLICT, "Huifu merchant business opening configuration is incomplete");
+        if (blank(properties.getSystemId()) || blank(properties.getProductId()) || blank(properties.getOnboardingUpperHuifuId()) || blank(properties.getMerchantWechatFeeRate()) || blank(properties.getMerchantOnlineBusinessType()) || blank(properties.getMerchantAgreementUrl()) || blank(properties.getPrivateKey()) || blank(properties.getPlatformPublicKey())) throw new BusinessException(ErrorCode.CONFLICT, "Huifu merchant business opening configuration is incomplete");
     }
 
     private void initializeSdk() throws Exception {
@@ -138,7 +158,9 @@
             String code = value(data, "sub_resp_code", "resp_code", "code");
             return new Result(success(code), value(data, "apply_no", "applyNo"), 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); }
+        // 90000000 = 审核中:业务开通已被受理、微信子商户号当场就入驻成功,但整张申请单仍在审核。
+        // 它与两个进件网关的判定保持一致;漏掉它会把一次成功受理记成 business_open_status=failed。
+        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 ""; }
     }
 }
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuPaymentProperties.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuPaymentProperties.java
index 431d210..3e1dca0 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuPaymentProperties.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuPaymentProperties.java
@@ -37,11 +37,12 @@
     /**
      * 商户业务开通的 {@code use_chains_flag}:是否用上级(渠道商)的微信商户号发起交易。
      *
-     * <p>我们是平台自有的一个小程序服务多个商户,没有实现「微信特约商户进件」给每个商户单独
-     * 申请微信商户号,所以保持 {@code 3}——仅微信、复用上级商户号。留空则不下发该字段,
-     * 由商户使用自己的微信商户号。
+     * <p>留空(默认)即不下发该字段,子商户会在渠道下拿到自己的微信子商户号。
+     * 不要下发 {@code 3}:那是「复用上级微信商户号」,要求上级本身有商户级微信配置,而我们的上级是
+     * 渠道商(没有商户级配置,查询返回「huifuId不存在」),实测会被通道拒:
+     * 「失败:复用交易信息,上级商户号为空」。
      */
-    private String merchantUseChainsFlag = "3";
+    private String merchantUseChainsFlag = "";
     private String merchantWechatFeeRate = "0.60";
     /**
      * 企业微信业务开通使用的汇付费率规则 ID。企业场景应优先填写该值,
diff --git a/talent-card-server/src/main/resources/application.yml b/talent-card-server/src/main/resources/application.yml
index 33228f3..a5c0b2c 100644
--- a/talent-card-server/src/main/resources/application.yml
+++ b/talent-card-server/src/main/resources/application.yml
@@ -88,15 +88,23 @@
       # 进件只做「渠道商 → 普通商户」两级:不再用 head_office_flag=0 + head_huifu_id 把商户
       # 建成平台自己的下级商户。实测那条路没有收益(建完照样 20002 数据权限认证失败),
       # 还会按总部客群类型索要 F725/F726/F727 材料。
-      # 商户业务开通是否复用上级(渠道商)的微信商户号:3=仅微信;留空=不下发该字段。
-      merchant-use-chains-flag: ${HUIFU_MERCHANT_USE_CHAINS_FLAG:3}
-      merchant-wechat-fee-rate: ${HUIFU_MERCHANT_WECHAT_FEE_RATE:0.60}
-      # 企业微信开通应填写汇付按实际行业确认的规则 ID;留空时沿用 mcc 兼容路径。
-      merchant-wechat-fee-rule-id: ${HUIFU_MERCHANT_WECHAT_FEE_RULE_ID:}
+      # 是否复用上级(渠道商)的微信商户号:1=微信+支付宝,2=不使用,3=仅微信,4=支付宝;留空=不下发。
+      # 渠道商 6666000220591054 本身没有商户级微信配置(查询返回「huifuId不存在」),实测下发 3 会被拒:
+      # 「失败:复用交易信息,上级商户号为空」。不下发时,子商户在渠道 00016904 下拿到自己的微信
+      # 子商户号(实测 sub_mer_id=W15737921333057628112,sub_mer_name 即商户注册名)。
+      merchant-use-chains-flag: ${HUIFU_MERCHANT_USE_CHAINS_FLAG:}
+      merchant-wechat-fee-rate: ${HUIFU_MERCHANT_WECHAT_FEE_RATE:0.70}
+      # 企业微信业务的费率规则号(cid)。756 是汇付《微信费率规则ID》表里「企业 + fee_type=01(线上)」
+      # 唯一的一条规则,细分行业含「就业信息平台/其他信息服务平台」,与本平台业务对应;
+      # 场景 12 在该表没有独立行,与线上公众号/小程序共用这一条。留空则退回 mcc 兼容路径。
+      merchant-wechat-fee-rule-id: ${HUIFU_MERCHANT_WECHAT_FEE_RULE_ID:756}
       merchant-online-business-type: ${HUIFU_MERCHANT_ONLINE_BUSINESS_TYPE:H5311L}
-      merchant-online-media-name: ${HUIFU_MERCHANT_ONLINE_MEDIA_NAME:东区人才卡}
-      # 运营媒介主体与商户主体是否一致。平台用一个运营媒介给众多子商户开通,主体天然不一致。
-      # 这是商户业务开通的顶层必填,缺了会被通道拒(实测报 media_merCommon_flag 不能为空)。
+      # 运营媒介(online_media_info_list)。文档写明它只在开通快捷/网银/余额支付Pro/分账/
+      # 大额转账/代扣时才需要,且是「基本信息入驻接口」的必填项,不是「商户业务开通」的必填项;
+      # 本平台只开线上微信,所以默认留空即不下发。要下发时再配媒介名称。
+      merchant-online-media-name: ${HUIFU_MERCHANT_ONLINE_MEDIA_NAME:}
+      # 媒介主体与商户主体是否一致;它是 online_media_info_list 的**元素字段**,不是顶层参数。
+      # 声明 N(不一致)时通道会一并索要授权或开发证明材料,因此不下发媒介时它也用不上。
       merchant-media-subject-same: ${HUIFU_MERCHANT_MEDIA_SUBJECT_SAME:N}
       # 小微经营类型,个人商户开通微信业务时必填;三种取值都要求 F22 门头照 + F24 内景照。
       merchant-micro-business-type: ${HUIFU_MERCHANT_MICRO_BUSINESS_TYPE:MICRO_TYPE_ONLINE}
diff --git a/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenRequiredFieldsTests.java b/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenRequiredFieldsTests.java
index 7e55a80..fe0091d 100644
--- a/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenRequiredFieldsTests.java
+++ b/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenRequiredFieldsTests.java
@@ -2,6 +2,7 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -14,9 +15,9 @@
  * <p>以汇付《商户业务开通》(v2.merchant.busi.open) 的请求规则为准逐项核对后发现两处缺口:
  *
  * <ul>
- *   <li>{@code media_mer_common_flag}(顶层):运营媒介主体与商户主体是否一致。平台用一个运营媒介
- *       给众多子商户开通,主体天然不一致。实测缺它会被拒:
- *       「【media_merCommon_flag-媒介主体与商户主体是否一致】不能为空,或格式非法」。</li>
+ *   <li>{@code media_mer_common_flag}({@code online_media_info_list} 的元素字段):运营媒介主体与商户主体
+ *       是否一致。平台用一个运营媒介给众多子商户开通,主体天然不一致,取 N。它属于媒介**元素**而不是顶层参数,
+ *       放顶层会被拒:「【media_merCommon_flag-媒介主体与商户主体是否一致】不能为空,或格式非法」。</li>
  *   <li>{@code micro_biz_type}(wx_conf_list 元素内的 wx_real_name_info):个人商户开通微信业务必填,
  *       且三种取值都要求 F22 店铺门头照 + F24 店铺内景照。</li>
  * </ul>
@@ -43,11 +44,30 @@
     }
 
     @Test
-    void theMediaSubjectFlagIsAlwaysSent() {
+    void theMediaSubjectFlagIsInsideTheMediaElement() {
         Map<String, Object> request = request(false);
 
-        // 平台媒介主体与商户主体不一致,取 N;关键是必须出现,否则通道判参数非法。
-        assertThat(request).containsEntry("media_mer_common_flag", "N");
+        // 平台媒介主体与商户主体不一致,取 N。它是 online_media_info_list 的**元素字段**,
+        // 不是顶层参数——放顶层会被通道判「不能为空,或格式非法」。
+        assertThat(request).doesNotContainKey("media_mer_common_flag");
+
+        Map<String, Object> media = onlineMedia(request);
+        assertThat(media).containsEntry("media_mer_common_flag", "N");
+    }
+
+    /**
+     * 运营媒介只在开通快捷/网银/余额支付Pro/分账/大额转账/代扣时才需要,本平台只开线上微信,
+     * 默认不下发;不下发媒介,通道也就不会再索要「授权或开发证明材料」。
+     */
+    @Test
+    void aBlankMediaNameKeepsTheMediaDeclarationOutOfTheRequest() {
+        HuifuPaymentProperties properties = properties();
+        properties.setMerchantOnlineMediaName(" ");
+
+        Map<String, Object> request = HuifuMerchantBusinessOpenGateway.buildRequestForTest(
+                properties, "BO-TEST-1", "6666000241390821", "阳野", "5812", false);
+
+        assertThat(request).doesNotContainKey("online_media_info_list");
     }
 
     @Test
@@ -55,9 +75,9 @@
         Map<String, Object> request = request(true);
 
         List<Map<String, Object>> wechat = wechatConfig(request);
-        assertThat(wechat).hasSize(1);
-        // 个人主体属于小微商户,mcc 也是必填。
-        assertThat(wechat.get(0)).containsKeys("mcc", "wx_real_name_info");
+        assertThat(wechat).hasSize(3);
+        // 个人主体属于小微商户,mcc 也是必填;三个线上场景都要带。
+        assertThat(wechat).allSatisfy(entry -> assertThat(entry).containsKeys("mcc", "wx_real_name_info"));
 
         // 层级:micro_biz_type 在 wx_real_name_info 这个嵌套 jsonObject 里,不在元素顶层。
         assertThat(wechat.get(0)).doesNotContainKey("micro_biz_type");
@@ -71,7 +91,7 @@
         Map<String, Object> request = request(false);
 
         List<Map<String, Object>> wechat = wechatConfig(request);
-        assertThat(wechat.get(0)).doesNotContainKey("wx_real_name_info");
+        assertThat(wechat).allSatisfy(entry -> assertThat(entry).doesNotContainKey("wx_real_name_info"));
     }
 
     @Test
@@ -82,8 +102,32 @@
         List<Map<String, Object>> wechat = wechatConfig(HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                 properties, "BO-TEST-RULE", "6666000241335656", "成都喜望科技", "7372", false));
 
-        assertThat(wechat.get(0)).containsEntry("fee_rule_id", "756");
-        assertThat(wechat.get(0)).doesNotContainKey("mcc");
+        assertThat(wechat).allSatisfy(entry -> {
+            assertThat(entry).containsEntry("fee_rule_id", "756");
+            assertThat(entry).doesNotContainKey("mcc");
+        });
+    }
+
+    /**
+     * 线上公众号、小程序、反扫三个场景一起开。只开小程序会让另外两条支付路径在通道侧缺配置,
+     * 而平台的小程序、H5 公众号与反扫共用同一个子商户号。
+     */
+    @Test
+    void theOnlineWeChatScenesAreOpenedTogether() {
+        List<Map<String, Object>> wechat = wechatConfig(request(false));
+
+        List<String> scenes = new ArrayList<>();
+        for (Map<String, Object> entry : wechat) scenes.add(String.valueOf(entry.get("pay_scene")));
+        assertThat(scenes).containsExactly("4", "5", "12");
+
+        assertThat(wechat).allSatisfy(entry -> {
+            assertThat(entry).containsEntry("fee_rate", "0.60");
+            // 最低收取手续费可选、文档写明「大于 0」:留空让通道按不设最低收费处理,
+            // 详情查询读回 fee_min_amt=0.00(参考商户线上三场景实测即 0.00),不要主动送 0。
+            assertThat(entry).doesNotContainKey("fee_min_amt");
+            // 不填子渠道号即入驻到渠道默认配置的微信渠道号下。
+            assertThat(entry).doesNotContainKey("pay_channel_id");
+        });
     }
 
     /**
@@ -100,4 +144,12 @@
     private List<Map<String, Object>> wechatConfig(Map<String, Object> request) {
         return JSON.parseObject(String.valueOf(request.get("wx_conf_list")), List.class);
     }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> onlineMedia(Map<String, Object> request) {
+        List<Map<String, Object>> media =
+                JSON.parseObject(String.valueOf(request.get("online_media_info_list")), List.class);
+        assertThat(media).hasSize(1);
+        return media.get(0);
+    }
 }
diff --git a/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenResultTests.java b/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenResultTests.java
new file mode 100644
index 0000000..f122f5d
--- /dev/null
+++ b/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenResultTests.java
@@ -0,0 +1,36 @@
+package com.talentcard.server.business.payment.huifu;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * 商户业务开通的回执判定。
+ *
+ * <p>{@code 90000000=审核中} 表示申请已被受理(微信子商户号当场就入驻成功),整张申请单仍在审核,
+ * 它与两个进件网关的判定一致。漏掉它会把一次成功受理记成 {@code business_open_status=failed},
+ * 两端页面就会把已经开好的商户显示成失败。
+ */
+class HuifuBusinessOpenResultTests {
+
+    @Test
+    void aSubmittedApplicationUnderReviewCountsAsAccepted() {
+        HuifuMerchantBusinessOpenGateway.Result result = HuifuMerchantBusinessOpenGateway.Result.from(
+                Map.of("resp_code", "90000000", "resp_desc", "审核中", "apply_no", "2026091530554624"));
+
+        assertThat(result.accepted()).isTrue();
+        assertThat(result.applyNo()).isEqualTo("2026091530554624");
+        assertThat(result.code()).isEqualTo("90000000");
+    }
+
+    @Test
+    void aRejectedApplicationIsNotAccepted() {
+        HuifuMerchantBusinessOpenGateway.Result result = HuifuMerchantBusinessOpenGateway.Result.from(
+                Map.of("resp_code", "00000001", "resp_desc", "基础参数校验失败"));
+
+        assertThat(result.accepted()).isFalse();
+        assertThat(result.applyNo()).isEmpty();
+    }
+}
diff --git a/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuOnboardingMerchantLevelTests.java b/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuOnboardingMerchantLevelTests.java
index 83052e9..cdaaf24 100644
--- a/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuOnboardingMerchantLevelTests.java
+++ b/talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuOnboardingMerchantLevelTests.java
@@ -47,26 +47,29 @@
     }
 
     /**
-     * 商户自己没有微信商户号(我们没实现「微信特约商户进件」),开通时仍然复用上级商户号发起交易,
-     * 否则子商户根本没有可用的微信商户号。留空该配置即不下发字段。
+     * 默认不下发 {@code use_chains_flag}:子商户在渠道下取得自己的微信子商户号。
+     *
+     * <p>下发 {@code 3}(复用上级微信商户号)曾是默认值,但上级是渠道商、没有商户级微信配置,
+     * 通道会拒:「失败:复用交易信息,上级商户号为空」。2026-09-15 的真实补开在不带该字段时被受理,
+     * 线上公众号/小程序/反扫三场景各自入驻成功并返回自己的 {@code sub_mer_id}。
      */
     @Test
-    void businessOpenReusesTheSuperiorWeChatMerchantNumber() {
+    void businessOpenLeavesTheUseChainsFlagOutByDefault() {
         Map<String, Object> request = HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                 properties(), "BO-TEST-1", "6666000241335656", "成都喜望科技", "5812", false);
 
-        assertThat(request).containsEntry("use_chains_flag", "3");
+        assertThat(request).doesNotContainKey("use_chains_flag");
     }
 
     @Test
-    void aBlankUseChainsFlagKeepsTheFieldOutOfTheRequest() {
+    void aConfiguredUseChainsFlagIsSentAsIs() {
         HuifuPaymentProperties properties = properties();
-        properties.setMerchantUseChainsFlag(" ");
+        properties.setMerchantUseChainsFlag("3");
 
         Map<String, Object> request = HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                 properties, "BO-TEST-1", "6666000241335656", "成都喜望科技", "5812", false);
 
-        assertThat(request).doesNotContainKey("use_chains_flag");
+        assertThat(request).containsEntry("use_chains_flag", "3");
     }
 
     private static HuifuPaymentProperties properties() {

--
Gitblit v1.7.1