PAYATHON 2026

支付宝面对面支付接口付款后二维码不跳转是什么原因?

支付小周

结论

支付宝面对面支付完成后,二维码页面没有自动跳转,通常并不代表支付失败,而是网站页面尚未获取支付状态。

alipay.trade.precreate 只负责生成二维码,不会控制浏览器跳转。用户扫码付款后,商户系统需要接收支付宝的异步通知,或主动调用 alipay.trade.query 查询订单状态。确认支付成功后,再由前端跳转到成功页。

常见原因

1. 只生成二维码,没有检测支付结果

如果页面只是调用 alipay.trade.precreate 获取 qr_code 并展示二维码,付款后页面不会自行变化。二维码只是支付入口,无法通知当前网页执行跳转。

用户用手机扫描电脑上的二维码时,付款操作发生在手机支付宝中,电脑浏览器无法直接得知支付已经完成。

2. notify_url 未配置或无法访问

支付成功后,支付宝可以向商户配置的 notify_url 发送异步通知。以下情况可能导致通知失败:

  • 没有设置 notify_url
  • 地址只能从内网访问
  • 域名、HTTPS 证书或网络配置异常
  • 接口被登录验证、CSRF 校验或防火墙拦截
  • 通知接口返回的内容不符合支付宝要求
  • 接口处理时间过长,支付宝没有及时收到响应

异步通知发送给商户服务器,不会直接触发用户浏览器跳转。服务器收到通知后,还要更新本地订单状态。

3. 前端没有轮询或实时通信机制

即使服务器已经收到支付通知,如果二维码页面不再查询本地订单状态,页面仍然不会自动跳转。

常见的处理方式包括:

  • 前端定时轮询商户订单查询接口
  • WebSocket
  • Server-Sent Events(SSE)

普通扫码支付页面通常使用短时间轮询,实现起来相对简单。

4. 把 return_url 当成二维码页面的跳转机制

面对面支付预创建交易使用的主要接口是 alipay.trade.precreate。扫码付款发生在支付宝客户端,不能直接依靠网页支付中的同步返回机制控制原二维码页面。

某个参数是否受支持,应以当前使用的支付宝接口文档和 SDK 定义为准。仅设置 return_url,并不能保证电脑端二维码页面自动跳转。

5. 支付状态判断错误

调用 alipay.trade.query 后,需要根据 trade_status 判断交易结果。常见状态如下:

  • WAIT_BUYER_PAY:等待买家付款
  • TRADE_SUCCESS:交易支付成功
  • TRADE_FINISHED:交易结束
  • TRADE_CLOSED:交易关闭

通常只有检测到 TRADE_SUCCESS 或 TRADE_FINISHED 后,页面才跳转到成功页。实际业务中还要核对订单金额、商户订单号,并结合退款规则处理。

6. 商户订单号不一致

创建二维码、接收通知和查询订单时使用的 out_trade_no,必须指向同一笔商户订单。

如果前端查询了错误的订单号,或重新生成二维码时更换了 out_trade_no,就可能出现支付宝已经扣款,但页面始终显示等待支付的情况。

推荐实现流程

完整的扫码支付流程可以按以下方式处理:

  1. 商户服务器生成唯一的 out_trade_no。
  2. 调用 alipay.trade.precreate 创建交易并获取 qr_code。
  3. 前端展示二维码,同时定时查询商户服务器中的订单状态。
  4. 支付宝向 notify_url 发送异步通知。
  5. 商户服务器验签并核对订单信息。
  6. 商户服务器把本地订单更新为已支付。
  7. 前端查询到已支付状态后停止轮询,并跳转到成功页。
  8. 如果异步通知暂时没有到达,服务器可以调用 alipay.trade.query 主动确认。

前端轮询示例

<div id="payment-status">等待支付</div>

<script>
const outTradeNo = "ORDER_202609090001";
let checking = false;

const timer = setInterval(async () => {
  if (checking) {
    return;
  }

  checking = true;

  try {
    const response = await fetch(
      `/api/payment/status?out_trade_no=${encodeURIComponent(outTradeNo)}`,
      {
        method: "GET",
        cache: "no-store"
      }
    );

    if (!response.ok) {
      return;
    }

    const result = await response.json();

    if (result.status === "paid") {
      clearInterval(timer);
      document.getElementById("payment-status").textContent = "支付成功";
      window.location.replace(
        `/payment/success?out_trade_no=${encodeURIComponent(outTradeNo)}`
      );
    } else if (result.status === "closed") {
      clearInterval(timer);
      document.getElementById("payment-status").textContent = "订单已关闭";
    }
  } catch (error) {
    console.error("查询支付状态失败", error);
  } finally {
    checking = false;
  }
}, 2000);
</script>

轮询间隔没有适用于所有系统的固定值。可以先设置为 2~5 秒,再根据接口限流、并发量和用户体验调整,同时设置最长查询时间。

服务端状态查询示例

以下代码仅用于演示处理逻辑。支付宝 SDK 的类名和调用方式可能随语言及 SDK 版本变化,请以实际安装的官方 SDK 为准。

app.get("/api/payment/status", async (req, res) => {
  const outTradeNo = req.query.out_trade_no;

  // 必须验证当前用户是否有权查询这笔订单
  const order = await orderRepository.findByOutTradeNo(outTradeNo);

  if (!order) {
    return res.status(404).json({ status: "not_found" });
  }

  if (order.paymentStatus === "paid") {
    return res.json({ status: "paid" });
  }

  if (order.paymentStatus === "closed") {
    return res.json({ status: "closed" });
  }

  // 可按业务需要调用 alipay.trade.query 主动确认
  const trade = await queryAlipayTrade({
    out_trade_no: outTradeNo
  });

  if (
    trade.trade_status === "TRADE_SUCCESS" ||
    trade.trade_status === "TRADE_FINISHED"
  ) {
    await orderRepository.markPaid(outTradeNo, trade.trade_no);
    return res.json({ status: "paid" });
  }

  if (trade.trade_status === "TRADE_CLOSED") {
    await orderRepository.markClosed(outTradeNo);
    return res.json({ status: "closed" });
  }

  return res.json({ status: "waiting" });
});

异步通知处理要点

通知接口不能只看到“支付成功”字段就直接发货,至少需要完成以下检查:

1. 使用支付宝公钥验证通知签名
2. 检查 app_id 是否属于当前商户应用
3. 检查 out_trade_no 是否存在
4. 检查 total_amount 是否与本地订单金额一致
5. 检查 seller_id 等商户信息是否符合预期
6. 确认 trade_status 为可接受的支付成功状态
7. 以幂等方式更新订单
8. 处理成功后返回纯文本 success

示意代码如下:

app.post("/api/alipay/notify", async (req, res) => {
  const params = req.body;

  const verified = verifyAlipayNotification(params);

  if (!verified) {
    return res.send("failure");
  }

  const order = await orderRepository.findByOutTradeNo(
    params.out_trade_no
  );

  if (!order) {
    return res.send("failure");
  }

  if (String(order.totalAmount) !== String(params.total_amount)) {
    return res.send("failure");
  }

  if (
    params.trade_status === "TRADE_SUCCESS" ||
    params.trade_status === "TRADE_FINISHED"
  ) {
    // 必须幂等:重复通知不能重复发货、重复入账
    await orderRepository.markPaid(
      params.out_trade_no,
      params.trade_no
    );
  }

  return res.type("text/plain").send("success");
});

verifyAlipayNotification 不能用自行编写的字符串比较代替。必须使用支付宝 SDK 提供的验签功能,并正确配置支付宝公钥。具体方法名取决于使用的语言和 SDK 版本。

排查步骤

可以按照以下顺序排查:

  1. 在支付宝商户后台或服务端日志中确认交易是否确实成功。
  2. 使用 out_trade_no 调用 alipay.trade.query,检查 trade_status。
  3. 检查创建交易时是否配置了正确的 notify_url。
  4. 查看服务器访问日志,确认支付宝是否请求过通知接口。
  5. 检查异步通知是否通过验签,金额是否一致。
  6. 确认通知接口最后返回的是纯文本 success。
  7. 检查本地订单是否已更新为已支付。
  8. 在浏览器开发者工具中查看状态查询请求是否持续发送。
  9. 检查前端判断条件和跳转代码是否执行。
  10. 确认前端查询的 out_trade_no 与创建二维码时使用的值完全一致。

注意事项

  • 不要根据前端传来的“支付成功”结果直接发货,最终状态必须由服务器确认。
  • 支付宝可能重复发送异步通知,因此订单更新和发货逻辑必须支持幂等。
  • 前端跳转只影响用户体验,不能作为交易成功的唯一凭据。
  • 不要把支付宝私钥、公钥配置或完整通知参数输出到前端。
  • 主动查询支付宝接口时需要控制频率,避免每次浏览器轮询都无条件调用支付宝。
  • 更稳妥的方式是先查询本地订单。只有本地状态尚未更新时,才按一定频率调用 alipay.trade.query。
  • 用户关闭页面、网络断开或跳转失败,都不应影响服务端更新订单和履行业务。

备注:内容仅供参考。