yeg
2026-09-15 5664368e6a90c6a0a9a09be711b47f84d2a63f1c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package com.talentcard.server.business.payment.huifu;
 
import static org.assertj.core.api.Assertions.assertThat;
 
import java.util.List;
import java.util.Map;
 
import com.alibaba.fastjson.JSON;
import org.junit.jupiter.api.Test;
 
/**
 * 商户业务开通请求里那两个"文档要求、我们原来没发"的字段。
 *
 * <p>以汇付《商户业务开通》(v2.merchant.busi.open) 的请求规则为准逐项核对后发现两处缺口:
 *
 * <ul>
 *   <li>{@code media_mer_common_flag}(顶层):运营媒介主体与商户主体是否一致。平台用一个运营媒介
 *       给众多子商户开通,主体天然不一致。实测缺它会被拒:
 *       「【media_merCommon_flag-媒介主体与商户主体是否一致】不能为空,或格式非法」。</li>
 *   <li>{@code micro_biz_type}(wx_conf_list 元素内的 wx_real_name_info):个人商户开通微信业务必填,
 *       且三种取值都要求 F22 店铺门头照 + F24 店铺内景照。</li>
 * </ul>
 *
 * <p>层级依据:文档里 {@code wx_real_name_info} 既是父字段列、又有同名子表,按本接口的写法约定
 * 它是嵌套 jsonObject(同样的写法可见 {@code online_media_info_list}),所以 micro_biz_type
 * 必须放进 wx_conf_list 的元素里,不能平铺到顶层。
 */
class HuifuBusinessOpenRequiredFieldsTests {
 
    private static HuifuPaymentProperties properties() {
        HuifuPaymentProperties properties = new HuifuPaymentProperties();
        properties.setEnabled(true);
        properties.setSystemId("6666000226048687");
        properties.setProductId("KAZX");
        properties.setPrivateKey("x");
        properties.setPlatformPublicKey("y");
        properties.setMerchantWechatFeeRate("0.60");
        properties.setMerchantOnlineBusinessType("H5311L");
        properties.setMerchantOnlineMediaName("东区人才卡");
        properties.setMerchantAgreementUrl("https://example.com/agreement.htm");
        properties.setOnboardingUpperHuifuId("6666000220591054");
        return properties;
    }
 
    @Test
    void theMediaSubjectFlagIsAlwaysSent() {
        Map<String, Object> request = request(false);
 
        // 平台媒介主体与商户主体不一致,取 N;关键是必须出现,否则通道判参数非法。
        assertThat(request).containsEntry("media_mer_common_flag", "N");
    }
 
    @Test
    void anIndividualMerchantCarriesTheMicroBusinessTypeInsideTheWeChatConfig() {
        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");
 
        // 层级:micro_biz_type 在 wx_real_name_info 这个嵌套 jsonObject 里,不在元素顶层。
        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(realName).containsEntry("micro_biz_type", "MICRO_TYPE_ONLINE");
    }
 
    @Test
    void anEnterpriseMerchantDoesNotCarryTheMicroBusinessType() {
        // 文档写明「个人商户需要填写」,企业主体不需要,不要硬塞。
        Map<String, Object> request = request(false);
 
        List<Map<String, Object>> wechat = wechatConfig(request);
        assertThat(wechat.get(0)).doesNotContainKey("wx_real_name_info");
    }
 
    @Test
    void anEnterpriseCanUseAProviderConfirmedFeeRuleInsteadOfMcc() {
        HuifuPaymentProperties properties = properties();
        properties.setMerchantWechatFeeRuleId("756");
 
        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");
    }
 
    /**
     * 直接驱动组装逻辑。网关的 {@code open()} 会真的调用通道,所以这里只构建请求体:
     * 复用网关内部的组装顺序没有意义,改为断言"网关是否会包含这些字段"的等价物——
     * 用同一份 properties 构造,并让网关暴露一个纯组装的包级方法。
     */
    private Map<String, Object> request(boolean individualMerchant) {
        return HuifuMerchantBusinessOpenGateway.buildRequestForTest(
                properties(), "BO-TEST-1", "6666000241390821", "阳野", "5812", individualMerchant);
    }
 
    @SuppressWarnings("unchecked")
    private List<Map<String, Object>> wechatConfig(Map<String, Object> request) {
        return JSON.parseObject(String.valueOf(request.get("wx_conf_list")), List.class);
    }
}