支付宝小程序支付成功后未收到success回调如何解决
结论
不要把 my.tradePay 的 success 回调当作支付成功的唯一依据。支付完成后,如果用户跳转到“优选好货”等其他小程序,原支付页面可能进入后台或被重新创建,相关 JavaScript 上下文也可能已经销毁,导致客户端回调无法执行。
建议按以下方式处理:
- 服务端接收支付宝异步支付通知,并更新订单状态。
- 小程序在
onShow中向业务服务端重新查询订单状态。 my.tradePay回调仅用于改善交互,不能作为最终支付凭证。- Android APP 如果需要获取支付结果,也应查询业务服务端的订单状态,不要等待小程序回传一次性的回调结果。
原因分析
取消支付后,用户仍停留在当前页面,页面上下文没有切换,因此 success 回调通常可以正常执行。不过,这里的 success 只表示支付 API 已返回结果,是否真正支付成功仍需根据 resultCode 判断。
正常支付后,如果用户点击“完成”并进入其他小程序,可能出现以下情况:
- 原小程序进入后台,回调被延迟执行;
- 支付页面被销毁或重新加载;
- 小程序进程被系统或支付宝回收;
- 跨小程序跳转中断了原页面的异步调用链;
- 用户返回时创建了新的页面实例,旧实例中的回调已经不存在。
所以,无论是否出现广告页,都不能假定支付接口一定会将结果返回给原页面。
推荐解决方案
1. 创建订单时保存业务订单号
调用支付前,先把业务订单号保存到页面状态或本地存储。不要只保存支付宝交易号,因为后续查询通常应以业务自己的订单号为准。
Page({
data: {
merchantOrderNo: '',
checking: false
},
async startPayment() {
const order = await createOrder();
this.setData({
merchantOrderNo: order.merchantOrderNo
});
my.setStorageSync({
key: 'pendingPaymentOrder',
data: {
merchantOrderNo: order.merchantOrderNo
}
});
my.tradePay({
tradeNO: order.tradeNo,
success: (res) => {
// success 只代表接口有结果返回,不能直接等同于业务订单支付成功。
if (res.resultCode === '9000') {
this.refreshOrderStatus(order.merchantOrderNo);
} else {
this.handlePaymentResult(res.resultCode);
}
},
fail: () => {
// 调用失败同样不能证明订单未支付,仍应查询服务端。
this.refreshOrderStatus(order.merchantOrderNo);
},
complete: () => {
// 页面发生跳转时,complete 也不保证一定有机会执行。
}
});
},
handlePaymentResult(resultCode) {
if (resultCode === '6001') {
my.showToast({ content: '支付已取消' });
return;
}
my.showToast({ content: '支付结果确认中' });
}
});
tradeNO 等字段应以当前接入的支付宝小程序支付产品文档为准。如果使用的是其他支付 API,也应采用相同的状态恢复方式。
2. 小程序恢复显示时查询订单
用户从“优选好货”或其他页面返回后,小程序会再次触发应用或页面的 onShow。此时可以读取待确认订单,再向业务服务端查询状态。
Page({
onShow() {
const stored = my.getStorageSync({
key: 'pendingPaymentOrder'
});
const order = stored && stored.data;
if (order && order.merchantOrderNo) {
this.refreshOrderStatus(order.merchantOrderNo);
}
},
async refreshOrderStatus(merchantOrderNo) {
if (this.data.checking) {
return;
}
this.setData({ checking: true });
try {
const result = await queryOrderFromServer(merchantOrderNo);
if (result.status === 'PAID') {
my.removeStorageSync({
key: 'pendingPaymentOrder'
});
my.redirectTo({
url: `/pages/pay-result/pay-result?orderNo=${encodeURIComponent(merchantOrderNo)}`
});
return;
}
if (result.status === 'CLOSED') {
my.removeStorageSync({
key: 'pendingPaymentOrder'
});
my.showToast({
content: '订单已关闭'
});
return;
}
my.showToast({
content: '支付结果确认中'
});
} catch (error) {
my.showToast({
content: '订单状态查询失败,请稍后重试'
});
} finally {
this.setData({ checking: false });
}
}
});
支付宝异步通知可能有短暂延迟。遇到这种情况,可以进行有限次数的轮询,例如每隔 1~2 秒查询一次,最多查询数次。不要无限轮询,也不能只根据客户端的 resultCode 将订单直接改为已支付。
3. 服务端以异步通知为准
服务端收到支付宝异步通知后,需要完成以下处理:
- 验证通知签名;
- 校验商户身份、应用身份、订单号、金额和币种;
- 确认交易状态符合业务要求;
- 以幂等方式更新订单;
- 按照支付宝协议返回应答内容;
- 对长时间未确认的订单主动调用交易查询接口,例如对应产品支持的
alipay.trade.query。
伪代码如下:
@PostMapping("/payment/alipay/notify")
public String handleNotify(HttpServletRequest request) {
Map<String, String> params = extractParameters(request);
if (!verifyAlipaySignature(params)) {
return "failure";
}
String orderNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
Order order = orderService.findByOrderNo(orderNo);
if (order == null || !amountMatches(order, totalAmount)) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 必须保证重复通知不会造成重复发货、重复记账。
orderService.markPaidIdempotently(orderNo, params.get("trade_no"));
}
return "success";
}
通知字段、交易状态和应答格式,应以所接入支付宝支付产品的当前文档为准。
Android APP 如何获取结果
如果 Android APP 只负责拉起支付宝小程序,就不应一直等待小程序返回支付结果。可以采用以下流程:
APP 创建业务订单
→ 拉起支付宝小程序支付
→ 服务端接收支付宝通知
→ APP 恢复前台后查询业务订单状态
APP 可以在 onResume() 中发起查询,也可以在订单详情页提供“刷新支付结果”功能。只有服务端确认订单状态为已支付后,APP 才进入支付成功页面。
关于“优选好货”跳转
如果该跳转来自支付宝支付完成页或支付宝侧的运营页面,业务代码通常既无法阻止它出现,也无法保证跨小程序跳转后原支付回调仍然有效。展示策略可能受到支付宝客户端版本、账号和支付产品配置等因素影响,不能仅凭这一现象判断是小程序代码出了问题。
如果跳转行为明显异常,可以记录以下信息并提交给支付宝技术支持排查:
- 支付发生时间;
- 支付宝交易号和业务订单号;
- 支付宝客户端版本;
- Android 系统版本和机型;
- 小程序
appId; - 从支付完成、进入其他小程序到返回的完整录屏;
- 小程序前后台生命周期日志。
注意事项
success回调不代表业务支付成功。必须检查resultCode,并通过服务端订单状态做最终确认。- 即使回调显示取消、失败或超时,订单仍有可能已经支付,应交由服务端最终确认。
- 不要根据客户端传来的金额或“支付成功”标记直接发货。
- 处理异步通知时必须验签、校验金额,并保证幂等。
- 持久化保存待支付订单号,避免页面重建后无法恢复。
onShow可能多次触发,订单查询和页面跳转都需要防止重复执行。
备注:内容仅供参考。
