PAYATHON 2026

支付宝跳转至支付二维码页面时为何出现白屏报警?

支付老李

结论

支付宝跳转到支付二维码页面后出现白屏报警,不一定是支付宝页面出了故障。常见原因可以分为三类:

  1. 支付接口返回异常,前端没有拿到有效的支付链接或表单;
  2. 浏览器、WebView、安全策略或网络环境拦截了跳转;
  3. 页面已经正常跳转,但白屏监控在页面卸载、切换到后台或支付宝接管页面时产生了误报。

排查的第一步是确认白屏具体发生在哪个页面,然后按照“下单接口 → 支付地址 → 浏览器跳转 → 支付宝页面响应”的顺序逐段检查。结合服务端支付请求日志、浏览器 Network 记录和前端异常日志,通常可以定位出问题所在。

先确认白屏发生在哪里

不要看到白屏就直接归因于“支付宝跳转”。先记录以下信息:

  • 白屏页面的完整 URL、域名和协议;
  • 白屏前最后一个成功请求;
  • 页面是否已经离开商户域名;
  • 在支付宝客户端和系统浏览器中是否都能复现;
  • Android、iOS 和 PC 浏览器的表现是否一致;
  • 用户是否真的看到了白屏,还是只有监控平台产生报警;
  • 支付订单是否创建成功,支付宝侧是否生成了有效交易号。

如果白屏时地址栏仍显示商户域名,应重点检查商户前端和服务端。如果页面已经进入支付宝域名,则继续核对支付参数、访问方式、网络情况和客户端兼容性。

常见原因

支付接口返回错误

支付接口可能返回业务错误、签名错误、参数缺失或参数过期。业务代码如果没有校验返回结果,仍然继续跳转,就可能打开空字符串、错误页面或不完整的 URL。

需要检查的内容通常包括:

  • app_id 是否属于当前环境;
  • 商户私钥与支付宝公钥是否匹配;
  • sign_type、字符集和签名原文是否一致;
  • out_trade_no 是否重复或格式异常;
  • 金额、订单标题等必填参数是否有效;
  • notify_url、return_url 是否可以访问;
  • 是否混用了沙箱环境和生产环境参数;
  • 是否完整记录了接口返回的 code、sub_code、sub_msg。

具体字段要以实际接入的支付宝产品和接口文档为准,不同支付产品的参数不能混用。

使用了不匹配的支付产品

二维码支付、手机网站支付、电脑网站支付和 App 支付的调用方式并不相同。移动端如果直接打开面向 PC 扫码场景的二维码页面,可能出现页面不兼容、无法拉起客户端或显示异常。

先确认业务实际使用的支付模式:

  • PC 页面展示二维码,通常使用电脑网站支付或当面付相关能力;
  • 手机浏览器跳转支付,通常使用手机网站支付;
  • 原生 App 内支付,应按照 App 支付流程调用客户端 SDK;
  • 商户主动生成二维码时,要确认二维码内容是合法的支付地址,而不是接口响应中的其他字段。

不能因为页面中显示了二维码,就直接判断所用的接口类型。

跳转地址为空、被截断或编码错误

支付地址经过 JSON、数据库、模板、URL 参数或第三方跳转页传递时,可能出现这些问题:

  • & 被转义为 &;
  • +、%、= 等字符被错误编码;
  • URL 被截断;
  • 字符串中混入 \0 等不可见字符;
  • 已编码的 URL 又被传给 encodeURIComponent();
  • HTML 表单内容被误当成 URL 跳转;
  • 前端读取了错误的响应字段。

参考问题中的“二维码”后面出现了不可见字符的迹象。如果该字符确实来自实际参数,而不只是文本展示问题,应重点排查字符串构造、数据库编码和接口序列化过程。

可以在跳转前输出字符串长度和字符编码,以便查找不可见字符:

function inspectString(value) {
  const text = String(value ?? '');

  console.log('length:', text.length);
  console.log(
    'codePoints:',
    Array.from(text).map(char =>
      `U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')}`
    )
  );
}

inspectString(paymentUrl);

发现空字符后,应先追查它的来源,不要直接删除字符后上线。直接清理字符可能掩盖服务端序列化错误或数据污染。

HTML 表单响应处理错误

部分网页支付流程返回的不是普通 URL,而是一段需要提交的 HTML 表单。如果前端把这段表单字符串赋给 window.location.href,页面无法按预期进入支付流程。

服务端生成表单后,可以将完整 HTML 直接返回给浏览器:

String form = alipayClient.pageExecute(request).getBody();

response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(form);
response.getWriter().flush();

如果前后端分离接口返回的是表单,前端需要将其写入受控容器并提交。innerHTML 存在安全风险,因此这里只能处理可信服务端生成的支付表单:

function submitPaymentForm(formHtml) {
  if (!formHtml || typeof formHtml !== 'string') {
    throw new Error('Payment form is empty');
  }

  const container = document.createElement('div');
  container.style.display = 'none';
  container.innerHTML = formHtml;
  document.body.appendChild(container);

  const form = container.querySelector('form');
  if (!form) {
    throw new Error('Payment form was not found');
  }

  form.submit();
}

如果接口返回的是支付 URL,应先完成校验,再执行跳转:

function redirectToPayment(paymentUrl) {
  if (!paymentUrl) {
    throw new Error('Payment URL is empty');
  }

  const url = new URL(paymentUrl, window.location.origin);

  if (url.protocol !== 'https:') {
    throw new Error(`Unsupported payment protocol: ${url.protocol}`);
  }

  window.location.assign(url.href);
}

允许使用哪些协议和域名,要根据实际接入方式配置,不能直接照搬示例并写死。

WebView 或内置浏览器限制

支付页可能运行在微信、企业应用、App 自建 WebView 或其他内置浏览器中。这些环境可能会限制:

  • 拉起外部应用;
  • 使用 alipays:// 等自定义 Scheme;
  • 打开新窗口;
  • 使用第三方 Cookie;
  • 通过 JavaScript 跳转;
  • 执行并非由用户点击直接触发的重定向;
  • 跨域下载或页面导航。

如果系统浏览器可以正常支付,但内置 WebView 出现白屏,通常要检查容器配置和跳转方式:

  • 是否实现了自定义 Scheme 的拦截与转发;
  • window.open() 是否被拦截;
  • 跳转是否必须由用户点击直接触发;
  • WebView 是否允许 JavaScript、DOM Storage 和外部导航;
  • 是否在当前 WebView 中加载了只能交给外部应用处理的地址。

不要把所有支付宝域名或 Scheme 无条件加入白名单。白名单范围应符合实际支付产品的接入方式和安全要求。

HTTPS、证书或网络异常

支付流程通常会经过多次 HTTPS 请求。以下问题都可能导致页面加载失败:

  • 商户页面混用了 HTTP 和 HTTPS 内容;
  • 证书过期、证书链不完整或证书域名不匹配;
  • DNS 解析异常;
  • 代理、VPN、防火墙或安全软件拦截请求;
  • 移动网络与 Wi-Fi 下的访问结果不同;
  • 页面依赖的脚本或接口超时;
  • CDN 节点异常。

检查浏览器开发者工具时,应重点留意:

  • net::ERR_CERT_*;
  • ERR_NAME_NOT_RESOLVED;
  • ERR_CONNECTION_RESET;
  • Mixed Content;
  • CSP;
  • 403、404、5xx;
  • 请求长时间停留在 Pending 状态。

CSP、跨域或页面安全策略拦截

如果商户页面设置了严格的内容安全策略,支付表单提交、页面跳转或相关资源加载都可能被阻止。例如:

Content-Security-Policy: default-src 'self'; form-action 'self'

其中的 form-action 'self' 限制表单只能提交到当前源。如果支付表单需要提交到外部支付域名,浏览器可能会将其拦截,并在控制台中报告 CSP 错误。

应按照实际接入地址配置范围尽可能小的允许列表,不要直接使用 *:

Content-Security-Policy: default-src 'self'; form-action 'self' https://允许的支付域名

允许的域名必须以当前支付宝接入文档和实际表单目标为准。

前端脚本异常

用户点击支付按钮后,前端需要获取支付地址、记录埋点、保存状态并执行跳转。如果其中任何一步发生 JavaScript 异常,页面也可能停在空白的中间页。

可以捕获以下错误:

window.addEventListener('error', event => {
  console.error('Global error:', {
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno
  });
});

window.addEventListener('unhandledrejection', event => {
  console.error('Unhandled promise rejection:', event.reason);
});

同时检查控制台中是否存在:

  • TypeError;
  • ReferenceError;
  • 动态资源加载失败;
  • JSON 解析失败;
  • 路由组件加载失败;
  • 接口响应结构与前端预期不一致。

白屏监控误报

进入支付流程时,页面可能切换到后台、触发 pagehide,或在卸载期间清空 DOM。某些白屏检测脚本如果正好在这时采样,就会把正常跳转误判为白屏。

可以记录页面生命周期来验证:

document.addEventListener('visibilitychange', () => {
  console.log('visibilitychange', {
    state: document.visibilityState,
    time: Date.now()
  });
});

window.addEventListener('pagehide', event => {
  console.log('pagehide', {
    persisted: event.persisted,
    time: Date.now()
  });
});

window.addEventListener('pageshow', event => {
  console.log('pageshow', {
    persisted: event.persisted,
    time: Date.now()
  });
});

如果报警前已经记录到合法的支付跳转,之后页面触发了 pagehide 或进入 hidden 状态,而且用户可以正常完成支付,就需要调整监控逻辑:

  • 支付跳转开始后暂停白屏检测;
  • 页面处于 hidden 状态时不执行白屏采样;
  • 触发 pagehide 后不再上报白屏;
  • 为支付中间页设置清晰的状态;
  • 将监控事件与真实用户反馈、截图和订单状态关联起来。

不要直接屏蔽所有支付页面的报警,否则可能漏掉真正的故障。

推荐排查步骤

1. 固定复现条件

记录设备、操作系统、浏览器或 WebView 版本、网络类型、入口页面和订单号。分别在系统浏览器、支付宝客户端内页面和业务 App WebView 中测试,确认问题是否只出现在某个特定环境。

2. 检查服务端支付日志

使用 out_trade_no 或内部请求 ID 串联日志,并确认:

  • 创建订单的请求是否到达服务端;
  • 支付接口是否调用成功;
  • 原始响应中是否包含错误码;
  • 最终返回给前端的是 URL 还是 HTML 表单;
  • 返回内容是否为空、被截断或包含不可见字符。

日志中不要记录商户私钥、完整签名、用户身份信息或其他敏感参数。

3. 检查浏览器 Network

打开开发者工具,启用 Preserve log,然后重新执行支付操作。按顺序确认:

  1. 下单接口是否返回 2xx;
  2. 响应体是否符合约定;
  3. 浏览器是否发起了预期的导航或表单提交;
  4. 中途是否发生 3xx 重定向;
  5. 最终失败请求的状态码和响应内容;
  6. 控制台中是否同时出现 CSP、证书、跨域或脚本错误。

4. 对比原始值与跳转值

分别记录:

  • 支付接口的原始响应;
  • 服务端解析后的字段;
  • 前端收到的字段;
  • 执行跳转前的最终字符串。

对比这四个值。如果内容不一致,就可以进一步确定转义、截断或错误解析发生在哪一层。

5. 绕过业务页面验证

在安全测试环境中,使用同一笔测试订单产生的合法支付输出直接验证:

  • 原始支付表单能否正常提交;
  • 原始支付 URL 能否在系统浏览器中打开;
  • 绕过业务中间页后是否仍然出现白屏。

如果直接访问可以正常完成支付,问题多半出在业务前端、中间页或 WebView。如果直接访问仍然失败,则需要继续检查支付接口响应和网络环境。

6. 核对监控时间线

将白屏报警时间与以下事件逐一对齐:

  • 用户点击支付;
  • 下单接口完成;
  • 开始跳转;
  • visibilitychange;
  • pagehide;
  • 支付结果通知;
  • 用户返回商户页面。

真实白屏通常还会伴随资源加载失败、脚本异常或错误响应。误报则往往紧跟在正常的页面卸载或客户端拉起之后。

注意事项

  • 不要因为页面空白就反复创建订单,以免产生重复支付风险。
  • 支付是否成功,应以服务端异步通知和主动查询结果为准,不能只看前端返回页面。
  • 排查签名问题时,可以记录参与签名的字段名和脱敏摘要,但不要记录私钥或完整敏感数据。
  • 不要通过关闭 HTTPS 校验、完全放开 CSP 或信任所有域名来处理白屏问题。
  • 自定义 Scheme 拉起失败时,应提供清晰的浏览器支付入口或操作提示,避免用户停留在空白中间页。
  • 涉及支付宝接口字段、域名和客户端行为时,应以当前接入产品的官方文档为准。不同产品、环境和客户端可能采用不同的处理方式。

备注:内容仅供参考。