PAYATHON 2026

网页支付返回 from 后跳转支付宝一直显示加载中

支付小周

结论

这种情况通常不是支付宝页面本身一直在加载,而是支付跳转流程没有走完。建议优先检查以下几项:

  1. 服务端返回的内容是否为支付宝 SDK 生成的完整支付表单。
  2. 前端是否提交了这份表单,而不是通过 Ajax 请求支付接口后就结束了。
  3. 支付表单是否经过转义、被截断或嵌套在另一个表单中。
  4. return_url、notify_url 和支付网关地址能否正常访问。
  5. 页面是否运行在微信、企业应用内置 WebView 等受限浏览器中。

如果问题中的 from 实际是 form,应重点检查支付表单的渲染和自动提交。如果 from 是业务页面的来源参数,则要确认支付返回后是否触发了循环跳转。

常见原因

支付表单没有真正提交

网页支付接口通常返回一段带有隐藏字段的 HTML <form>,浏览器通过 POST 或自动提交进入支付宝收银台。如果前端只把这段内容当作普通字符串接收,或者只是通过 Ajax 调用后端,浏览器不会自动跳转到支付页面。

错误示例:

fetch('/api/create-payment', {
  method: 'POST'
}).then(response => response.json());

Ajax 适合获取数据,但不能直接完成第三方收银台跳转。

返回内容被前端框架转义

如果服务端返回的表单被 Vue、React 或模板引擎当作普通文本处理,页面可能一直显示加载动画、出现空白,或者直接显示一串 HTML 字符。

还需要确认支付表单是否经过以下处理:

  • HTML 转义
  • JSON 序列化
  • URL 编码
  • 富文本过滤
  • CSP 或安全组件拦截
  • 将完整 <form> 插入另一个 <form>

这些处理可能破坏签名字段或自动提交脚本。

返回页面发生循环跳转

如果用户通过 return_url 回到业务页面后,页面初始化逻辑又调用了一次支付接口,就会形成下面的循环:

业务页 → 支付宝 → return_url → 再次发起支付 → 支付宝

部分浏览器遇到这种情况时只会一直显示加载状态,不一定明确提示重定向次数过多。

内置浏览器限制了支付跳转

微信、部分 App WebView 和企业应用内置浏览器可能会限制:

  • 跨域表单提交
  • 自动执行脚本
  • 打开支付宝客户端
  • 自定义 URL Scheme
  • 未由用户操作触发的页面跳转

可以先在系统浏览器中测试。如果系统浏览器可以正常支付,而内置 WebView 无法完成跳转,问题通常出在容器策略或唤起方式上,而不是订单参数。

支付环境或网关地址不匹配

沙箱应用、正式应用和各自对应的网关必须匹配。如果应用配置、密钥、签名算法或网关混用,页面可能无法正常进入收银台。

网关地址应根据当前使用的支付宝 SDK 或接入文档确定,不要在前端自行拼接支付参数。

排查步骤

1. 检查浏览器网络请求

打开开发者工具的 Network 面板,重新发起支付并确认:

  • 创建支付订单的接口是否返回 200
  • 响应内容是 JSON、HTML 表单还是重定向
  • 提交表单后是否请求了支付宝域名
  • 是否出现 301、302 循环
  • 是否存在 4xx、5xx 或 net::ERR_*
  • 最后一个请求停在哪个地址

如果创建订单接口已经成功,但后续没有出现对支付宝域名的请求,通常说明支付表单没有执行。

2. 查看服务端原始响应

支付宝 SDK 返回表单后,服务端应直接输出表单内容。除非前端明确负责创建并提交表单,否则不要先把它包装成 JSON。

以 PHP 为例,具体方法名以实际使用的 SDK 为准:

<?php

$form = $alipayClient->pageExecute($request);

header('Content-Type: text/html; charset=UTF-8');
echo $form;
exit;

如果接口必须返回 JSON,可以把表单作为字符串返回,再由前端将其写入独立页面并提交,但要确认内容没有被转义或过滤:

async function startPayment() {
  const response = await fetch('/api/create-payment', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      orderNo: 'ORDER_123'
    })
  });

  const result = await response.json();

  if (!result.form) {
    throw new Error('支付接口未返回 form');
  }

  const container = document.createElement('div');
  container.innerHTML = result.form;
  document.body.appendChild(container);

  const form = container.querySelector('form');
  if (!form) {
    throw new Error('返回内容中没有支付表单');
  }

  form.submit();
}

只有在响应来自可信服务端,并且能够确认表单由支付宝 SDK 生成时,才适合使用 innerHTML。不要把用户可控内容直接写入页面。

更稳妥的做法是让浏览器通过普通页面跳转进入后端支付入口:

window.location.href = `/payment/start?orderNo=${encodeURIComponent(orderNo)}`;

后端收到请求后创建支付请求,并直接输出支付宝 SDK 生成的表单。

3. 检查是否重复嵌套表单

下面的 HTML 结构无效:

<form id="business-form">
  <!-- 支付宝 SDK 返回的 form 被插入到了这里 -->
  <form method="post" action="https://example.com">
  </form>
</form>

HTML 不允许嵌套 <form>。浏览器可能会自动修正 DOM,造成支付字段丢失或提交到错误的地址。支付宝支付表单应放在现有业务表单之外。

4. 检查回跳逻辑

回跳页面应先读取订单号,然后向服务端查询订单状态。不要在页面加载时直接再次发起支付:

async function handlePaymentReturn(orderNo) {
  const response = await fetch(
    `/api/payment/status?orderNo=${encodeURIComponent(orderNo)}`
  );
  const result = await response.json();

  if (result.status === 'PAID') {
    window.location.replace('/payment/success');
    return;
  }

  if (result.status === 'PENDING') {
    showPendingMessage();
    return;
  }

  showPaymentFailedMessage();
}

判断支付结果时应以服务端保存的订单状态为准。浏览器跳回 return_url 只能说明用户回到了业务页面,并不代表支付一定成功。

5. 检查服务端异步通知

支付结果应主要通过支付宝异步通知确认。服务端收到通知后,需要按照当前接入协议完成:

  • 验签
  • 校验应用标识
  • 校验订单号
  • 校验金额和币种
  • 校验商户身份
  • 幂等更新订单状态
  • 返回协议要求的响应内容

不要仅凭前端回跳参数修改订单状态,也不要因为回跳页面仍在加载就重复创建新订单。

6. 排除浏览器环境问题

可以按以下顺序测试:

  1. 使用 Chrome、Safari 等系统浏览器。
  2. 使用无痕模式,或清除站点缓存后重试。
  3. 关闭广告拦截、脚本拦截类扩展。
  4. 更换网络环境。
  5. 确认问题是否只出现在微信或某个 App WebView 中。
  6. 检查 WebView 是否允许 JavaScript、跨域导航和打开外部应用。

如果问题只出现在某个 WebView 中,应按照该容器和支付宝当前的接入规范处理,例如提示用户通过系统浏览器打开。具体支持方式取决于支付产品、浏览器环境和接入方案,不能只靠前端强制唤起。

from 参数导致循环跳转时的处理

如果业务中确实有名为 from 的查询参数,不要仅根据该参数无条件发起支付:

const params = new URLSearchParams(window.location.search);
const source = params.get('from');
const returned = params.get('payment_returned') === '1';

if (source === 'checkout' && !returned) {
  startPayment();
}

也可以将支付回跳地址设置为专用结果页:

https://merchant.example.com/payment/return

结果页只查询并展示订单状态,不执行结算页中的自动支付逻辑。这样能避免用户回跳后再次进入支付流程。

注意事项

  • 不要修改支付宝 SDK 生成的签名字段、字段顺序或字符编码。
  • 支付参数和私钥必须保存在服务端,不能写入前端代码。
  • return_url 负责页面跳转,异步通知地址负责可靠地更新订单状态,两者用途不同。
  • 跳转前关闭页面加载遮罩并不能解决问题。应通过 Network 面板确认请求具体停在哪一步。
  • 不要对同一订单反复创建支付请求。重新支付前,应先查询订单状态。
  • 建议记录商户订单号、支付请求时间、回跳地址、通知时间和验签结果。排查日志中不要输出私钥或完整的敏感信息。
  • 如果支付宝页面已经打开,但页面内部长期停留在加载状态,应保留订单号、请求时间、浏览器和网络信息,再结合服务端日志及支付宝侧可查询的调用记录定位问题。仅凭”加载中”这一现象,无法判断具体原因是参数错误、网络问题还是浏览器兼容问题。

备注:内容仅供参考。