PAYATHON 2026

支付宝小程序支付成功后未收到success回调如何解决

支付阿杰

结论

不要把 my.tradePay 的 success 回调当作支付成功的唯一依据。支付完成后,如果用户跳转到“优选好货”等其他小程序,原支付页面可能进入后台或被重新创建,相关 JavaScript 上下文也可能已经销毁,导致客户端回调无法执行。

建议按以下方式处理:

  1. 服务端接收支付宝异步支付通知,并更新订单状态。
  2. 小程序在 onShow 中向业务服务端重新查询订单状态。
  3. my.tradePay 回调仅用于改善交互,不能作为最终支付凭证。
  4. 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 可能多次触发,订单查询和页面跳转都需要防止重复执行。

备注:内容仅供参考。