PAYATHON 2026

小程序支付后跳转广告页导致支付成功回调无法触发

支付阿杰

结论

从支付宝广告服务助手返回后,原来的 my.tradePay 回调不会再次触发,也不能手动补触发。建议按以下方式处理:

  1. 支付前保存业务订单号,并将订单标记为”待确认”。
  2. 正常处理 my.tradePay 回调。
  3. 在小程序 onShow 或页面 onShow 中检查是否有待确认订单。
  4. 调用自己的服务端查询订单状态。服务端根据支付异步通知或 alipay.trade.query 确认支付结果。
  5. 发货、开通权益等支付成功后的操作必须由服务端幂等执行。

从广告页返回,只能说明需要重新确认支付状态,不能据此认定支付成功。

原因

my.tradePay 回调只属于当前这次支付调用的客户端执行链。支付后跳转到第三方小程序时,当前小程序可能进入后台,页面也可能被系统销毁。用户返回时,可能出现以下情况:

  • 原支付页面仍然存在,但支付回调不会重新执行。
  • 页面被重新创建,只执行 onLoad 和 onShow。
  • 小程序进程已被回收,再次进入时会执行 App.onLaunch 和页面生命周期。
  • 支付已经有结果,但客户端因页面跳转、网络异常或进程中断而没有收到结果。

因此,支付回调可以用于改善交互体验,但不能作为判断支付结果的唯一依据。最终状态应以服务端记录和支付宝侧的查询结果为准。

处理步骤

1. 支付前记录待确认订单

发起支付前,先把业务订单号保存到本地。不要只保存支付宝交易号,因为页面恢复后,通常需要使用业务订单号查询自己的服务端。

Page({
  startPay() {
    const orderNo = this.data.orderNo;
    const tradeNO = this.data.tradeNO;

    my.setStorageSync({
      key: 'pendingPayment',
      data: {
        orderNo,
        createdAt: Date.now()
      }
    });

    my.tradePay({
      tradeNO,
      success: (result) => {
        if (result.resultCode === '9000') {
          this.confirmPayment(orderNo);
        } else {
          // 未明确成功时仍由服务端确认,不要直接认定支付失败
          this.confirmPayment(orderNo);
        }
      },
      fail: () => {
        // 调用失败、页面跳转或回调丢失时保留待确认记录
        this.confirmPayment(orderNo);
      }
    });
  }
});

如果项目通过 orderStr 发起支付,可以用它替换 tradeNO,后续的恢复逻辑不变。

2. 从第三方小程序返回时查询订单

在支付页面的 onShow 中读取待确认订单,然后向自己的服务端查询支付状态。

Page({
  onShow() {
    const result = my.getStorageSync({
      key: 'pendingPayment'
    });

    const pendingPayment = result && result.data;

    if (pendingPayment && pendingPayment.orderNo) {
      this.confirmPayment(pendingPayment.orderNo);
    }
  },

  confirmPayment(orderNo) {
    if (this.paymentChecking) {
      return;
    }

    this.paymentChecking = true;

    my.request({
      url: `https://example.com/api/orders/${encodeURIComponent(orderNo)}/payment-status`,
      method: 'GET',
      success: (response) => {
        const paymentStatus = response.data && response.data.status;

        if (paymentStatus === 'PAID') {
          my.removeStorageSync({
            key: 'pendingPayment'
          });

          this.handlePaymentSuccess(orderNo);
          return;
        }

        if (paymentStatus === 'CLOSED') {
          my.removeStorageSync({
            key: 'pendingPayment'
          });

          my.showToast({
            content: '订单已关闭'
          });
          return;
        }

        // PAYING、UNKNOWN 等非终态暂时保留,后续进入页面时继续确认
      },
      complete: () => {
        this.paymentChecking = false;
      }
    });
  },

  handlePaymentSuccess(orderNo) {
    my.showToast({
      content: '支付成功'
    });

    // 刷新订单、跳转结果页等
    my.redirectTo({
      url: `/pages/payment-result/payment-result?orderNo=${encodeURIComponent(orderNo)}`
    });
  }
});

如果返回后不一定进入原支付页面,也可以在 App.onShow 中检查待确认订单,再通知对应页面刷新。持久化时只保存订单号,不要保存页面实例或回调函数。

3. 由服务端确认支付宝交易状态

服务端查询接口不能直接返回客户端上报的状态。它应先查询本地订单,必要时再调用支付宝交易查询接口,例如 alipay.trade.query。

async function getPaymentStatus(req, res) {
  const { orderNo } = req.params;
  const order = await orderRepository.findByOrderNo(orderNo);

  if (!order) {
    return res.status(404).json({
      status: 'NOT_FOUND'
    });
  }

  if (order.paymentStatus === 'PAID') {
    return res.json({
      status: 'PAID'
    });
  }

  const queryResult = await alipayClient.exec('alipay.trade.query', {
    bizContent: {
      out_trade_no: orderNo
    }
  });

  const paid =
    queryResult.tradeStatus === 'TRADE_SUCCESS' ||
    queryResult.tradeStatus === 'TRADE_FINISHED';

  if (paid) {
    await orderService.markPaidIdempotently({
      orderNo,
      tradeNo: queryResult.tradeNo
    });

    return res.json({
      status: 'PAID'
    });
  }

  return res.json({
    status: queryResult.tradeStatus || 'UNKNOWN'
  });
}

不同语言和 SDK 版本使用的方法名、字段名可能不同,具体写法应以当前接入的支付宝 SDK 文档为准。服务端应使用 out_trade_no 或支付宝交易号查询真实状态,不能让客户端自行声明支付成功。

优先使用支付异步通知

如果接入的支付产品支持服务端异步通知,应优先通过异步通知更新订单状态:

  1. 服务端接收支付通知。
  2. 验证通知签名。
  3. 校验商户号、应用、订单号、金额、币种等字段。
  4. 幂等更新订单状态。
  5. 客户端从广告页返回后,只查询服务端保存的订单状态。

alipay.trade.query 适合用于补偿查询,例如异步通知尚未到达、客户端主动刷新或定时对账。不要在每次 onShow 时无限制地高频调用。

注意事项

  • onShow 只表示小程序重新进入前台,不能说明支付成功。
  • 从广告服务助手返回后,不要直接发货或开通权益。
  • 不能只判断 my.tradePay 是否进入 success 回调。还要检查具体结果,并以服务端状态为准。
  • 查询接口和订单更新必须幂等,避免支付回调、异步通知和主动查询同时到达时重复发货。
  • 订单处于”处理中”时,不要立即删除本地待确认记录。可以稍后重试,但要限制重试次数并设置合理的时间间隔。
  • 订单支付成功或明确关闭后,应清除本地待确认记录。
  • 本地缓存可能被清理,因此服务端还应支持用户通过订单列表重新查询支付结果。
  • 如果广告页跳转由某个营销产品或支付配置触发,能否关闭取决于对应产品的能力。即使可以关闭,也应保留支付状态恢复机制。

备注:内容仅供参考。