手机 H5 如何携带上游商家信息并通过 scheme 拉起支付宝 App 支付?
结论
手机 H5 支付需要由服务端调用 alipay.trade.wap.pay。支付宝生成收银台表单后,服务端将表单返回给前端并自动提交。支付宝会根据终端环境尝试拉起支付宝 App;如果无法拉起,通常会进入 H5 收银台。
常见的支付宝拉起 scheme 如下:
alipays://platformapi/startapp?appId=20000067&url={URL_ENCODED_CASHIER_URL}
其中,url 必须是经过 URL 编码的支付宝收银台地址。不要直接放入商户订单号,也不要在 scheme 中拼接自定义商家信息。
“上游商家信息”应通过支付接口支持的业务参数传递,并参与签名,不能放在 scheme 中。使用哪个字段取决于具体业务模式:
- 服务商标识通常放在
extend_params.sys_service_provider_id中,但对应的服务商模式必须已经开通。 - 如果需要在异步通知中原样取回业务标识,可以使用
passback_params。 - 收银台显示的商户主体取决于签约账号、
app_id、卖家关系以及支付宝侧配置,无法通过 H5 参数随意修改。
推荐支付流程
完整流程如下:
- H5 将商品和订单信息提交给商户服务端。
- 服务端创建本地订单,并生成唯一的
out_trade_no。 - 服务端调用
alipay.trade.wap.pay。 - 将上游商家标识放入双方约定的接口字段。
- 使用商户私钥为整个请求签名。
- 服务端将支付宝 SDK 生成的表单返回给 H5。
- H5 自动提交表单,进入支付宝收银台。
- 支付宝通过
notify_url异步通知支付结果。 - 服务端完成验签并核对订单信息,再更新订单状态。
支付结果必须以服务端收到的异步通知或主动查询结果为准,不能只看前端跳转页面。
服务端请求示例
以下是 Java SDK 的典型写法。不同 SDK 版本的方法签名可能有差异,请以项目实际使用的支付宝 SDK 为准。
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
APP_ID,
MERCHANT_PRIVATE_KEY,
"json",
"UTF-8",
ALIPAY_PUBLIC_KEY,
"RSA2"
);
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://pay.example.com/alipay/return");
request.setNotifyUrl("https://pay.example.com/alipay/notify");
Map<String, Object> extendParams = new HashMap<>();
extendParams.put("sys_service_provider_id", "2088XXXXXXXXXXXX");
Map<String, Object> bizContent = new HashMap<>();
bizContent.put("out_trade_no", "ORDER_202609090001");
bizContent.put("total_amount", "99.00");
bizContent.put("subject", "商品订单");
bizContent.put("product_code", "QUICK_WAP_WAY");
bizContent.put("quit_url", "https://m.example.com/order/ORDER_202609090001");
bizContent.put("passback_params", URLEncoder.encode(
"upstream_merchant_id=M12345",
StandardCharsets.UTF_8
));
bizContent.put("extend_params", extendParams);
request.setBizContent(new ObjectMapper().writeValueAsString(bizContent));
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"创建支付宝支付失败:" + response.getSubCode() + " " + response.getSubMsg()
);
}
// response.getBody() 通常是需要返回给浏览器并自动提交的 HTML 表单
String paymentForm = response.getBody();
服务端应将生成的表单直接作为 HTML 返回:
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(paymentForm);
商户私钥不能保存在浏览器端,也不要让前端自行生成支付宝签名。
上游商家信息如何传递
如果需要传递的只是商户自己的订单路由标识,例如上游商家编号,可以使用 passback_params:
{
"out_trade_no": "ORDER_202609090001",
"total_amount": "99.00",
"subject": "商品订单",
"product_code": "QUICK_WAP_WAY",
"passback_params": "upstream_merchant_id%3DM12345"
}
支付宝通常会在异步通知中返回该字段。服务端收到后需要进行 URL 解码:
String passbackParams = URLDecoder.decode(
request.getParameter("passback_params"),
StandardCharsets.UTF_8
);
如果“上游商家”是指支付宝服务商体系中的服务商,则可能需要使用:
{
"extend_params": {
"sys_service_provider_id": "2088XXXXXXXXXXXX"
}
}
这个字段并不是通用的自定义商户字段。只有签约产品、应用归属和服务商关系都符合要求时才应传入,也不能用其他商户的 PID 冒充服务商。
使用 scheme 拉起支付宝
如果已经取得合法的支付宝收银台 URL,并确认当前产品允许通过这种方式调用,可以按下面的方式构造 scheme:
function openAlipay(cashierUrl) {
const scheme =
"alipays://platformapi/startapp?appId=20000067&url=" +
encodeURIComponent(cashierUrl);
window.location.href = scheme;
}
例如:
alipays://platformapi/startapp?appId=20000067&url=https%3A%2F%2Fmclient.alipay.com%2F...
使用时要注意以下几点:
- 必须对完整的收银台 URL 执行
encodeURIComponent。 - 不要将未经签名的金额、卖家编号或订单号直接拼入收银台 URL。
- 不要自行解析 SDK 返回的 HTML 表单来提取 URL,因为 SDK 的输出结构可能发生变化。
- scheme 通常要在用户点击事件中触发,否则可能被浏览器拦截。
- 微信、企业应用内置浏览器以及部分 iOS 或 Android WebView 可能会限制外部 App 拉起。
- 如果无法拉起 App,需要保留 H5 收银台或浏览器打开提示作为降级方案。
实际项目中,建议优先提交 alipay.trade.wap.pay 返回的表单,由支付宝收银台处理 App 拉起和 H5 降级。只有支付宝为当前签约产品明确提供了收银台 URL,并允许通过 scheme 唤起时,才自行包装 alipays://。
异步通知处理
收到 notify_url 请求后,至少要完成验签:
boolean signVerified = AlipaySignature.rsaCheckV1(
parameterMap,
ALIPAY_PUBLIC_KEY,
"UTF-8",
"RSA2"
);
if (!signVerified) {
throw new SecurityException("支付宝通知验签失败");
}
验签通过后,还需要核对以下信息:
app_id是否属于当前应用。out_trade_no是否存在。total_amount是否与本地订单一致。seller_id是否为预期的收款主体。- 订单是否已经处理,避免重复通知。
trade_status是否属于业务认可的成功状态。
处理完成后,按照支付宝协议返回:
success
注意事项
return_url 只是用户支付完成后的页面跳转地址,可能被关闭、拦截或重复访问,因此不能作为记账依据。
passback_params 适合传递简短的业务关联信息,但不要放入身份证号、手机号或密钥等敏感数据。更稳妥的做法是只传内部关联 ID,将完整信息保存在商户服务端。
如果业务涉及二级商户、间连商户、分账或受托收款,只传 sys_service_provider_id 通常无法满足要求。此时应根据已经签约的支付宝产品使用对应的二级商户或结算参数,不能通过普通 WAP 支付参数临时更改收款主体。
备注:内容仅供参考。
