|
| 1 | +# 支持一个商户号对应多个 appId 的使用说明 |
| 2 | + |
| 3 | +## 背景 |
| 4 | + |
| 5 | +在实际业务中,经常会遇到一个微信支付商户号需要绑定多个小程序的场景。例如: |
| 6 | +- 一个商家有多个小程序(主店、分店、活动小程序等) |
| 7 | +- 所有小程序共用同一个支付商户号 |
| 8 | +- 支付配置(商户号、密钥、证书等)完全相同,只有 appId 不同 |
| 9 | + |
| 10 | +## 解决方案 |
| 11 | + |
| 12 | +WxJava 支持在配置多个相同商户号、不同 appId 的情况下,**可以仅通过商户号进行配置切换**,无需每次都指定 appId。 |
| 13 | + |
| 14 | +## 使用方式 |
| 15 | + |
| 16 | +### 1. 配置多个 appId |
| 17 | + |
| 18 | +```java |
| 19 | +WxPayService payService = new WxPayServiceImpl(); |
| 20 | + |
| 21 | +String mchId = "1234567890"; // 商户号 |
| 22 | + |
| 23 | +// 配置小程序1 |
| 24 | +WxPayConfig config1 = new WxPayConfig(); |
| 25 | +config1.setMchId(mchId); |
| 26 | +config1.setAppId("wx1111111111111111"); // 小程序1的appId |
| 27 | +config1.setMchKey("your_mch_key"); |
| 28 | +config1.setApiV3Key("your_api_v3_key"); |
| 29 | +// ... 其他配置 |
| 30 | + |
| 31 | +// 配置小程序2 |
| 32 | +WxPayConfig config2 = new WxPayConfig(); |
| 33 | +config2.setMchId(mchId); |
| 34 | +config2.setAppId("wx2222222222222222"); // 小程序2的appId |
| 35 | +config2.setMchKey("your_mch_key"); |
| 36 | +config2.setApiV3Key("your_api_v3_key"); |
| 37 | +// ... 其他配置 |
| 38 | + |
| 39 | +// 配置小程序3 |
| 40 | +WxPayConfig config3 = new WxPayConfig(); |
| 41 | +config3.setMchId(mchId); |
| 42 | +config3.setAppId("wx3333333333333333"); // 小程序3的appId |
| 43 | +config3.setMchKey("your_mch_key"); |
| 44 | +config3.setApiV3Key("your_api_v3_key"); |
| 45 | +// ... 其他配置 |
| 46 | + |
| 47 | +// 添加到配置映射 |
| 48 | +Map<String, WxPayConfig> configMap = new HashMap<>(); |
| 49 | +configMap.put(mchId + "_" + config1.getAppId(), config1); |
| 50 | +configMap.put(mchId + "_" + config2.getAppId(), config2); |
| 51 | +configMap.put(mchId + "_" + config3.getAppId(), config3); |
| 52 | + |
| 53 | +payService.setMultiConfig(configMap); |
| 54 | +``` |
| 55 | + |
| 56 | +### 2. 切换配置的方式 |
| 57 | + |
| 58 | +#### 方式一:精确切换(原有方式,向后兼容) |
| 59 | + |
| 60 | +```java |
| 61 | +// 切换到小程序1的配置 |
| 62 | +payService.switchover("1234567890", "wx1111111111111111"); |
| 63 | + |
| 64 | +// 切换到小程序2的配置 |
| 65 | +payService.switchover("1234567890", "wx2222222222222222"); |
| 66 | +``` |
| 67 | + |
| 68 | +#### 方式二:仅使用商户号切换(新功能) |
| 69 | + |
| 70 | +```java |
| 71 | +// 仅使用商户号切换,会自动匹配该商户号的某个配置 |
| 72 | +// 适用于不关心具体使用哪个 appId 的场景 |
| 73 | +boolean success = payService.switchover("1234567890"); |
| 74 | +``` |
| 75 | + |
| 76 | +**注意**:当使用仅商户号切换时,会按照以下逻辑查找配置: |
| 77 | +1. 先尝试精确匹配商户号(针对只配置商户号、没有 appId 的情况) |
| 78 | +2. 如果未找到,则尝试前缀匹配(查找以 `商户号_` 开头的配置) |
| 79 | +3. 如果有多个匹配项,将返回其中任意一个匹配项,具体选择结果不保证稳定或可预测,如需确定性行为请使用精确匹配方式(同时指定商户号和 appId) |
| 80 | + |
| 81 | +#### 方式三:链式调用 |
| 82 | + |
| 83 | +```java |
| 84 | +// 精确切换,支持链式调用 |
| 85 | +WxPayUnifiedOrderResult result = payService |
| 86 | + .switchoverTo("1234567890", "wx1111111111111111") |
| 87 | + .unifiedOrder(request); |
| 88 | + |
| 89 | +// 仅商户号切换,支持链式调用 |
| 90 | +WxPayUnifiedOrderResult result = payService |
| 91 | + .switchoverTo("1234567890") |
| 92 | + .unifiedOrder(request); |
| 93 | +``` |
| 94 | + |
| 95 | +### 3. 动态添加配置 |
| 96 | + |
| 97 | +```java |
| 98 | +// 运行时动态添加新的 appId 配置 |
| 99 | +WxPayConfig newConfig = new WxPayConfig(); |
| 100 | +newConfig.setMchId("1234567890"); |
| 101 | +newConfig.setAppId("wx4444444444444444"); |
| 102 | +// ... 其他配置 |
| 103 | + |
| 104 | +payService.addConfig("1234567890", "wx4444444444444444", newConfig); |
| 105 | + |
| 106 | +// 切换到新添加的配置 |
| 107 | +payService.switchover("1234567890", "wx4444444444444444"); |
| 108 | +``` |
| 109 | + |
| 110 | +### 4. 移除配置 |
| 111 | + |
| 112 | +```java |
| 113 | +// 移除特定的 appId 配置 |
| 114 | +payService.removeConfig("1234567890", "wx1111111111111111"); |
| 115 | +``` |
| 116 | + |
| 117 | +## 实际应用场景 |
| 118 | + |
| 119 | +### 场景1:根据用户来源切换 appId |
| 120 | + |
| 121 | +```java |
| 122 | +// 在支付前,根据订单来源切换到对应小程序的配置 |
| 123 | +String orderSource = order.getSource(); // 例如: "miniapp1", "miniapp2" |
| 124 | +String appId = getAppIdBySource(orderSource); |
| 125 | + |
| 126 | +// 精确切换到特定小程序 |
| 127 | +payService.switchover(mchId, appId); |
| 128 | + |
| 129 | +// 创建订单 |
| 130 | +WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); |
| 131 | +// ... 设置订单参数 |
| 132 | +WxPayUnifiedOrderResult result = payService.unifiedOrder(request); |
| 133 | +``` |
| 134 | + |
| 135 | +### 场景2:处理支付回调 |
| 136 | + |
| 137 | +```java |
| 138 | +@PostMapping("/pay/notify") |
| 139 | +public String handlePayNotify(@RequestBody String xmlData) { |
| 140 | + try { |
| 141 | + // 解析回调通知 |
| 142 | + WxPayOrderNotifyResult notifyResult = payService.parseOrderNotifyResult(xmlData); |
| 143 | + |
| 144 | + // 注意:parseOrderNotifyResult 方法内部会自动调用 |
| 145 | + // switchover(notifyResult.getMchId(), notifyResult.getAppid()) |
| 146 | + // 切换到正确的配置进行签名验证 |
| 147 | + |
| 148 | + // 处理业务逻辑 |
| 149 | + processOrder(notifyResult); |
| 150 | + |
| 151 | + return WxPayNotifyResponse.success("成功"); |
| 152 | + } catch (WxPayException e) { |
| 153 | + log.error("支付回调处理失败", e); |
| 154 | + return WxPayNotifyResponse.fail("失败"); |
| 155 | + } |
| 156 | +} |
| 157 | +``` |
| 158 | + |
| 159 | +### 场景3:不关心具体 appId 的场景 |
| 160 | + |
| 161 | +```java |
| 162 | +// 某些场景下,只要是该商户号的配置即可,不关心具体是哪个 appId |
| 163 | +// 例如:查询订单、退款等操作 |
| 164 | + |
| 165 | +// 仅使用商户号切换 |
| 166 | +payService.switchover(mchId); |
| 167 | + |
| 168 | +// 查询订单 |
| 169 | +WxPayOrderQueryResult queryResult = payService.queryOrder(null, outTradeNo); |
| 170 | + |
| 171 | +// 申请退款 |
| 172 | +WxPayRefundRequest refundRequest = new WxPayRefundRequest(); |
| 173 | +// ... 设置退款参数 |
| 174 | +WxPayRefundResult refundResult = payService.refund(refundRequest); |
| 175 | +``` |
| 176 | + |
| 177 | +## 注意事项 |
| 178 | + |
| 179 | +1. **向后兼容**:所有原有的使用方式继续有效,不需要修改现有代码。 |
| 180 | + |
| 181 | +2. **配置隔离**:每个 `mchId + appId` 组合都是独立的配置,修改一个配置不会影响其他配置。 |
| 182 | + |
| 183 | +3. **线程安全**:配置切换使用 `WxPayConfigHolder`(基于 `ThreadLocal`),是线程安全的。 |
| 184 | + |
| 185 | +4. **自动切换**:在处理支付回调时,SDK 会自动根据回调中的 `mchId` 和 `appId` 切换到正确的配置。 |
| 186 | + |
| 187 | +5. **推荐实践**: |
| 188 | + - 如果知道具体的 appId,建议使用精确切换方式,避免歧义 |
| 189 | + - 如果使用仅商户号切换,确保该商户号下至少有一个可用的配置 |
| 190 | + |
| 191 | +## 相关 API |
| 192 | + |
| 193 | +| 方法 | 参数 | 返回值 | 说明 | |
| 194 | +|-----|------|--------|------| |
| 195 | +| `switchover(String mchId, String appId)` | 商户号, appId | boolean | 精确切换到指定配置 | |
| 196 | +| `switchover(String mchId)` | 商户号 | boolean | 仅使用商户号切换 | |
| 197 | +| `switchoverTo(String mchId, String appId)` | 商户号, appId | WxPayService | 精确切换,支持链式调用 | |
| 198 | +| `switchoverTo(String mchId)` | 商户号 | WxPayService | 仅商户号切换,支持链式调用 | |
| 199 | +| `addConfig(String mchId, String appId, WxPayConfig)` | 商户号, appId, 配置 | void | 动态添加配置 | |
| 200 | +| `removeConfig(String mchId, String appId)` | 商户号, appId | void | 移除指定配置 | |
0 commit comments