支付宝扫一扫与普通浏览器访问H5商城应选择哪种支付产品
明确结论
这两种访问场景通常需要使用不同的支付方式:
- 用户通过普通手机浏览器进入 H5 商城时,使用支付宝“手机网站支付”,由服务端调用
alipay.trade.wap.pay。 - 用户在支付宝 App 内通过扫一扫进入 H5 商城时,使用支付宝客户端的 JSAPI 支付。服务端先调用
alipay.trade.create创建交易,再由页面调用tradePay唤起支付宝收银台。
草料二维码只负责将商城 URL 编码为二维码,不会决定使用哪种支付产品。支付方式取决于付款时页面所在的运行环境。
这里使用的是“商城入口码”,并非支付宝“当面付”生成的支付二维码。因此,即使用户通过扫一扫进入商城,也不应直接选择当面付。
为什么需要区分
普通浏览器无法直接调用支付宝客户端内部的 JSAPI,一般需要通过 alipay.trade.wap.pay 跳转或唤起支付宝完成付款。
用户在支付宝 App 中扫描二维码后,商城页面会运行在支付宝客户端的 WebView 中。如果此时仍使用手机网站支付,可能遇到重复跳转、无法正确返回或其他兼容性问题。更合适的流程如下:
支付宝内打开商城
↓
服务端调用 alipay.trade.create
↓
取得 trade_no
↓
H5 页面调用 tradePay
↓
支付宝客户端展示收银台
两条支付链路可以共用商品、订单和支付结果处理逻辑,但需要使用不同的支付发起接口。
推荐实现方式
1. 判断当前运行环境
前端可以通过 User-Agent 判断页面是否运行在支付宝客户端中:
function isAlipayClient() {
return /AlipayClient/i.test(navigator.userAgent);
}
User-Agent 判断只用于选择前端交互方式。创建订单、校验金额和确认支付结果等操作必须在服务端完成,不能信任前端传入的金额或支付状态。
2. 支付宝客户端内使用 JSAPI 支付
前端先请求服务端创建支付宝交易并取得 trade_no:
async function payInAlipay(orderId) {
const response = await fetch('/api/pay/alipay/jsapi/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ orderId })
});
const data = await response.json();
if (!data.tradeNo) {
throw new Error(data.message || '创建支付宝交易失败');
}
invokeAlipayTradePay(data.tradeNo);
}
拿到交易号后,通过支付宝客户端提供的 tradePay JSAPI 唤起收银台:
function invokeAlipayTradePay(tradeNo) {
function pay() {
AlipayJSBridge.call(
'tradePay',
{
tradeNO: tradeNo
},
function (result) {
// 此处结果只能用于页面展示,不能作为最终支付依据
console.log('tradePay result:', result);
// 支付结束后向自己的服务端查询订单状态
queryOrderStatus();
}
);
}
if (window.AlipayJSBridge) {
pay();
} else {
document.addEventListener('AlipayJSBridgeReady', pay, false);
}
}
服务端需要按以下流程处理:
- 根据
orderId查询本地订单。 - 重新校验订单金额、商品状态和订单归属。
- 使用商户私钥调用
alipay.trade.create。 - 将支付宝返回的
trade_no传给前端。 - 由前端通过
tradePay唤起收银台。
服务端请求参数示意:
{
"method": "alipay.trade.create",
"biz_content": {
"out_trade_no": "ORDER_202609090001",
"total_amount": "99.00",
"subject": "商城订单",
"product_code": "JSAPI_PAY"
}
}
具体必填参数和 product_code 要求,应以商户当前签约的产品及支付宝开放平台控制台中的接口文档为准,不要直接照搬示例中的固定值。
3. 普通浏览器使用手机网站支付
在普通浏览器中,由服务端调用:
alipay.trade.wap.pay
用户提交订单后,前端可以跳转到服务端提供的支付地址:
function payInBrowser(orderId) {
window.location.href =
'/api/pay/alipay/wap?orderId=' + encodeURIComponent(orderId);
}
服务端调用接口时,通常需要设置业务参数及同步、异步回调地址:
{
"method": "alipay.trade.wap.pay",
"return_url": "https://shop.example.com/pay/result",
"notify_url": "https://api.example.com/pay/alipay/notify",
"biz_content": {
"out_trade_no": "ORDER_202609090001",
"total_amount": "99.00",
"subject": "商城订单",
"product_code": "QUICK_WAP_WAY",
"quit_url": "https://shop.example.com/order/ORDER_202609090001"
}
}
支付宝 SDK 通常会生成自动提交的表单或支付跳转内容。服务端应将这些内容原样返回给浏览器,不要自行拼接签名后的 URL。
统一前端入口
两种运行环境可以共用一个支付按钮:
async function startAlipay(orderId) {
try {
if (isAlipayClient()) {
await payInAlipay(orderId);
} else {
payInBrowser(orderId);
}
} catch (error) {
console.error(error);
alert('支付发起失败,请稍后重试');
}
}
后端也应尽量使用统一的业务订单号,再根据运行环境通过不同接口创建支付宝交易。不要让前端自行生成订单号或金额。
支付结果必须以服务端为准
无论使用 tradePay 还是手机网站支付,都不能根据前端回调直接将订单改为“已支付”。
服务端应按以下流程确认支付结果:
- 接收支付宝异步通知。
- 验证通知签名。
- 校验
app_id、out_trade_no、交易金额和收款方信息。 - 确认交易状态符合要求。
- 以幂等方式更新本地订单。
- 必要时调用交易查询接口再次确认。
同步返回页面和 JSAPI 回调只能用于提示用户“正在确认支付结果”,不能作为发货依据。
注意事项
- 商户需要在支付宝开放平台开通并签约相应的支付产品。能否在同一个应用下同时使用这些产品,应以当前商户资质、应用类型和开放平台控制台中的签约结果为准。
- H5 商城域名、应用公钥、支付宝公钥、网关地址和回调地址需要按开放平台要求配置。
notify_url必须能由支付宝服务器通过公网访问,不能依赖登录状态,也不能只配置内网地址。- 同一笔业务订单需要防止重复创建、重复支付和重复通知,订单状态更新必须具备幂等性。
- 不要将应用私钥放入 JavaScript、H5 页面或移动端代码。所有签名和验签操作都应在服务端完成。
- 支付完成后,应查询自己的订单状态,不能只展示
tradePay返回的结果码。 - 如果页面还可能运行在微信、其他 App WebView 或桌面浏览器中,需要为不支持的场景单独设计提示和跳转策略。
推荐方案是:普通浏览器使用 alipay.trade.wap.pay,支付宝客户端内使用 alipay.trade.create + tradePay,两者共用本地订单和异步通知处理体系。
备注:内容仅供参考。
