H5 页面如何调用支付宝 JSAPI 支付?
结论
普通 H5 页面无法在任意浏览器中直接调用支付宝小程序的 my.tradePay。聚合支付码牌通常走的是下面这条受控链路,而不是由外部浏览器中的 H5 调用小程序 JSAPI:
- 用户使用支付宝客户端扫码;
- 收银页面在支付宝客户端的内置 WebView 中打开;
- 服务端通过支付宝开放平台接口创建交易;
- 页面调用支付宝客户端注入的
AlipayJSBridge,传入支付宝交易号并唤起收银台。
页面能否发起支付,不只是看有没有调用代码,还要看扫码入口、客户端环境、应用权限、签约产品、域名配置和订单创建方式。把同一个 URL 复制到微信、系统浏览器或普通 WebView 中打开,通常无法使用这条链路。
如果需要兼容外部浏览器,应接入支付宝手机网站支付,例如 alipay.trade.wap.pay,不要强行调用 JSAPI。
为什么会提示“唤起方式不正确”
支付宝会校验支付请求的运行环境和来源。常见原因有:
- 页面没有在支付宝客户端的内置 WebView 中打开;
- 调用了仅供支付宝小程序使用的
my.tradePay; - 页面虽然在支付宝中打开,但没有通过允许的扫码入口或业务入口进入;
- 开放平台应用没有相应支付产品的权限;
- 页面域名、应用、商户账号或支付产品没有正确关联;
- 前端传入的不是服务端创建交易后返回的有效支付宝交易号;
- 订单所属的应用或商户与当前调用环境不一致;
- 页面通过调试工具、复制链接或自定义 scheme 模拟入口,触发了风控或来源校验。
支付宝客户端内存在 AlipayJSBridge,不等于任何网页都能无条件调用支付能力。
推荐的实现流程
1. 准备支付宝开放平台应用
使用已完成必要认证的支付宝开放平台应用,并根据实际业务签约对应的支付产品。
开放平台可能会调整产品名称、准入范围和配置入口,具体应以当前商户后台展示的能力为准。不能只看前端接口名称,就认定应用已经获得支付权限。
还需要检查:
app_id是否正确;- 商户账号是否已与应用绑定;
- 支付产品是否已经生效;
- H5 收银页是否使用 HTTPS;
- 页面域名是否已按产品要求配置;
- 服务端是否正确保存了支付宝应用私钥和支付宝公钥。
2. 让用户从支付宝客户端进入页面
码牌中的二维码一般指向一个 HTTPS 收银地址,例如:
https://pay.example.com/cashier?store_id=1001&terminal_id=2001
用户使用支付宝扫码后,该地址会在支付宝客户端内打开。二维码中不要直接写入金额、商品名称或可复用的支付参数。金额和门店信息应由服务端根据可信数据确定。
只通过 User-Agent 判断页面是否处于支付宝浏览器并不可靠,还要检查 AlipayJSBridge 是否真正可用。
3. 获取付款用户身份
如果创建交易的接口需要付款人的支付宝身份,通常要先完成支付宝网页授权:
- 服务端生成授权地址;
- 用户在支付宝内完成静默授权或主动授权;
- 支付宝回跳业务页面,并携带
auth_code; - 服务端使用
auth_code换取用户标识; - 服务端为该用户创建交易。
授权范围应根据业务需要选择。仅需识别支付宝用户时,通常优先使用最小授权范围。授权地址、回调地址和相关参数都应由服务端生成并校验,不能直接信任前端传值。
4. 服务端创建订单
订单必须由服务端创建。前端只提交业务订单标识,服务端负责:
- 查询真实金额;
- 校验门店和终端;
- 生成唯一的
out_trade_no; - 调用支付宝交易创建接口;
- 保存业务订单号与支付宝交易号的对应关系;
- 将唤起支付所需的交易号返回前端。
下面是示意代码,具体 SDK 方法名应以实际使用的支付宝服务端 SDK 为准:
app.post('/api/pay/create', async (req, res) => {
const { orderId } = req.body;
const order = await loadAndValidateOrder(orderId);
// 金额必须从服务端订单中读取,不能采用前端提交的金额。
const result = await alipaySdk.exec('alipay.trade.create', {
bizContent: {
out_trade_no: order.outTradeNo,
total_amount: order.totalAmount,
subject: order.subject,
buyer_id: order.alipayUserId
}
});
res.json({
tradeNO: result.tradeNo
});
});
这里的接口和字段只用于说明典型流程。是否必须传入 buyer_id,以及应该使用哪个交易创建接口,取决于已签约的支付产品和当前开放平台文档。
5. 在支付宝 WebView 中调用 AlipayJSBridge
页面应等待桥接对象初始化完成,再调用 tradePay:
<script>
function ready(callback) {
if (window.AlipayJSBridge) {
callback();
return;
}
document.addEventListener(
'AlipayJSBridgeReady',
callback,
false
);
}
async function startPay(orderId) {
if (!window.AlipayJSBridge) {
throw new Error('当前页面不在支持支付的支付宝客户端环境中');
}
const response = await fetch('/api/pay/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ orderId })
});
if (!response.ok) {
throw new Error('创建支付订单失败');
}
const { tradeNO } = await response.json();
return new Promise((resolve, reject) => {
AlipayJSBridge.call(
'tradePay',
{ tradeNO },
result => {
if (result.resultCode === '9000') {
resolve(result);
} else {
reject(result);
}
}
);
});
}
ready(() => {
document
.querySelector('#pay-button')
.addEventListener('click', async () => {
try {
await startPay('BUSINESS_ORDER_ID');
location.href = '/pay/result?order_id=BUSINESS_ORDER_ID';
} catch (error) {
console.error('支付未完成', error);
}
});
});
</script>
<button id="pay-button" type="button">支付宝支付</button>
AlipayJSBridge.call('tradePay', ...) 是支付宝客户端内的网页桥接调用,与小程序中的 my.tradePay 分属不同运行环境,不能混用。
不同支付产品允许传入的参数可能不同。有些文档或历史方案中还会出现其他参数形式。接入时应严格按照当前产品文档传参,不要同时提交多套订单参数进行试探。
外部浏览器应使用手机网站支付
如果页面可能通过短信、普通二维码、搜索结果或其他 App 打开,应准备手机网站支付方案。服务端调用 alipay.trade.wap.pay 生成支付表单或跳转内容,再引导用户前往支付宝完成付款。
典型流程如下:
外部浏览器 H5
→ 服务端调用 alipay.trade.wap.pay
→ 跳转支付宝收银台
→ 支付宝同步返回页面
→ 支付宝异步通知服务端
这种方式不依赖页面中的 AlipayJSBridge,更适合通用 H5 场景。
可以根据运行环境分流:
function isAlipayClient() {
return /AlipayClient/i.test(navigator.userAgent);
}
if (isAlipayClient()) {
// 尝试支付宝客户端内的授权和 JSBridge 支付流程。
} else {
// 请求服务端生成手机网站支付跳转地址。
}
User-Agent 只能用于界面分流,不能作为安全校验,也不能替代支付产品的准入和来源校验。
支付结果必须由服务端确认
前端回调中的成功状态,只能说明客户端支付流程返回了成功结果,不能直接作为发货、充值或核销的依据。
服务端至少应完成以下处理:
- 接收支付宝异步通知;
- 验证通知签名;
- 校验
app_id、商户身份、订单号、金额和币种; - 判断交易状态;
- 使用幂等逻辑更新业务订单;
- 必要时主动查询支付宝订单状态。
前端支付完成后,可以跳转到结果页,轮询业务订单状态:
async function waitForPayment(orderId) {
for (let i = 0; i < 10; i += 1) {
const response = await fetch(
`/api/pay/status?order_id=${encodeURIComponent(orderId)}`
);
const result = await response.json();
if (result.status === 'PAID') {
return true;
}
await new Promise(resolve => setTimeout(resolve, 1500));
}
return false;
}
排查“唤起方式不正确”
可以按以下顺序检查:
- 确认调用的是网页环境中的
AlipayJSBridge.call('tradePay', ...),不是小程序的my.tradePay。 - 确认页面由支付宝客户端扫码进入,而不是将 URL 复制到普通浏览器中打开。
- 在真机页面中确认
window.AlipayJSBridge存在,并等待AlipayJSBridgeReady。 - 确认交易由服务端通过当前应用创建,返回的支付宝交易号有效。
- 核对创建订单和前端唤起支付时使用的
app_id、商户账号和签约产品。 - 检查授权回调域名、支付页面域名和应用配置。
- 查看服务端接口响应中的错误码、
sub_code和sub_msg,不要只看前端提示。 - 如果配置和代码都没有异常,向支付宝技术支持提供应用 ID、接口名、调用时间、支付宝交易号和完整错误信息,请其确认该产品是否允许从当前入口调用。
聚合支付服务商能够实现这类流程,通常是因为已经完成了支付产品签约、应用配置、商户进件和域名配置,并通过支付宝认可的客户端入口发起调用。只复制其前端调用代码,无法绕过这些前置条件。
备注:内容仅供参考。
