Vue 中 Alipay JSSDK 支付成功后回调不执行
结论
不要把 Alipay JSSDK 的支付回调当作订单支付成功的最终依据。前端回调可以用来更新界面或触发订单查询,但订单是否支付成功,必须由服务端通过支付宝异步通知或交易查询接口确认。
取消支付时能够收到回调,说明 JSSDK 基本调用成功。支付成功后没有执行回调,常见原因包括页面跳转或刷新、WebView 生命周期变化、Vue 组件销毁,以及支付宝客户端环境差异。即使回调返回 resultCode: "9000",服务端也必须验签并核对订单状态。
推荐处理流程
建议按以下流程处理:
- Vue 页面向业务服务端请求创建订单。
- 服务端调用支付宝接口创建交易,再将
trade_no返回给前端。 - 前端调用
ap.tradePay。 - 前端收到回调后,立即向业务服务端查询订单状态。
- 如果没有收到回调,则在页面重新显示、路由恢复或用户点击”我已支付”时查询订单状态。
- 服务端通过支付宝异步通知或主动交易查询确认最终支付结果。
前端结果只能用于界面提示。数据库中的订单状态必须经过服务端确认后才能更新。
Vue 调用示例
以下示例使用支付宝客户端内的 ap.tradePay。tradeNO 应使用服务端创建交易后取得的支付宝交易号,具体字段以实际接入接口的返回值为准。
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue'
const paying = ref(false)
const orderStatus = ref('UNPAID')
const outTradeNo = ref('')
function waitAlipayReady() {
return new Promise((resolve, reject) => {
if (window.AlipayJSBridge) {
resolve()
return
}
const timer = window.setTimeout(() => {
document.removeEventListener('AlipayJSBridgeReady', handleReady)
reject(new Error('AlipayJSBridge 初始化超时'))
}, 10000)
function handleReady() {
window.clearTimeout(timer)
resolve()
}
document.addEventListener(
'AlipayJSBridgeReady',
handleReady,
{ once: true }
)
})
}
async function createOrder() {
const response = await fetch('/api/payment/alipay/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
outTradeNo: outTradeNo.value
})
})
if (!response.ok) {
throw new Error('创建支付宝交易失败')
}
return response.json()
}
async function queryOrderStatus() {
if (!outTradeNo.value) {
return
}
const response = await fetch(
`/api/payment/orders/${encodeURIComponent(outTradeNo.value)}`
)
if (!response.ok) {
throw new Error('查询订单状态失败')
}
const result = await response.json()
orderStatus.value = result.status
if (result.status === 'PAID') {
// 展示支付成功页面或进入后续业务流程
}
}
async function pay() {
if (paying.value) {
return
}
paying.value = true
try {
await waitAlipayReady()
const payment = await createOrder()
window.ap.tradePay(
{
tradeNO: payment.tradeNo
},
async result => {
console.log('tradePay callback:', result)
// 回调结果只用于交互提示,最终状态仍从服务端获取。
if (String(result.resultCode) === '6001') {
console.log('用户取消支付')
}
try {
await queryOrderStatus()
} catch (error) {
console.error('查询订单状态失败:', error)
}
}
)
} catch (error) {
console.error('发起支付失败:', error)
} finally {
paying.value = false
}
}
function handlePageShow() {
queryOrderStatus().catch(error => {
console.error('恢复页面后查询订单失败:', error)
})
}
onMounted(() => {
window.addEventListener('pageshow', handlePageShow)
document.addEventListener('visibilitychange', handleVisibilityChange)
})
onBeforeUnmount(() => {
window.removeEventListener('pageshow', handlePageShow)
document.removeEventListener('visibilitychange', handleVisibilityChange)
})
function handleVisibilityChange() {
if (document.visibilityState === 'visible') {
queryOrderStatus().catch(error => {
console.error('页面重新可见后查询订单失败:', error)
})
}
}
</script>
resultCode === "9000" 通常表示支付宝客户端认为支付成功,resultCode === "6001" 通常表示用户取消。不同接入方式和客户端版本可能返回不同字段,因此应先打印完整的 result,再根据当前使用的支付宝接口文档进行判断。
服务端必须处理异步通知
创建交易时,应配置支付宝异步通知地址。服务端收到通知后,至少要完成以下检查:
- 使用支付宝公钥验证通知签名。
- 核对
app_id、商户订单号和支付宝交易号。 - 核对订单金额、币种和收款方信息。
- 确认交易状态符合业务要求。
- 以幂等方式将订单更新为已支付。
- 按照支付宝协议返回通知处理结果。
伪代码如下:
app.post('/api/payment/alipay/notify', async (req, res) => {
const params = req.body
const verified = verifyAlipaySignature(params)
if (!verified) {
res.status(400).send('failure')
return
}
const order = await findOrder(params.out_trade_no)
if (!order) {
res.status(404).send('failure')
return
}
if (String(order.amount) !== String(params.total_amount)) {
res.status(400).send('failure')
return
}
if (
params.trade_status === 'TRADE_SUCCESS' ||
params.trade_status === 'TRADE_FINISHED'
) {
await markOrderPaidIdempotently({
outTradeNo: params.out_trade_no,
tradeNo: params.trade_no
})
}
res.send('success')
})
这段代码只展示处理结构。签名算法、请求体解析方式、金额精度和通知响应格式,都应按照实际使用的支付宝服务端 SDK 和当前接口文档实现。
给前端提供订单查询接口
异步通知可能晚于页面查询到达,因此前端可以进行有限次数的短轮询:
async function waitForPaid(maxAttempts = 6, interval = 1500) {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
await queryOrderStatus()
if (orderStatus.value === 'PAID') {
return true
}
await new Promise(resolve => {
window.setTimeout(resolve, interval)
})
}
return false
}
不要无限轮询。如果暂时查不到已支付状态,可以显示”支付结果确认中”,并允许用户稍后刷新页面或手动点击”查询支付结果”。
排查支付成功后回调不执行
可以逐项检查以下问题:
- 发起支付后,页面是否执行了
location.href、location.reload()、window.close()或路由跳转。 - 支付成功时,是否通过
returnUrl返回了新页面,导致原页面的 JavaScript 上下文被销毁。 - Vue 组件是否因路由变化而卸载,回调中的状态对象是否已经失效。
- 是否在
AlipayJSBridgeReady触发前调用了 JSSDK。 tradeNO是否来自正确的支付宝交易创建接口,是否误传成了商户订单号。- 当前接入的是支付宝客户端 JSAPI,还是手机网站支付。手机网站支付通常采用表单跳转流程,不能套用
ap.tradePay的回调模型。 - 是否只在开发者工具中查看日志。支付过程中 WebView 可能发生切换,支付成功后的日志未必还会显示在原调试窗口中。
- 支付成功页面能否通过
pageshow或visibilitychange恢复订单查询。
即使找到了前端回调不执行的原因并完成修复,也不能依赖该回调发货、充值或发放权益。可靠的处理方式仍然是:以服务端异步通知为主,用交易查询进行补偿,前端回调只负责改善交互体验。
备注:内容仅供参考。
