yeg
2026-09-16 c18b9fac0edf5ebe3aecd74c93ec3f841fe5bf9e
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);
    }
}