支付宝小程序支付后跳转广告且未触发 success 或 fail 回调
结论
不要把 my.tradePay 的 success 或 fail 回调当作判断支付结果的唯一依据。支付过程中如果出现广告页、收银台跳转、进程挂起或页面生命周期切换,前端回调可能延迟,也可能直接丢失。
建议按以下流程处理:
- 调起支付前,保存待确认订单;
- 服务端接收支付宝异步通知并完成验签;
- 小程序重新显示时,向服务端查询订单状态;
- 服务端确认支付成功后,再发货、开通权益或跳转业务页面;
- 将广告跳转和回调缺失问题提交给支付宝技术支持排查。
可能原因
调用 my.tradePay 后,支付流程并不完全在当前小程序页面内运行。进入支付宝收银台、营销广告或其他承接页面时,小程序可能触发 onHide,JavaScript 也可能暂停执行。
关闭广告并返回小程序,不代表原来的支付调用上下文仍然存在。此时可能出现以下情况:
success没有执行;fail没有执行;complete也没有执行;- 用户已经支付成功,但前端仍停留在等待状态。
仅凭这一现象,无法判断广告页面是由支付宝平台策略、商家营销配置还是特定客户端环境触发。支付宝侧需要结合订单号、发生时间和客户端日志进一步排查。
另外,success 只表示接口调用成功返回,不能直接视为业务上的最终支付成功。最终支付结果仍需由服务端确认。
推荐处理流程
1. 调起支付前记录待确认订单
服务端创建交易并返回 tradeNO 后,小程序应先保存业务订单号,再调用支付接口。
const pendingOrder = {
orderNo: "YOUR_ORDER_NO",
tradeNO: "ALIPAY_TRADE_NO"
};
my.setStorageSync({
key: "pendingPayOrder",
data: pendingOrder
});
my.tradePay({
tradeNO: pendingOrder.tradeNO,
success(res) {
// resultCode 的具体含义以支付宝当前接口文档为准。
// 即使前端返回成功,也建议由服务端再次确认订单状态。
confirmPayment(pendingOrder.orderNo);
},
fail(err) {
console.error("my.tradePay fail:", err);
// fail 可能代表接口调用失败、用户取消或其他异常,
// 不应仅凭该回调把服务端订单直接改成支付失败。
confirmPayment(pendingOrder.orderNo);
},
complete(res) {
console.log("my.tradePay complete:", res);
}
});
支付流程如果触发页面跳转,这三个回调都可能无法正常执行,因此还需要通过生命周期进行补偿。
2. 在小程序恢复显示时查询订单
可以在应用或支付页面的 onShow 中检查是否存在待确认订单。
Page({
onShow() {
const result = my.getStorageSync({
key: "pendingPayOrder"
});
const pendingOrder = result && result.data;
if (!pendingOrder || !pendingOrder.orderNo) {
return;
}
this.queryPaymentStatus(pendingOrder.orderNo);
},
queryPaymentStatus(orderNo) {
my.request({
url: "https://example.com/api/orders/payment-status",
method: "GET",
data: {
orderNo
},
success: (res) => {
const status = res.data && res.data.status;
if (status === "PAID") {
my.removeStorageSync({
key: "pendingPayOrder"
});
this.handlePaymentSuccess();
return;
}
if (status === "CLOSED" || status === "FAILED") {
my.removeStorageSync({
key: "pendingPayOrder"
});
this.handlePaymentFailure(status);
return;
}
// PENDING 或 UNKNOWN 状态下提示用户稍后刷新,
// 不要立即认定支付失败,也不要自动创建重复订单。
this.setData({
paymentPending: true
});
},
fail: (err) => {
console.error("query payment status failed:", err);
this.setData({
paymentPending: true
});
}
});
},
handlePaymentSuccess() {
// 刷新订单、展示支付成功页或进入后续业务流程。
},
handlePaymentFailure(status) {
// 根据服务端确认的状态处理关闭或失败。
}
});
应用被彻底关闭后再次打开时,也要读取这条待确认记录,不能只依赖支付页面实例的 onShow。
3. 由服务端确认最终状态
服务端应同时使用以下两种机制:
- 接收支付宝异步支付通知;
- 通知未到达或前端主动查询时,调用支付宝交易查询接口核实状态。
流程示意:
小程序创建订单
↓
服务端创建支付宝交易
↓
小程序保存待确认订单并调用 my.tradePay
↓
支付宝异步通知服务端
↓
服务端验签并更新订单
↓
小程序回到前台后查询服务端订单状态
处理异步通知时,至少要完成以下工作:
- 使用支付宝公钥验签;
- 校验商户身份、应用身份、订单号和金额;
- 保证订单状态更新具备幂等性;
- 确认交易成功后,才执行发货或开通权益;
- 按支付宝协议返回通知处理结果。
如果异步通知暂时没有到达,服务端可以根据业务订单号调用 alipay.trade.query 查询交易状态。具体请求参数、签名方式和交易状态枚举,以当前支付宝开放平台文档为准。
避免重复执行后续业务
前端支付回调、异步通知和主动查询可能先后返回同一个成功结果,因此服务端必须进行幂等控制。
例如,只有订单状态从“待支付”成功变更为“已支付”时,才能首次执行发货:
UPDATE orders
SET status = 'PAID',
paid_at = CURRENT_TIMESTAMP
WHERE order_no = ?
AND status = 'PENDING';
是否执行后续业务,可以根据受影响行数判断。如果受影响行数为 0,通常表示订单已经处理,或者当前状态不允许变更,此时不能再次发货。
临时止损措施
在支付宝侧明确问题原因前,可以增加以下提示和处理措施:
- 提示“支付结果确认中,请勿重复支付”;
- 提供“刷新支付结果”按钮;
- 订单仍处于待确认状态时,不要立即创建新交易;
- 超过合理时间仍未确认时,引导用户进入订单页查询;
- 保留订单号、支付宝交易号和支付发起时间,方便客服核对。
不要等待固定时长后直接判定支付成功。小程序进入后台后,setTimeout 可能暂停执行,而且计时结束也不能证明交易已经成功。
向支付宝提交排查信息
如果问题近期集中出现且可以稳定复现,应通过支付宝开放平台的技术支持渠道提交工单,并提供以下信息:
- 小程序
appId; - 接口名称
my.tradePay; - 商户订单号
out_trade_no; - 支付宝交易号
trade_no; - 精确发生时间;
- 支付宝客户端版本;
- 手机系统和机型;
- 是否必现以及涉及的账号范围;
- 广告页面截图或录屏;
- 小程序
onShow、onHide和支付调用日志; - 服务端是否收到异步通知;
- 交易查询接口返回的实际状态。
不要在公开渠道提交完整的用户账号、签名密钥或未经脱敏的支付信息。
注意事项
fail未触发不代表支付成功,success未触发也不代表支付失败。- 前端显示结果只能用于交互提示,最终订单状态以服务端核验结果为准。
- 不建议通过重复调用
my.tradePay处理回调丢失,否则可能造成重复支付,也会让用户困惑。 - 可以测试支付宝客户端、小程序基础库和开发者工具的稳定版本,但没有官方说明时,不能断言升级或降级一定能解决广告跳转问题。
- 如果服务端已经收到有效的支付成功通知,用户重新进入订单页后应能恢复后续流程,不需要继续等待原来的前端回调。
备注:内容仅供参考。
