H5支付完成后为何未直接跳转到returnUrl?
支付小周
结论
这是 H5 支付的正常交互流程。
在 alipay.trade.wap.pay 场景中,支付宝 App 内的“完成”按钮用于结束收银台并返回发起支付的浏览器页面,并不会直接访问商户配置的 return_url。回到浏览器后,用户还需要点击页面上的“已支付完成返回查看订单”,随后才会进入 return_url。
正式环境也应按这一流程设计。页面样式、按钮文案和部分机型的自动跳转表现可能有所不同,业务系统不能依赖“完成”按钮直接跳转到 return_url。
为什么不能直接跳转
H5 支付会在支付宝 App 和手机浏览器两个独立环境中运行,完整流程如下:
- 用户在手机浏览器中打开商户页面。
- 浏览器通过 URL Scheme 等方式拉起支付宝 App。
- 用户在支付宝 App 中完成支付。
- 支付宝 App 结束收银台流程,并尝试返回原浏览器。
- 浏览器中的支付宝中间页提示用户确认已经完成支付。
- 用户点击确认按钮后,中间页跳转到商户的
return_url。
支付宝 App 无法稳定控制用户原先使用的浏览器,也不能保证浏览器允许跨应用自动唤起和重定向。操作系统、浏览器及其安全策略都可能影响返回过程,因此需要中间确认页衔接 App 和浏览器。
还需要区分两个回调地址:
return_url:前端同步跳转地址,主要用于展示支付完成后的页面。notify_url:服务端异步通知地址,用于可靠地确认支付结果。
如果用户关闭页面、浏览器拦截跳转、网络异常,或者用户没有点击确认按钮,return_url 就可能不会被访问。因此,不能把它作为判断订单支付成功的唯一依据。
推荐处理方式
1. 正确配置两个回调地址
发起 alipay.trade.wap.pay 请求时,应同时配置 return_url 和 notify_url:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://www.example.com/pay/return");
request.setNotifyUrl("https://www.example.com/pay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609080001",
"total_amount": "99.00",
"subject": "订单支付",
"product_code": "QUICK_WAP_WAY"
}
""");
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
String paymentForm = response.getBody();
这两个地址都应能通过公网访问,正式环境建议使用 HTTPS。
2. 在 notify_url 中更新订单状态
收到异步通知后,先完成验签,再核对订单号、金额、商户身份和交易状态。确认各项信息无误后更新订单状态,并返回纯文本 success。
@PostMapping("/pay/notify")
public String handleNotify(HttpServletRequest request) throws Exception {
Map<String, String> params = extractAlipayParams(request);
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
String outTradeNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
// 还应核对 app_id、seller_id、订单金额等业务数据。
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
markOrderPaidIdempotently(outTradeNo, totalAmount);
}
return "success";
}
支付宝可能重复发送异步通知,因此订单更新必须支持幂等处理。
3. 在 return_url 页面主动查询订单状态
用户返回商户页面后,不要立即显示“支付成功”。页面应根据商户订单号查询服务端保存的订单状态。如果异步通知还没有到达,可以由服务端调用 alipay.trade.query 补充确认。
async function refreshPaymentResult(outTradeNo) {
const response = await fetch(
`/api/orders/${encodeURIComponent(outTradeNo)}/payment-status`,
{
credentials: "include",
cache: "no-store"
}
);
const result = await response.json();
if (result.status === "PAID") {
window.location.replace(`/orders/${encodeURIComponent(outTradeNo)}`);
return;
}
document.querySelector("#payment-status").textContent =
"支付结果确认中,请稍后刷新";
}
可以按以下流程处理:
return_url 页面
→ 查询商户服务端订单状态
→ 已支付:展示成功或进入订单详情
→ 未确认:服务端调用 alipay.trade.query
→ 仍未确认:提示用户稍后刷新
注意事项
- 不要把用户访问
return_url视为支付成功的证明。 - 不要根据
return_url中未经验证的参数直接修改订单状态。 - 用户没有返回商户页面不代表支付失败。用户可能已经付款,只是关闭了浏览器或没有点击确认按钮。
notify_url需要完成验签、订单匹配、金额核对和幂等处理。- 沙箱与正式环境的页面表现可能不同,不能据此认定按钮文案或自动跳转行为一定相同。
- 前端可以提供“我已完成支付”按钮,但点击后应查询真实的订单状态,不能直接把订单标记为已支付。
- 如需更稳定地确认支付结果,应结合异步通知和主动查询完成支付闭环,不要依赖 App 自动跳转回浏览器。
备注:内容仅供参考。
