1
0
mirror of synced 2026-02-09 12:17:54 +08:00
Files
WxJava/weixin-java-pay/MULTI_APPID_USAGE.md
2026-01-19 03:54:42 +00:00

10 KiB
Raw Blame History

支持一个商户号对应多个 appId 的使用说明

背景

在实际业务中,经常会遇到一个微信支付商户号需要绑定多个小程序的场景。例如:

  • 一个商家有多个小程序(主店、分店、活动小程序等)
  • 所有小程序共用同一个支付商户号
  • 支付配置(商户号、密钥、证书等)完全相同,只有 appId 不同

解决方案

WxJava 支持在配置多个相同商户号、不同 appId 的情况下,可以仅通过商户号进行配置切换,无需每次都指定 appId。

使用方式

1. 配置多个 appId

WxPayService payService = new WxPayServiceImpl();

String mchId = "1234567890";  // 商户号

// 配置小程序1
WxPayConfig config1 = new WxPayConfig();
config1.setMchId(mchId);
config1.setAppId("wx1111111111111111");  // 小程序1的appId
config1.setMchKey("your_mch_key");
config1.setApiV3Key("your_api_v3_key");
// ... 其他配置

// 配置小程序2
WxPayConfig config2 = new WxPayConfig();
config2.setMchId(mchId);
config2.setAppId("wx2222222222222222");  // 小程序2的appId
config2.setMchKey("your_mch_key");
config2.setApiV3Key("your_api_v3_key");
// ... 其他配置

// 配置小程序3
WxPayConfig config3 = new WxPayConfig();
config3.setMchId(mchId);
config3.setAppId("wx3333333333333333");  // 小程序3的appId
config3.setMchKey("your_mch_key");
config3.setApiV3Key("your_api_v3_key");
// ... 其他配置

// 添加到配置映射
Map<String, WxPayConfig> configMap = new HashMap<>();
configMap.put(mchId + "_" + config1.getAppId(), config1);
configMap.put(mchId + "_" + config2.getAppId(), config2);
configMap.put(mchId + "_" + config3.getAppId(), config3);

payService.setMultiConfig(configMap);

2. 获取配置的方式

方式一:直接获取配置(推荐,新功能)

直接通过商户号和 appId 获取配置,不依赖 ThreadLocal,适用于多商户管理场景:

// 精确获取指定商户号和 appId 的配置
WxPayConfig config1 = payService.getConfig("1234567890", "wx1111111111111111");

// 仅使用商户号获取配置(会返回该商户号的任意一个配置)
WxPayConfig config = payService.getConfig("1234567890");

// 使用获取的配置进行支付操作
if (config != null) {
  String appId = config.getAppId();
  String mchKey = config.getMchKey();
  // ... 使用配置信息
}

优势

  • 不依赖 ThreadLocal可以在任何上下文中使用
  • 适合在异步场景、线程池等环境中使用
  • 线程安全,不会因为线程切换导致配置丢失
  • 可以同时获取多个不同的配置

方式二:切换配置后使用(原有方式)

通过切换配置,然后调用 getConfig() 获取当前配置:

// 精确切换到指定的配置
payService.switchover("1234567890", "wx1111111111111111");
WxPayConfig config = payService.getConfig();  // 获取当前切换的配置

// 仅使用商户号切换
payService.switchover("1234567890");
config = payService.getConfig();  // 获取切换后的配置

注意:此方式依赖 ThreadLocal需要注意线程上下文的问题。

3. 切换配置的方式

方式一:精确切换(原有方式,向后兼容)

// 切换到小程序1的配置
payService.switchover("1234567890", "wx1111111111111111");

// 切换到小程序2的配置
payService.switchover("1234567890", "wx2222222222222222");

方式二:仅使用商户号切换(新功能)

// 仅使用商户号切换,会自动匹配该商户号的某个配置
// 适用于不关心具体使用哪个 appId 的场景
boolean success = payService.switchover("1234567890");

注意:当使用仅商户号切换时,会按照以下逻辑查找配置:

  1. 先尝试精确匹配商户号(针对只配置商户号、没有 appId 的情况)
  2. 如果未找到,则尝试前缀匹配(查找以 商户号_ 开头的配置)
  3. 如果有多个匹配项,将返回其中任意一个匹配项,具体选择结果不保证稳定或可预测,如需确定性行为请使用精确匹配方式(同时指定商户号和 appId

方式三:链式调用

// 精确切换,支持链式调用
WxPayUnifiedOrderResult result = payService
    .switchoverTo("1234567890", "wx1111111111111111")
    .unifiedOrder(request);

// 仅商户号切换,支持链式调用
WxPayUnifiedOrderResult result = payService
    .switchoverTo("1234567890")
    .unifiedOrder(request);

4. 动态添加配置

// 运行时动态添加新的 appId 配置
WxPayConfig newConfig = new WxPayConfig();
newConfig.setMchId("1234567890");
newConfig.setAppId("wx4444444444444444");
// ... 其他配置

payService.addConfig("1234567890", "wx4444444444444444", newConfig);

// 切换到新添加的配置
payService.switchover("1234567890", "wx4444444444444444");

5. 移除配置

// 移除特定的 appId 配置
payService.removeConfig("1234567890", "wx1111111111111111");

实际应用场景

场景1根据用户来源切换 appId

// 在支付前,根据订单来源切换到对应小程序的配置
String orderSource = order.getSource();  // 例如: "miniapp1", "miniapp2"
String appId = getAppIdBySource(orderSource);

// 精确切换到特定小程序
payService.switchover(mchId, appId);

// 创建订单
WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
// ... 设置订单参数
WxPayUnifiedOrderResult result = payService.unifiedOrder(request);

场景2处理支付回调

@PostMapping("/pay/notify")
public String handlePayNotify(@RequestBody String xmlData) {
    try {
        // 解析回调通知
        WxPayOrderNotifyResult notifyResult = payService.parseOrderNotifyResult(xmlData);
        
        // 注意parseOrderNotifyResult 方法内部会自动调用 
        // switchover(notifyResult.getMchId(), notifyResult.getAppid())
        // 切换到正确的配置进行签名验证
        
        // 处理业务逻辑
        processOrder(notifyResult);
        
        return WxPayNotifyResponse.success("成功");
    } catch (WxPayException e) {
        log.error("支付回调处理失败", e);
        return WxPayNotifyResponse.fail("失败");
    }
}

场景3不关心具体 appId 的场景

// 某些场景下,只要是该商户号的配置即可,不关心具体是哪个 appId
// 例如:查询订单、退款等操作

// 仅使用商户号切换
payService.switchover(mchId);

// 查询订单
WxPayOrderQueryResult queryResult = payService.queryOrder(null, outTradeNo);

// 申请退款
WxPayRefundRequest refundRequest = new WxPayRefundRequest();
// ... 设置退款参数
WxPayRefundResult refundResult = payService.refund(refundRequest);

场景4多商户管理推荐使用直接获取配置

// 在多商户管理系统中,可以直接获取指定商户的配置
// 这种方式不依赖 ThreadLocal适合异步场景和线程池环境

public void processMerchantOrder(String mchId, String appId, Order order) {
    // 直接获取配置,无需切换
    WxPayConfig config = payService.getConfig(mchId, appId);
    
    if (config == null) {
        log.error("找不到商户配置mchId={}, appId={}", mchId, appId);
        return;
    }
    
    // 使用配置信息
    String merchantKey = config.getMchKey();
    String apiV3Key = config.getApiV3Key();
    
    // ... 处理订单逻辑
}

// 或者在不确定 appId 的情况下
public void processRefund(String mchId, String outTradeNo) {
    // 获取该商户号的任意一个配置
    WxPayConfig config = payService.getConfig(mchId);
    
    if (config == null) {
        log.error("找不到商户配置mchId={}", mchId);
        return;
    }
    
    // 先切换到该配置,然后进行退款
    payService.switchover(mchId, config.getAppId());
    // ... 执行退款操作
}

新增方法对比

方法 说明 是否依赖 ThreadLocal 适用场景
getConfig() 获取当前配置 单线程同步场景
getConfig(String mchId, String appId) 直接获取指定配置 多商户管理、异步场景、线程池
getConfig(String mchId) 根据商户号获取配置 不确定 appId 的场景
switchover(String mchId, String appId) 精确切换配置 需要切换上下文的场景
switchover(String mchId) 根据商户号切换 不关心 appId 的切换场景

注意事项

  1. 向后兼容:所有原有的使用方式继续有效,不需要修改现有代码。

  2. 配置隔离:每个 mchId + appId 组合都是独立的配置,修改一个配置不会影响其他配置。

  3. 线程安全

    • 配置切换使用 WxPayConfigHolder(基于 ThreadLocal),是线程安全的
    • 直接获取配置方法(getConfig(mchId, appId))不依赖 ThreadLocal可以在任何上下文中安全使用
  4. 自动切换在处理支付回调时SDK 会自动根据回调中的 mchIdappId 切换到正确的配置。

  5. 推荐实践

    • 如果知道具体的 appId建议使用精确切换或获取方式避免歧义
    • 在多商户管理、异步场景、线程池等环境中,建议使用 getConfig(mchId, appId) 直接获取配置
    • 如果使用仅商户号切换或获取,确保该商户号下至少有一个可用的配置

相关 API

方法 参数 返回值 说明
getConfig() WxPayConfig 获取当前配置(依赖 ThreadLocal
getConfig(String mchId, String appId) 商户号, appId WxPayConfig 直接获取指定配置(不依赖 ThreadLocal
getConfig(String mchId) 商户号 WxPayConfig 根据商户号获取配置(不依赖 ThreadLocal
switchover(String mchId, String appId) 商户号, appId boolean 精确切换到指定配置
switchover(String mchId) 商户号 boolean 仅使用商户号切换
switchoverTo(String mchId, String appId) 商户号, appId WxPayService 精确切换,支持链式调用
switchoverTo(String mchId) 商户号 WxPayService 仅商户号切换,支持链式调用
addConfig(String mchId, String appId, WxPayConfig) 商户号, appId, 配置 void 动态添加配置
removeConfig(String mchId, String appId) 商户号, appId void 移除指定配置