支付宝APP内H5支付成功后未回调且跳转异常
结论
支付成功后的页面跳转,不能只依赖前端的“支付成功回调”,需要同时处理两条链路:
- 服务端通过支付宝异步通知确认订单是否支付成功;
- 前端根据实际接入方式,通过
tradePay回调或 WAP 支付的return_url进入自定义结果页。
如果支付过程中已经离开原来的 H5 WebView,或者支付宝收银台关闭、页面容器发生切换,原页面中的 JavaScript 回调可能不会执行。H5 也无法直接监听支付宝收银台上的“完成”按钮。遇到这种情况,后续页面应由创建支付订单时配置的返回地址决定,不能继续等待原页面回调。
支付完成后进入另一个支付宝小程序,通常表示支付请求、返回地址或中间重定向链路与该小程序有关。排查时应重点检查服务端生成的支付参数,不能只看前端回调代码。
先确认当前使用的支付方式
支付宝 APP 内的 H5 页面通常有两种支付方式,不能混用。
使用 AlipayJSBridge.tradePay
这种方式不会让页面整体跳转,而是通过支付宝 JSAPI 拉起收银台。支付结束后,结果会回调到当前页面。
function ready(callback) {
if (window.AlipayJSBridge) {
callback();
} else {
document.addEventListener('AlipayJSBridgeReady', callback, false);
}
}
ready(function () {
AlipayJSBridge.call(
'tradePay',
{
tradeNO: '服务端创建并返回的支付宝交易号'
},
function (result) {
if (String(result.resultCode) === '9000') {
window.location.replace('/payment/result?orderNo=YOUR_ORDER_NO');
return;
}
if (String(result.resultCode) === '6001') {
// 用户取消支付
return;
}
// 其他结果不要直接判定为支付失败,应向服务端查询订单状态
checkOrderStatus('YOUR_ORDER_NO');
}
);
});
只有原来的 H5 页面仍然存在,回调函数才可能执行。如果支付过程中原 WebView 被销毁、页面发生导航,或者实际打开了另一个小程序页面,原页面的回调就不会继续运行。
使用 alipay.trade.wap.pay
如果服务端调用手机网站支付接口,通常会生成表单或支付 URL。浏览器提交后会离开当前页面,因此不能再用原页面的 JavaScript 回调控制支付完成后的跳转,而要在服务端请求中配置 return_url。
示意代码:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setNotifyUrl("https://example.com/api/alipay/notify");
request.setReturnUrl("https://example.com/payment/result");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001",
"total_amount": "100.00",
"subject": "订单支付",
"product_code": "QUICK_WAP_WAY"
}
""");
用户完成支付并点击收银台中的“完成”后,支付宝通常会通过同步返回链路打开 return_url。实际行为还可能受到支付宝客户端页面容器、支付产品类型和接入方案的影响,最终应以所用接口的官方文档和联调结果为准。
完整处理流程
1. 创建支付订单时明确返回地址
检查服务端调用支付宝接口时传入的以下参数:
return_urlnotify_urlapp_idout_trade_noproduct_code- 支付接口名称
return_url 应填写可通过 HTTPS 访问的完整地址,例如:
https://example.com/payment/result?orderNo=ORDER_202609090001
不要把订单支付状态直接写入 URL,也不要只根据同步返回参数修改订单状态。
如果接口或 SDK 支持 quit_url,需要注意,它通常是用户中途退出支付时的返回地址,并不等同于支付成功后的 return_url。当前支付产品是否支持该参数及其具体含义,应以对应接口定义为准。
2. 用异步通知确认支付结果
更新订单状态应主要依据支付宝服务端的异步通知。接收通知时,至少要完成以下处理:
- 验证支付宝签名;
- 校验
app_id; - 校验
out_trade_no; - 校验金额和币种;
- 判断交易状态;
- 对重复通知进行幂等处理。
伪代码如下:
@PostMapping("/api/alipay/notify")
public String alipayNotify(HttpServletRequest request) {
Map<String, String> params = extractParams(request);
boolean verified = verifyAlipaySignature(params);
if (!verified) {
return "failure";
}
String orderNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
validateAppId(params.get("app_id"));
validateOrderAmount(orderNo, params.get("total_amount"));
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
markOrderPaidIdempotently(orderNo, params.get("trade_no"));
}
return "success";
}
前端没有收到 9000,不代表订单支付失败。前端回调、同步返回和异步通知的到达顺序可能不同,也可能因为网络或页面生命周期问题而缺失。
3. 自定义结果页主动查询订单状态
无论页面是通过 tradePay 回调跳转,还是通过 return_url 返回,结果页都应向业务服务端查询订单状态:
async function checkOrderStatus(orderNo) {
const response = await fetch(
`/api/orders/${encodeURIComponent(orderNo)}/payment-status`,
{
credentials: 'include',
cache: 'no-store'
}
);
const result = await response.json();
if (result.status === 'PAID') {
renderPaymentSuccess();
} else if (result.status === 'PENDING') {
setTimeout(() => checkOrderStatus(orderNo), 1500);
} else {
renderPaymentPending();
}
}
结果页可以先显示“支付结果确认中”,并进行短时间轮询,以免异步通知尚未到达时误报支付失败。轮询必须设置次数或超时时间,不能无限执行。
为什么成功时不回调,失败时却能回调
这种现象通常表示支付成功和支付失败使用了不同的页面链路。
支付失败或取消时,支付宝可能直接关闭收银台并返回原 H5,因此 tradePay 回调还能执行。支付成功后,如果支付宝根据支付订单信息、返回地址或客户端路由打开了另一个页面,原 H5 就不再处于活动状态,回调也无法完成。
还要检查以下情况:
- 同时使用了表单提交和
AlipayJSBridge.call('tradePay'); - 支付前执行了
location.href、location.replace或表单跳转; return_url指向旧域名、旧活动页或小程序跳转链接;- 返回页面的服务端又执行了一次
302重定向; - 支付订单使用了错误的
app_id、商户配置或支付产品; - 中间页根据 User-Agent 自动跳转到支付宝小程序;
- 页面绑定了
visibilitychange、pageshow等事件,并在恢复时触发错误跳转; - 前端重复发起支付,实际完成的是另一笔订单;
- CDN、网关或服务端缓存仍在返回旧的支付参数。
排查跳入其他小程序的问题
可以保存并对比一笔正常订单和一笔异常订单的完整支付链路,但日志中不能记录私钥、完整签名或用户敏感信息。
重点检查:
- 创建订单时实际使用的
app_id是否属于当前应用; return_url是否确实指向自定义页面的 HTTPS 地址;return_url是否经过短链接、网关或活动页重定向;- 返回地址是否包含
alipays://、小程序 Scheme 或小程序跳转参数; - 支付参数是否由旧服务、旧配置中心或另一套环境生成;
- 支付订单号是否与前端当前展示的业务订单一致;
- 支付宝开放平台配置、应用授权或支付产品接入方式是否发生变化。
可以在自定义结果页临时记录以下信息,用于确认该页面是否被打开:
navigator.sendBeacon(
'/api/payment/return-page-log',
JSON.stringify({
orderNo: new URLSearchParams(location.search).get('orderNo'),
href: location.href,
referrer: document.referrer,
time: Date.now()
})
);
日志中不要写入支付宝私钥、签名密钥、用户身份凭证或完整支付请求内容。
注意事项
- H5 无法可靠监听支付宝收银台内部“完成”按钮的点击事件。
tradePay的前端回调只能用于页面交互,不能作为最终入账依据。return_url是同步返回地址,同样不能作为确认支付成功的唯一依据。- 服务端应结合异步通知和主动查询确认订单状态。
- 不要只判断
resultCode === '9000'。如果回调缺失或结果不明确,应查询服务端订单状态。 - 如果代码没有变化,但问题最近才出现,还应检查支付订单参数、开放平台应用配置、网关重定向和客户端容器变化。
- 如果支付参数和业务跳转都没有异常,但页面仍然稳定进入无关小程序,需要整理异常订单号、支付宝交易号、发生时间、支付宝客户端版本和复现路径,提交给支付宝开放平台技术支持进一步核查。
备注:内容仅供参考。
