H5 页面支付宝支付应调用手机网站支付还是当面付接口?
结论
这个流程应使用支付宝手机网站支付,对应接口通常是:
alipay.trade.wap.pay
用户扫描的“第三方二维码”只是进入订单详情页的方式,实际付款发生在 H5 订单页。因此,选择支付产品时,应看用户点击支付按钮时所处的页面形态,不能仅凭流程中是否有二维码来判断。
当面付主要用于线下面对面收款。例如,商家展示支付宝付款二维码,用户扫码后直接进入支付宝收银台付款。其预下单接口通常是:
alipay.trade.precreate
如果用户扫码后先进入商户自己的订单详情页,再点击支付按钮,就不是典型的当面付扫码支付流程。
推荐流程
可以按以下步骤设计:
-
商户后台创建业务订单,并生成唯一的
out_trade_no。 -
后台生成订单详情页地址,例如:
https://example.com/orders/202609090001 -
将该地址生成普通二维码。
-
用户使用支付宝扫码,打开 H5 订单详情页。
-
用户点击“支付宝支付”。
-
H5 请求商户后端的支付接口。
-
商户后端调用
alipay.trade.wap.pay,生成支付宝收银台表单或跳转地址。 -
前端跳转到支付宝收银台,由用户完成付款。
-
支付宝通过
notify_url异步通知商户支付结果。 -
商户验签成功后更新订单状态。
这里需要区分两种二维码:
- 页面入口二维码:二维码内容是商户订单详情页的 URL,由商户自行生成。
- 支付宝当面付二维码:二维码内容由
alipay.trade.precreate返回,用户扫码后直接付款。
题述场景使用的是页面入口二维码。
后端调用示例
下面使用支付宝 Java SDK 演示核心调用逻辑。SDK 版本、网关地址以及证书或密钥配置,应以项目的实际接入方式为准。
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://example.com/pay/result");
request.setNotifyUrl("https://api.example.com/alipay/notify");
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo("202609090001");
model.setTotalAmount("99.00");
model.setSubject("订单 202609090001");
model.setProductCode("QUICK_WAP_WAY");
request.setBizModel(model);
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
// body 通常是用于跳转至支付宝收银台的 HTML 表单
String paymentForm = response.getBody();
// 将 paymentForm 返回给浏览器
} else {
throw new IllegalStateException(
"支付宝下单失败:" + response.getSubCode() + " " + response.getSubMsg()
);
}
如果接口返回自动提交的 HTML 表单,后端可以直接输出,也可以将其返回给前端,由前端写入页面。具体实现取决于使用的 SDK 和后端框架。
支付按钮处理示例
用户点击前端按钮后,请求商户后端创建支付:
<button id="alipay-button">支付宝支付</button>
<script>
document.getElementById("alipay-button").addEventListener("click", async () => {
const response = await fetch("/api/orders/202609090001/alipay", {
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
if (!response.ok) {
alert("发起支付失败,请稍后重试");
return;
}
const html = await response.text();
document.open();
document.write(html);
document.close();
});
</script>
商户私钥不能保存在浏览器端,也不能由浏览器端使用。签名、金额校验和支付宝接口调用都必须在商户后端完成。
为什么不建议使用当面付
当面付的典型流程是:
商户调用 alipay.trade.precreate
↓
支付宝返回支付二维码内容
↓
用户使用支付宝扫码
↓
直接进入支付流程
题述流程则是:
商户生成订单页面二维码
↓
用户扫码进入订单详情页
↓
用户主动点击支付
↓
跳转支付宝收银台
两种流程的区别不是“是否扫码”,而是二维码中承载的内容,以及付款动作的发起方式。
如果流程改为扫码后直接付款,不再经过订单详情页上的支付按钮,可以考虑使用当面付接口 alipay.trade.precreate。
注意事项
以异步通知为支付成功依据
return_url 只用于支付完成后的页面跳转,不能单独作为订单支付成功的依据。用户可能在跳转前关闭页面,跳转参数也可能被伪造。
商户应通过 notify_url 接收支付宝的异步通知,并完成以下检查:
- 验证支付宝签名。
- 校验
app_id。 - 校验
out_trade_no。 - 校验订单金额。
- 校验收款方信息。
- 判断
trade_status。 - 对通知进行幂等处理。
不要信任前端提交的金额
前端只需提交订单标识。后端应重新读取订单数据,并据此确定和校验实际支付金额:
Order order = orderService.findByOrderNo(orderNo);
if (order == null) {
throw new IllegalArgumentException("订单不存在");
}
if (!order.isPendingPayment()) {
throw new IllegalStateException("订单状态不允许支付");
}
String totalAmount = order.getPayableAmount().setScale(2).toPlainString();
避免为同一订单重复创建支付
用户可能连续点击支付按钮。商户后端应使用唯一且稳定的 out_trade_no,并在发起支付前检查订单状态,避免为同一笔交易生成多笔互不关联的业务订单。
在实际扫码环境中验证
用户使用支付宝扫码后,订单详情页通常会在支付宝内置浏览器中打开。手机网站支付能否正常唤起收银台,还会受到商户产品签约状态、应用配置、域名、页面跳转方式和支付宝客户端环境的影响。
上线前至少需要验证:
- 支付产品是否已经签约并生效。
- 应用公钥或证书配置是否正确。
notify_url是否能通过公网 HTTPS 访问。- 支付域名和跳转地址是否符合当前开放平台要求。
- 支付流程在支付宝内置浏览器和普通手机浏览器中是否正常。
- 用户取消支付、重复支付或支付后关闭页面时,订单状态是否正确。
只要二维码用于打开商户的 H5 订单详情页,并由用户在页面中点击支付,就应优先使用手机网站支付 alipay.trade.wap.pay。只有二维码本身是支付宝付款码,用户扫码后直接付款时,才适合使用当面付。
备注:内容仅供参考。
