单个APP如何支持多个支付宝商户号收款?
结论
同一个客户端 APP 可以为多所学校提供收款功能,但不应采用“一个总商户统一收款,再由后台随意转账”的方式。
通常有两种可行方案:
- 支付宝服务商或第三方应用代商户调用模式:每所学校使用自己的支付宝商户账户,并授权给平台。后台创建支付订单时,根据学校选择对应的商户授权令牌,资金直接进入该学校账户。
- 支付宝认可的分账或二级商户模式:如果业务需要由平台统一收款,再按订单分配资金,应申请相应的分账产品,或接入具备这类能力的支付服务商。普通转账接口不能代替分账。
具体采用哪种模式,要看平台主体、学校主体资质、签约产品以及支付宝当前的审核政策。正式开发前,应向支付宝开放平台或合作服务商确认准入条件。
为什么不建议“总账户收款后自动打款”
支付宝可能提供转账、批量付款等能力,但能调用转账接口,不等于可以用它处理平台代收代付。
由总商户先收款,会带来以下问题:
- 支付订单显示的实际收款方是平台,而不是学校。
- 平台可能被认定为替其他主体归集和清算资金。
- 退款、投诉、账单及交易凭证都会落在总商户名下。
- 学校收到的是后续转账,无法与用户的原始支付自然对应。
- 业务可能涉及支付合规、税务、发票、反洗钱和资金沉淀问题。
- 如果转账失败、账户受限或余额不足,平台还要承担资金垫付和对账责任。
因此,不能在收到支付成功通知后直接调用普通转账接口。只有支付宝为该业务明确开通了分账、资金结算或其他匹配的产品,才能按照相应规则实施。
推荐方案:各学校授权,资金直接入校
如果平台是软件服务商,可以考虑创建一个支付宝第三方应用,再由每所学校完成商户授权。
基本流程如下:
- 平台在支付宝开放平台创建第三方应用,并申请所需的支付能力。
- 每所学校使用自己的商户账户签约相应的支付产品。
- 学校通过支付宝授权页面向平台应用授权。
- 平台取得该学校对应的
app_auth_token,并将其与内部school_id绑定。 - 用户充值时,后台根据用户所属学校查询对应的授权信息。
- 后台以该学校的授权身份调用支付接口。
- 用户仍在同一个 APP 内完成支付,资金直接进入对应学校的商户账户。
- 支付宝将异步通知发送到统一回调地址,平台再根据订单记录确认学校和充值对象。
关键在于:客户端 APP 可以共用,但服务端需要根据租户切换收款商户。客户端不能保存商户私钥,也不能自行决定收款账户。
某个支付产品是否支持第三方应用代调用,要以该接口当前的文档、应用类型和商户签约情况为准。
服务端订单设计
订单中至少应保存以下字段:
out_trade_no
school_id
user_id
merchant_id
app_auth_token_id
amount
status
alipay_trade_no
created_at
paid_at
app_auth_token 本身应加密保存,不要直接写入普通业务日志。
创建订单时,必须根据服务端可信数据确定 school_id,不能由客户端随意提交学校编号并据此选择收款商户。例如,用户登录后的所属学校,应通过账号关系、组织关系或服务端签名的上下文确定。
Java 调用示例
下面的示例说明服务端如何按学校授权创建 APP 支付订单。类名采用支付宝 Java SDK 的常见写法,具体 SDK 版本、构造方式和可用参数应以接入时的官方文档为准。
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
serviceProviderAppId,
serviceProviderPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
SchoolMerchant merchant = merchantService.getAuthorizedMerchant(schoolId);
if (merchant == null || !merchant.isEnabled()) {
throw new IllegalStateException("该学校尚未完成支付宝商户授权");
}
RechargeOrder order = orderService.createPendingOrder(
schoolId,
userId,
amount
);
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
request.setNotifyUrl("https://api.example.com/pay/alipay/notify");
request.putOtherTextParam(
"app_auth_token",
merchant.getAppAuthToken()
);
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(order.getOutTradeNo());
model.setTotalAmount(order.getAmount().toPlainString());
model.setSubject("校园卡充值");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"创建支付宝订单失败:" + response.getSubCode()
);
}
// 将 response.getBody() 返回给 APP,由支付宝客户端组件发起支付
return response.getBody();
如果实际签约模式要求每所学校使用独立的应用配置,而不是通过第三方应用授权,服务端也可以按学校选择不同的 AlipayClient。不过,这种模式在应用管理、密钥轮换、签约和审核方面的成本更高。同一个移动端安装包能否共用,也需要向支付宝确认。
支付通知处理
支付结果必须以服务端查询结果或验签后的异步通知为准,不能只相信 APP 返回的“支付成功”。
public String handleNotify(Map<String, String> params) throws Exception {
boolean valid = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!valid) {
return "failure";
}
String outTradeNo = params.get("out_trade_no");
RechargeOrder order = orderService.getByOutTradeNo(outTradeNo);
if (order == null) {
return "failure";
}
BigDecimal notifiedAmount =
new BigDecimal(params.get("total_amount"));
if (notifiedAmount.compareTo(order.getAmount()) != 0) {
return "failure";
}
String tradeStatus = params.get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
orderService.markPaidIdempotently(
outTradeNo,
params.get("trade_no")
);
}
return "success";
}
处理通知时,还要核对其中的 app_id、收款方标识等字段,确认它们与订单保存的商户配置一致。不同接入模式下,这些字段的含义可能有所不同,应以对应产品的文档为准。
充值入账必须具备幂等控制,防止支付宝重复发送通知,导致校园卡余额被重复增加。
必须注意的事项
- 每所学校都需要有符合要求的支付宝商户主体,并取得相应支付产品的权限。
- 商户授权过期、被撤销或失效后,应立即停止该学校的新交易。
- 金额、学校、用户和业务订单必须在支付前确定,不能等支付成功后再指定收款学校。
- 退款应尽量通过原交易和原收款商户发起,不能由平台账户随意代退。
- 对账数据应按学校隔离,并同时保存平台订单号和支付宝交易号。
- 回调验签使用支付宝公钥,不是应用公钥,也不是学校商户私钥。
- 商户私钥、应用私钥和
app_auth_token只能保存在服务端,并需要加密存储和限制访问。 - 如果学校无法独立签约,或者平台需要处理抽佣、补贴及多方资金分配,应向支付宝咨询能否接入服务商二级商户、分账或相应的行业解决方案。
整体上,应优先采用“各学校作为实际收款商户,平台统一提供 APP 和交易路由”的模式。只有支付宝明确批准并开通了相应的资金产品,才可以考虑由总商户收款后再分账。
备注:内容仅供参考。
