支付宝小程序如何使用不同商户号进行支付?
结论
同一个支付宝小程序可以按业务场景使用不同商户收款,但商户号不能由前端直接填写或切换。
收款主体应由服务端确定。常见方案是使用支付宝服务商(ISV)代调用模式:各商户先授权应用,服务端保存相应的授权凭证。创建订单时,服务端根据订单所属商户,选择对应的 app_auth_token 调用 alipay.trade.create,然后将支付宝返回的交易号交给小程序,由 my.tradePay 唤起收银台。
如果小程序是普通商户的自研应用,没有开通服务商代调用能力,也没有取得其他商户的授权,通常只能由该应用签约主体对应的商户号收款,不能随意切换到其他 PID。
为什么不能在小程序前端切换商户号
小程序端的 my.tradePay 只负责唤起支付宝收银台。它接收服务端创建订单后返回的 trade_no,不决定最终由哪个商户收款。
收款主体取决于服务端创建交易时使用的应用身份、签名、产品签约关系和商户授权信息。商户私钥、app_auth_token 等敏感凭证也不能放入小程序代码,否则可能造成密钥泄露和越权支付。
完整调用链路如下:
小程序选择业务门店或商户
↓
服务端根据业务关系找到目标商户
↓
服务端使用该商户对应的 app_auth_token 创建交易
↓
支付宝返回 trade_no
↓
小程序调用 my.tradePay
实现步骤
1. 确认接入模式
如果不同商户是相互独立的支付宝商家,通常需要采用服务商代商户调用模式:
- 服务商创建并配置具备相应支付能力的应用。
- 各商户完成支付宝应用授权。
- 服务端获取并保存每个商户对应的
app_auth_token。 - 确认相关应用、商户和支付产品已完成签约及必要的关联配置。
- 创建支付订单时,根据订单所属商户选择对应的授权令牌。
具体可以使用哪些支付产品,以及小程序是否需要与商户建立额外关联,应以当前支付宝开放平台控制台显示的能力和签约要求为准。
即使几个“商户号”属于同一个支付宝主体,也不能只根据 PID 判断是否可以切换。能否指定其他收款账户,仍取决于产品签约关系和支付宝侧配置。
2. 建立业务商户映射
服务端需要维护业务商户与支付宝授权信息的映射,例如:
merchant_code alipay_user_id app_auth_token
store_a 2088...001 token_a
store_b 2088...002 token_b
merchant_code 应由服务端根据门店、商品归属或订单关系确定。不要直接采用前端上传的商户号或 app_auth_token。
3. 服务端创建交易
下面以支付宝 Java SDK 的常见调用方式为例。不同版本的 SDK 可能使用不同的构造方法和模型类,实际代码应以项目使用的版本为准。
AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
AlipayTradeCreateModel model = new AlipayTradeCreateModel();
model.setOutTradeNo(orderNo);
model.setTotalAmount(totalAmount);
model.setSubject(subject);
model.setBuyerId(buyerId);
request.setBizModel(model);
request.setNotifyUrl("https://example.com/alipay/notify");
// 根据服务端确认的业务商户读取授权令牌
String appAuthToken = merchantConfig.getAppAuthToken();
AlipayTradeCreateResponse response =
alipayClient.execute(request, null, appAuthToken);
if (!response.isSuccess()) {
throw new IllegalStateException(
"创建支付宝交易失败:" + response.getSubCode() + " " + response.getSubMsg()
);
}
String tradeNo = response.getTradeNo();
其中最需要注意的是:
alipayClient.execute(request, null, appAuthToken);
不同业务商户需要使用各自的 app_auth_token。在代调用模式下,支付宝会根据应用身份、授权关系和签约状态判断实际收款主体。
不要试图只修改 seller_id 来切换商户。即使接口模型中有这个字段,也不表示应用可以向未授权、未签约或没有关联关系的账户收款。
4. 将交易号返回给小程序
服务端只需向小程序返回支付所需的交易号,例如:
{
"tradeNo": "2026xxxxxxxxxxxx"
}
商户私钥、应用私钥、app_auth_token 以及其他服务端凭证都不能返回给前端。
5. 小程序唤起支付
my.tradePay({
tradeNO: response.tradeNo,
success: (result) => {
console.log('支付调用完成', result);
// 不要仅凭前端结果修改订单状态
queryOrderStatus();
},
fail: (error) => {
console.error('支付调用失败', error);
}
});
前端支付回调只能反映收银台的调用结果,不能证明款项已经入账。最终支付状态需要由服务端结合支付宝异步通知和主动查单结果确认。
服务端选择商户的示例
假设一个小程序对应多个门店,且每个门店由不同的支付宝商户收款,服务端可以根据订单归属选择授权信息:
public String createPayment(String orderNo, String currentUserId) {
Order order = orderService.getOrder(orderNo);
if (order == null || !order.getUserId().equals(currentUserId)) {
throw new IllegalArgumentException("订单不存在或无权支付");
}
if (!order.isPendingPayment()) {
throw new IllegalStateException("订单状态不允许支付");
}
MerchantConfig config =
merchantConfigService.findByMerchantCode(order.getMerchantCode());
if (config == null || !config.isEnabled()) {
throw new IllegalStateException("收款商户未配置或不可用");
}
return alipayPaymentService.createTrade(
order,
config.getAppAuthToken()
);
}
商户归属应从服务端订单中读取,不能允许前端通过下面这种方式任意指定:
{
"orderNo": "ORDER_10001",
"merchantPid": "2088xxxxxxxxxxxx",
"appAuthToken": "任意令牌"
}
否则,攻击者可能篡改参数,将订单转到错误的收款账户。
异步通知处理
在多商户场景中,服务端收到支付通知后,除了验签,还要确认订单与收款主体是否匹配。至少应检查以下内容:
app_id是否属于本系统允许的应用。out_trade_no对应的订单是否存在。- 订单金额是否与
total_amount一致。 - 订单当前状态是否允许更新。
- 通知中的收款主体信息是否与订单绑定的商户一致。
- 重复收到同一通知时能否幂等处理。
示意代码如下:
if (!verifySignature(requestParams)) {
return "failure";
}
Order order = orderService.getByOrderNo(
requestParams.get("out_trade_no")
);
if (order == null) {
return "failure";
}
if (!order.getAmount().toPlainString()
.equals(requestParams.get("total_amount"))) {
return "failure";
}
if (!merchantMatcher.matches(order, requestParams)) {
return "failure";
}
orderService.markPaidIdempotently(
order.getOrderNo(),
requestParams.get("trade_no")
);
return "success";
用于核对收款主体的通知字段,应以实际接口返回内容和当前支付产品文档为准。不同接入模式返回的字段不一定完全相同。
注意事项
不要在前端保存授权信息
以下信息必须保存在安全的服务端环境中:
- 应用私钥
- 商户授权令牌
app_auth_token - 应用授权刷新令牌
- 商户与业务门店的映射关系
正确处理授权失效
商户取消授权、令牌失效或签约状态发生变化时,创建交易可能失败。服务端应记录支付宝返回的 code、sub_code 和 sub_msg,并让订单保持在可恢复状态。不要在失败后自动改用另一个商户收款。
每个商户分别对账和退款
订单中应永久记录实际收款商户、支付宝交易号和授权主体。后续进行退款、交易查询、关闭订单或对账时,也要使用与原支付订单一致的商户授权关系,不能只读取门店当前的配置。
避免重复创建订单
out_trade_no 应在相应的商户和应用范围内保持唯一,创建支付接口也需要提供幂等控制。用户重复点击支付时,应优先查询或复用已有交易,避免反复创建新订单。
不要完全根据前端结果判断支付成功
用户可能关闭收银台,网络也可能中断,前端回调还有可能没有及时触发。订单状态应以支付宝服务端通知和交易查询结果为准。
同一小程序使用多个商户号,实际要解决的不是前端如何“切换 PID”,而是服务端如何根据订单归属,在合法的授权和签约关系下,使用对应商户的身份创建支付交易。
备注:内容仅供参考。
