PAYATHON 2026

支付宝小程序支付成功后点击完成出现异常跳转或关闭

支付老李

结论

支付宝收银台的“完成”按钮通常没有单独的点击事件可供监听。小程序通过 my.tradePay 发起支付后,收银台返回小程序时会触发 success、fail 或 complete 回调。支付是否最终成功,应以服务端异步通知和主动查单结果为准。

支付后有时跳到淘宝页面,有时直接关闭小程序,通常不是同一个支付回调随机产生了不同结果,而可能与以下情况有关:

  • 扫码入口、外部页面或收银台的返回链路不同。
  • 支付完成后,小程序执行了 navigateBack、reLaunch 或关闭页面等跳转逻辑。
  • 小程序通过临时扫码会话进入,返回时页面栈中没有可以恢复的上一页。
  • 实际链路不只是小程序支付,还经过了 H5、中间页、短链接或其他应用页面。
  • 二维码内容、订单返回地址,或服务端创建交易时使用的应用身份不一致。
  • 淘宝页面可能是收银台结果页中的运营入口,用户点击的并不是单纯的“完成”按钮。仅凭页面现象无法确认,需要结合录屏和日志判断。

“完成”后如何回调

典型的小程序支付流程如下:

  1. 服务端创建支付宝交易并取得 trade_no。
  2. 小程序调用 my.tradePay。
  3. 支付宝收银台显示支付页面。
  4. 用户完成支付,然后关闭收银台或点击完成。
  5. 收银台将控制权交还给调用方,my.tradePay 返回结果。
  6. 支付宝另行向商户服务端发送异步支付通知。

示例:

my.tradePay({
  tradeNO: tradeNo,

  success: (res) => {
    const resultCode = String(res.resultCode || '');

    if (resultCode === '9000') {
      // 这里只能表示客户端收到“支付成功”结果,
      // 页面可进入“确认支付结果”状态,然后向服务端查单。
      this.confirmPayment(orderId);
      return;
    }

    if (resultCode === '8000') {
      // 结果处理中,应向服务端查询订单状态。
      this.confirmPayment(orderId);
      return;
    }

    if (resultCode === '6001') {
      my.showToast({
        content: '支付已取消'
      });
      return;
    }

    my.showToast({
      content: `支付未完成:${resultCode}`
    });
  },

  fail: (error) => {
    console.error('my.tradePay fail', error);

    // fail 也不能直接认定订单未支付,
    // 网络中断时用户仍可能已经完成付款。
    this.confirmPayment(orderId);
  },

  complete: (res) => {
    // 无论调用成功或失败都可能进入 complete。
    // 它不是“用户点击完成”的专用事件。
    console.log('my.tradePay complete', res);
  }
});

小程序应通过自己的服务端确认订单状态:

confirmPayment(orderId) {
  my.request({
    url: 'https://example.com/api/orders/payment-status',
    method: 'GET',
    data: {
      orderId
    },
    success: ({ data }) => {
      if (data.status === 'PAID') {
        my.redirectTo({
          url: `/pages/pay-result/pay-result?orderId=${orderId}`
        });
      } else {
        my.showToast({
          content: '支付结果确认中'
        });
      }
    },
    fail: (error) => {
      console.error('query payment status failed', error);
    }
  });
}

不要在 complete 中直接返回或关闭页面,例如:

complete: () => {
  // 不建议:支付失败、取消或异常时同样可能执行
  my.navigateBack();
}

为什么第一次跳到淘宝页面

先检查从扫码到支付之间是否经过了外部页面。

如果二维码打开的是 H5、短链接或其他应用页面,再由该页面唤起小程序,收银台结束后就可能返回调用链中的外部页面。如果外部页面配置了淘宝链接、广告落地页或兜底跳转,用户看到的现象就是“点击完成后进入淘宝”。

还需要确认用户看到的“淘宝精品”是否为支付结果页中的推荐入口。建议录制完整操作过程,确认用户点击的具体位置,并对比跳转前后的支付宝标题栏、页面样式和右上角菜单信息。

同时检查以下内容:

  • 解码后的二维码原始内容是否确实为支付宝小程序码。
  • 两次扫码得到的二维码内容是否完全相同。
  • 链路中是否存在 302 重定向、短链接、H5 中间页或 Universal Link。
  • 创建订单所用的 app_id 是否与当前小程序及支付产品配置匹配。
  • 代码是否根据 scene、query 或订单状态执行了不同跳转。
  • 支付结果页是否包含淘宝商品、广告或其他外部链接。

如果同一套代码通过不同二维码进入时表现不同,应优先检查二维码入口和服务端订单参数,而不是只排查 my.tradePay。

为什么第二次直接关闭小程序

通过扫码打开的小程序可能处于一次性启动链路中。支付完成后,如果代码尝试返回上一页,而当前页面已经位于页面栈底部,用户看到的结果就可能是小程序退出或关闭。

常见代码包括:

my.navigateBack();

还要检查支付成功、页面 onShow 或订单状态变化后执行的条件跳转:

if (paid) {
  my.navigateBack();
}

重复支付时也可能进入不同的代码分支,例如:

  • 第一次订单状态为待支付,因此进入支付结果页。
  • 第二次发现订单已支付,代码直接返回。
  • 扫码参数被缓存,第二次启动仍在读取旧订单。
  • onShow 和支付回调同时执行了跳转。
  • success、complete 和查单回调分别触发跳转,导致连续返回。
  • 同一个按钮被重复绑定,或者 my.tradePay 被重复调用。

可以为跳转增加一次性保护:

Page({
  data: {
    handlingPaymentResult: false
  },

  handlePaid(orderId) {
    if (this.data.handlingPaymentResult) {
      return;
    }

    this.setData({
      handlingPaymentResult: true
    });

    my.redirectTo({
      url: `/pages/pay-result/pay-result?orderId=${orderId}`
    });
  }
});

支付结果页通常更适合使用 redirectTo,这样可以避免用户再次回到支付发起页。具体使用 redirectTo、navigateTo 还是 reLaunch,需要根据实际页面层级决定。页面栈状态不明确时,不要直接调用 navigateBack。

建议的排查步骤

1. 记录完整支付结果

至少记录以下信息:

  • 订单号和支付宝交易号。
  • my.tradePay 的返回值。
  • 发起支付的页面路径。
  • 扫码带入的参数。
  • 支付前后的页面栈。
  • 小程序 onShow、页面 onShow 和支付回调的执行顺序。
  • 每次跳转调用的目标地址。

调试时可以输出页面栈:

function logPageStack(stage) {
  const pages = getCurrentPages();

  console.log(stage, pages.map((page) => ({
    route: page.route
  })));
}

分别在发起支付、收到支付回调和页面重新显示时调用:

logPageStack('before tradePay');
logPageStack('tradePay success');
logPageStack('page onShow');

2. 全局检查跳转代码

搜索这些 API,并逐一检查调用位置:

my.navigateBack
my.navigateTo
my.redirectTo
my.reLaunch
my.switchTab

重点查看 success、complete、页面 onShow、定时器和订单轮询中是否存在重复跳转。

3. 对比两次扫码的启动参数

确认两次扫码是否进入同一页面,是否携带相同的 scene 和订单参数,同时检查第二次扫码是否错误复用了第一次支付的订单号。

启动参数的具体字段可能因入口类型和基础库能力而不同,应以实际日志和当前支付宝小程序文档为准,不能只根据页面表现判断入口。

4. 核对服务端交易信息

服务端应保存并核对:

  • 商户订单号。
  • 支付宝交易号。
  • 创建交易时使用的应用身份。
  • 订单金额。
  • 异步通知处理结果。
  • 主动查单结果。

同一个商户订单号通常不应作为全新订单重复支付。用户再次扫码时,应先查询订单状态。订单已经支付,就直接展示支付结果,不要再次调起收银台。

注意事项

客户端返回 resultCode === '9000',不能作为发货或记账的唯一依据。客户端可能断网或退出,回调也可能丢失,还可能出现结果处理中等状态。服务端需要验签支付宝的异步通知,必要时再调用支付宝交易查询接口确认结果。

“点击完成后跳到哪里”还会受到支付宝客户端版本、扫码入口、页面栈和支付产品接入方式的影响。如果缺少二维码原始内容、my.tradePay 返回日志、页面跳转日志和服务端交易参数,就无法判断淘宝页面由哪一方产生。分别记录两次操作的完整链路,才能确认跳转发生在支付宝收银台内部、小程序代码中,还是扫码前经过的外部页面中。

备注:内容仅供参考。