PAYATHON 2026

Vue 中 Alipay JSSDK 支付成功后回调不执行

支付小周

结论

不要把 Alipay JSSDK 的支付回调当作订单支付成功的最终依据。前端回调可以用来更新界面或触发订单查询,但订单是否支付成功,必须由服务端通过支付宝异步通知或交易查询接口确认。

取消支付时能够收到回调,说明 JSSDK 基本调用成功。支付成功后没有执行回调,常见原因包括页面跳转或刷新、WebView 生命周期变化、Vue 组件销毁,以及支付宝客户端环境差异。即使回调返回 resultCode: "9000",服务端也必须验签并核对订单状态。

推荐处理流程

建议按以下流程处理:

  1. Vue 页面向业务服务端请求创建订单。
  2. 服务端调用支付宝接口创建交易,再将 trade_no 返回给前端。
  3. 前端调用 ap.tradePay。
  4. 前端收到回调后,立即向业务服务端查询订单状态。
  5. 如果没有收到回调,则在页面重新显示、路由恢复或用户点击”我已支付”时查询订单状态。
  6. 服务端通过支付宝异步通知或主动交易查询确认最终支付结果。

前端结果只能用于界面提示。数据库中的订单状态必须经过服务端确认后才能更新。

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,再根据当前使用的支付宝接口文档进行判断。

服务端必须处理异步通知

创建交易时,应配置支付宝异步通知地址。服务端收到通知后,至少要完成以下检查:

  1. 使用支付宝公钥验证通知签名。
  2. 核对 app_id、商户订单号和支付宝交易号。
  3. 核对订单金额、币种和收款方信息。
  4. 确认交易状态符合业务要求。
  5. 以幂等方式将订单更新为已支付。
  6. 按照支付宝协议返回通知处理结果。

伪代码如下:

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 恢复订单查询。

即使找到了前端回调不执行的原因并完成修复,也不能依赖该回调发货、充值或发放权益。可靠的处理方式仍然是:以服务端异步通知为主,用交易查询进行补偿,前端回调只负责改善交互体验。

备注:内容仅供参考。