talent-card-server/src/test/java/com/talentcard/server/business/payment/huifu/HuifuBusinessOpenRequiredFieldsTests.java
@@ -79,9 +79,12 @@
        // 个人主体属于小微商户,mcc 也是必填;三个线上场景都要带。
        assertThat(wechat).allSatisfy(entry -> assertThat(entry).containsKeys("mcc", "wx_real_name_info"));
        // 层级:micro_biz_type 在 wx_real_name_info 这个嵌套 jsonObject 里,不在元素顶层。
        // 层级与形态:micro_biz_type 在 wx_real_name_info 这个**嵌套 jsonObject** 里,不在元素顶层,
        // 也不能是 JSON 字符串。官方字段目录里该字段类型是 `Object`(对照 wx_conf_list[] 本身才是
        // `String` 的 jsonArray字符串)——双重编码发出去通道会回「wx_conf_list格式错误」。
        assertThat(wechat.get(0)).doesNotContainKey("micro_biz_type");
        Map<String, Object> realName = JSON.parseObject(String.valueOf(wechat.get(0).get("wx_real_name_info")), Map.class);
        assertThat(wechat.get(0).get("wx_real_name_info")).isInstanceOf(Map.class);
        Map<String, Object> realName = JSON.parseObject(JSON.toJSONString(wechat.get(0).get("wx_real_name_info")), Map.class);
        assertThat(realName).containsEntry("micro_biz_type", "MICRO_TYPE_ONLINE");
    }
@@ -94,6 +97,87 @@
        assertThat(wechat).allSatisfy(entry -> assertThat(entry).doesNotContainKey("wx_real_name_info"));
    }
    /**
     * 个人(小微)商户填了 {@code micro_biz_type},就欠这两张门店照片:官方在 micro_biz_type
     * 的说明里写明「需要提供 F22:店铺门头照图片;F24:店铺内景图片,补充资料填写在
     * extended_material_list 对象中」。原先只有 micro_biz_type、没有材料,开通是缺材料提交。
     */
    @Test
    void anIndividualMerchantCarriesTheStorePhotosAsExtendedMaterial() {
        Map<String, Object> request = HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                properties(), "BO-TEST-MATERIAL", "6666000241390821", "阳野", "5812", true, true, "file-f22", "file-f24");
        List<Map<String, Object>> materials =
                JSON.parseObject(String.valueOf(request.get("extended_material_list")), List.class);
        assertThat(materials).hasSize(2);
        assertThat(materials.get(0)).containsEntry("file_type", "F22").containsEntry("file_id", "file-f22");
        assertThat(materials.get(1)).containsEntry("file_type", "F24").containsEntry("file_id", "file-f24");
    }
    /** 门店照片缺一张就只发一张:宁可让通道明确报缺哪一项,也不要塞占位图顶上。 */
    @Test
    void aMissingStorePhotoIsNotReplacedByAPlaceholder() {
        Map<String, Object> request = HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                properties(), "BO-TEST-MATERIAL-1", "6666000241390821", "阳野", "5812", true, true, "file-f22", "");
        List<Map<String, Object>> materials =
                JSON.parseObject(String.valueOf(request.get("extended_material_list")), List.class);
        assertThat(materials).hasSize(1);
        assertThat(materials.get(0)).containsEntry("file_type", "F22");
    }
    /** 企业主体没有这项材料要求,不要凭空给企业商户加附件。 */
    @Test
    void anEnterpriseMerchantCarriesNoExtendedMaterial() {
        Map<String, Object> request = HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                properties(), "BO-TEST-MATERIAL-2", "6666000241390821", "成都喜望科技", "5812", false, false, "file-f22", "file-f24");
        assertThat(request).doesNotContainKey("extended_material_list");
    }
    /**
     * 个体工商户走企业进件接口,所以不是「个人主体」,但官方把「个体工商户、小微商户」并列为
     * {@code mcc} 必填的一类。配了 {@code fee_rule_id} 也不能把 mcc 省掉——官方请求示例里
     * {@code mcc} 与 {@code fee_rule_id} 本来就出现在同一个元素里。
     *
     * <p>原先这条判断写在 {@code fee_rule_id} 的 else 分支里,平台配了规则号 756 之后
     * 小微/个体工商户的 mcc 永远发不出去,业务开通被通道回 {@code wx_conf_list格式错误}。
     *
     * <p>费率规则号也要换成该主体类型的那一个:同表「线上、类型 01」下个体工商户是 761。
     */
    @Test
    void anIndividualBusinessCarriesTheMccAndItsOwnFeeRule() {
        HuifuPaymentProperties properties = properties();
        properties.setMerchantWechatFeeRuleId("756");
        List<Map<String, Object>> wechat = wechatConfig(HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                properties, "BO-TEST-INDV", "6666000241390821", "阳野餐饮店", "5812", false, true, "", ""));
        assertThat(wechat).hasSize(3);
        assertThat(wechat).allSatisfy(entry -> {
            assertThat(entry).containsEntry("fee_rule_id", "761");
            assertThat(entry).containsEntry("mcc", "5812");
        });
    }
    /**
     * 个人(小微)主体的费率规则号必须换成小微那一行。官方的《微信费率规则ID》对照表按
     * 「支付场景 × 业务开通类型 × 主体类型」定位 CID:线上(场景 4/5、类型 01)下企业是 756、
     * 小微是 775。拿企业的 756 发给小微,通道回的是「微信费率规则号填写不正确,请检查!」——
     * 2026-09-16 真实个人商户实测(该次报文的格式已经正确,纯粹是号选错)。
     */
    @Test
    void aMicroMerchantUsesTheMicroFeeRuleNotTheEnterpriseOne() {
        HuifuPaymentProperties properties = properties();
        properties.setMerchantWechatFeeRuleId("756");
        List<Map<String, Object>> wechat = wechatConfig(HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                properties, "BO-TEST-MICRO", "6666000241804122", "阳野", "5812", true));
        assertThat(wechat).allSatisfy(entry ->
                assertThat(entry).containsEntry("fee_rule_id", "775").containsEntry("mcc", "5812"));
    }
    @Test
    void anEnterpriseCanUseAProviderConfirmedFeeRuleInsteadOfMcc() {
        HuifuPaymentProperties properties = properties();