支付宝 App 内打开 H5 页面应接入哪种支付方式?
结论
如果 H5 页面确定运行在支付宝 App 内,建议优先接入支付宝的 JSAPI 支付,不要把“手机网站支付”作为首选。
典型流程如下:
- H5 请求商户服务端创建订单。
- 服务端调用
alipay.trade.create。 - 服务端将支付宝交易号
trade_no返回给 H5。 - H5 通过
AlipayJSBridge.call('tradePay', ...)唤起支付宝收银台。 - 服务端根据异步通知确认最终支付结果。
用户已经在支付宝客户端内时,JSAPI 支付会直接调用客户端内的收银台,一般不会再次要求用户“打开支付宝 App”。支付过程中可能出现收银台切换、支付确认或验证页面,但这不代表系统重新调起了另一个客户端。
为什么不优先使用手机网站支付
手机网站支付通常通过 alipay.trade.wap.pay 生成跳转页面,主要用于手机浏览器中的支付场景。用户从普通浏览器发起支付时,页面可能尝试唤起支付宝 App,也可能进入支付宝提供的中间页面。
如果 H5 已经运行在支付宝 App 的 WebView 中,再使用手机网站支付通常会增加一层跳转,返回路径和用户体验也更难控制。手机网站支付并非完全不可用,只是 JSAPI 支付更适合支付宝内嵌 H5。
可根据页面的运行环境选择支付方式:
| 页面运行环境 | 建议支付方式 |
|---|---|
| 支付宝 App 内的 H5 | JSAPI 支付 |
| 微信、Safari、Chrome 等普通移动浏览器 | 手机网站支付 |
| 商户自己的原生 App | App 支付 |
| PC 网站 | 电脑网站支付 |
| 线下扫码、商家动态收款码 | 当面付等扫码产品 |
如果同一个链接既可能在支付宝内打开,也可能在普通浏览器中打开,可以按运行环境分流:支付宝内使用 JSAPI 支付,外部浏览器使用手机网站支付。
接入流程
1. 开通并配置对应产品
先在支付宝开放平台创建应用,确认应用已具备 JSAPI 支付所需的产品权限。通常还需要完成商户签约、应用上线、密钥或证书配置,并设置业务域名和授权回调地址等信息。
开放平台的产品名称和控制台入口可能发生调整,具体应以当前应用控制台展示的可签约产品和接入文档为准。
2. 在服务端创建订单
支付金额、订单号和商品信息必须由服务端生成或校验,不能直接信任前端提交的数据。
下面是简化后的伪代码。SDK 类名和初始化方式需要根据实际使用的支付宝 SDK 调整:
AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderNo);
bizContent.put("total_amount", "9.90");
bizContent.put("subject", "测试商品");
bizContent.put("buyer_id", alipayUserId);
request.setBizContent(bizContent.toJSONString());
AlipayTradeCreateResponse response = alipayClient.execute(request);
if (!response.isSuccess()) {
throw new RuntimeException(
response.getCode() + ": " + response.getSubMsg()
);
}
String tradeNo = response.getTradeNo();
其中,alipayUserId 一般通过支付宝网页授权获得。是否必须传入该参数,以及具体的获取方式,应以当前 alipay.trade.create 接口要求和已签约产品的规则为准。
创建订单必须在服务端完成,因为调用支付宝接口时需要使用应用私钥签名。不要把应用私钥、证书私钥或服务端 SDK 配置放入 H5。
3. 在 H5 中调用支付宝收银台
H5 获得服务端返回的 trade_no 后,通过 AlipayJSBridge 发起支付:
<script>
function ready(callback) {
if (window.AlipayJSBridge) {
callback();
return;
}
document.addEventListener(
'AlipayJSBridgeReady',
callback,
false
);
}
async function pay() {
const response = await fetch('/api/payment/alipay/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
orderId: 'ORDER_202609090001'
})
});
const result = await response.json();
if (!result.tradeNo) {
throw new Error(result.message || '创建支付订单失败');
}
ready(function () {
AlipayJSBridge.call(
'tradePay',
{
tradeNO: result.tradeNo
},
function (payResult) {
console.log('tradePay result:', payResult);
// 此处只能用于更新页面提示。
// 最终支付状态必须向服务端查询。
window.location.href =
'/payment/result?orderId=' +
encodeURIComponent('ORDER_202609090001');
}
);
});
}
</script>
<button type="button" onclick="pay()">立即支付</button>
这里要注意字段名称:JSAPI 的参数通常是 tradeNO,支付宝创建订单接口返回的字段通常是 trade_no。两者名称相似,但不能直接把整个响应对象原样传入。
4. 通过服务端确认支付结果
前端 tradePay 的回调只能表示客户端支付流程已经结束,不能单独作为发货或开通服务的依据。用户可能关闭页面,网络也可能中断,而且前端返回值存在被伪造的可能。
服务端需要:
- 接收支付宝异步通知并验证签名。
- 校验
app_id、out_trade_no、金额、币种和商户身份等字段。 - 对订单状态更新进行幂等处理。
- 必要时调用
alipay.trade.query主动查询交易状态。 - 服务端确认支付成功后,再执行发货、充值或开通权益。
同一页面兼容支付宝内外环境
可以先对运行环境做简单判断:
function isAlipayClient() {
return /AlipayClient/i.test(navigator.userAgent);
}
if (isAlipayClient()) {
// 调用服务端创建 JSAPI 支付订单
} else {
// 请求服务端生成手机网站支付跳转地址或表单
}
User-Agent 只能用来选择页面交互流程,不能用于安全校验。服务端仍然需要校验订单、支付渠道和回调签名。
如果业务只允许用户在支付宝内付款,还需要处理页面在非支付宝环境中打开的情况:
if (!isAlipayClient()) {
document.body.innerHTML =
'<p>请使用支付宝扫描二维码后完成支付。</p>';
}
常见注意事项
- 不要在 H5 中保存支付宝应用私钥,也不要让前端自行计算支付金额。
- 商户订单号
out_trade_no应当唯一且可追踪,重复请求需要进行幂等处理。 - 不要只根据前端回调中的“成功”信息发货。
- 调用 JSAPI 前应等待
AlipayJSBridgeReady,否则可能出现对象未定义的问题。 - 支付宝网页授权的
auth_code有有效期,而且通常只能使用一次,应由服务端及时换取用户身份信息。 - 支付授权域名、回调地址、应用模式和签约主体必须与实际环境匹配。
- 沙箱环境与正式环境的应用、网关、密钥和账号不能混用。
- 用户取消支付并不代表订单已经关闭。是否关闭订单,应由服务端按照业务规则决定。
- 支付完成后的页面跳转只负责展示,最终订单状态仍需从服务端获取。
如果二维码打开的链接确定运行在支付宝 App 内,推荐使用 JSAPI 支付:服务端通过 alipay.trade.create 创建交易,H5 再调用 AlipayJSBridge.tradePay 发起支付。只有页面还要兼容支付宝之外的手机浏览器时,才需要同时接入手机网站支付,并按照运行环境分流。
备注:内容仅供参考。
