PAYATHON 2026

支付宝沙箱支付页面打开后为什么显示空白?

支付小周

结论

支付宝沙箱支付页突然空白,常见原因主要有以下几类:

  • 服务端没有正确生成或返回支付宝表单 HTML;
  • 请求参数、应用私钥、公钥或网关环境发生了变化;
  • 支付页面被放在 iframe 中,或被浏览器安全策略、插件拦截;
  • 网络、DNS、代理、HTTPS 证书或系统时间异常;
  • 支付宝沙箱环境暂时不可用。

排查前先确定空白页来自哪里:浏览器收到的是自己项目返回的空内容,还是页面跳转到支付宝域名后才变成空白。这两种情况的排查方向不同。

第一步:确认空白页面属于哪一端

打开浏览器开发者工具,在 Network 面板中找到支付请求,检查最终页面的 URL、HTTP 状态码和响应内容。

仍停留在自己的项目域名

例如页面地址仍是:

http://localhost:8080/pay

如果地址是本地项目或自己的业务域名,问题多半在服务端。需要检查:

  • 是否成功调用 pageExecute;
  • 返回的支付表单字符串是否为空;
  • Controller 是否把表单当成普通视图名称返回;
  • 是否存在未处理的异常;
  • 页面响应的 Content-Type 和字符编码是否正确。

已跳转到支付宝沙箱域名

如果 URL 已经变成支付宝沙箱网关或收银台域名,继续检查:

  • Network 中是否出现 4xx、5xx,或有请求被取消;
  • Console 中是否有 CSP、跨域、证书或脚本加载错误;
  • 当前是否使用代理、VPN、广告拦截插件或公司网络;
  • 无痕窗口或其他浏览器能否正常打开;
  • 沙箱网关当前能否正常访问。

如果更换浏览器和网络后,空白页仍只出现在支付宝域名下,问题才更可能来自沙箱环境。此时应保存请求时间、out_trade_no、网关响应和错误截图,稍后再试。

检查服务端生成的支付表单

支付宝网页支付通常由服务端生成一段自动提交的 HTML 表单,再将表单原样返回给浏览器。以 Java SDK 为例:

@GetMapping(value = "/pay", produces = "text/html;charset=UTF-8")
@ResponseBody
public String pay() throws AlipayApiException {
    AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

    request.setReturnUrl("https://example.com/pay/return");
    request.setNotifyUrl("https://example.com/pay/notify");

    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", "ORDER_202609090001");
    bizContent.put("total_amount", "0.01");
    bizContent.put("subject", "测试订单");
    bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");

    request.setBizContent(bizContent.toJSONString());

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

    if (form == null || form.isBlank()) {
        throw new IllegalStateException("支付宝支付表单生成失败");
    }

    return form;
}

常见错误之一是漏写 @ResponseBody。下面这种写法可能导致 Spring MVC 把返回值当成视图名称,而不是 HTML 内容:

@GetMapping("/pay")
public String pay() {
    return alipayClient.pageExecute(request).getBody();
}

可以添加 @ResponseBody,也可以使用 ResponseEntity 明确返回 HTML:

@GetMapping("/pay")
public ResponseEntity<String> pay() throws AlipayApiException {
    String form = alipayClient.pageExecute(request).getBody();

    return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType("text/html;charset=UTF-8"))
            .body(form);
}

调试时可以记录表单是否生成成功,但不要把应用私钥、完整签名或其他敏感配置写入日志。

核对沙箱配置

项目重启后,需要确认程序实际加载的仍是沙箱配置,而不是其他环境的配置文件。

典型配置如下:

AlipayClient alipayClient = new DefaultAlipayClient(
        "https://openapi-sandbox.dl.alipaydev.com/gateway.do",
        appId,
        merchantPrivateKey,
        "json",
        "UTF-8",
        alipayPublicKey,
        "RSA2"
);

沙箱网关的具体地址应以当前支付宝开放平台控制台或官方文档为准,不能只依据旧代码中的地址。

逐项检查以下配置:

  • appId 是否属于当前沙箱应用;
  • 应用私钥是否与当前应用公钥配套;
  • 配置的是否为”支付宝公钥”,而不是应用公钥;
  • sign_type 是否与密钥配置一致;
  • 网关是否确实指向沙箱环境;
  • 启动时激活的 Spring Profile 是否正确;
  • 环境变量是否缺失,或被正式环境配置覆盖;
  • 私钥的换行和头尾标记是否在复制时损坏。

同时检查 pageExecute 是否抛出 AlipayApiException,不要吞掉异常后只返回一个空白页:

try {
    return alipayClient.pageExecute(request).getBody();
} catch (AlipayApiException e) {
    log.error(
        "生成支付宝支付页面失败,code={}, subCode={}, message={}",
        e.getErrCode(),
        e.getErrCode(),
        e.getMessage()
    );
    throw e;
}

不同 SDK 版本提供的异常字段可能不同,应以项目实际使用的 SDK API 为准。

检查业务参数

表单可以生成,不代表业务参数一定有效。需要重点检查:

{
  "out_trade_no": "ORDER_202609090001",
  "total_amount": "0.01",
  "subject": "测试订单",
  "product_code": "FAST_INSTANT_TRADE_PAY"
}

注意以下几点:

  • out_trade_no 应保持唯一;
  • 不要重复使用已经支付成功或状态异常的订单号;
  • total_amount 应是合法的金额字符串;
  • subject 不能为空;
  • 电脑网站支付通常需要正确设置 product_code;
  • 参数应放在 biz_content 中,并使用正确的 JSON 格式;
  • 请求字符集应与客户端配置一致。

排查时可以使用新的订单号重新发起支付,以免旧订单状态影响判断。

不要在 iframe 中打开收银台

如果前端使用 iframe、弹窗组件或局部页面容器加载支付地址,浏览器安全策略可能阻止页面或脚本正常运行,最终表现为空白页。

不建议使用以下方式:

<iframe src="/pay"></iframe>

可以让浏览器直接跳转到支付接口:

window.location.href = "/pay";

也可以在新窗口中打开:

window.open("/pay", "_blank");

如果通过 Ajax 获取支付表单,只把返回的 HTML 放入某个 div,表单也可能无法自动提交。支付宝 SDK 返回的通常是完整表单,应将其作为 HTML 页面响应交给浏览器,不能当作普通 JSON 数据处理。

检查回调地址,但不要把它与页面空白混为一谈

notify_url 无法访问通常会影响异步通知,一般不会直接造成支付收银台空白。不过,如果配置存在严重异常,仍需一起检查。

沙箱回调地址需要满足以下条件:

  • 支付宝服务器可以访问;
  • 不能使用 localhost 或 127.0.0.1;
  • 接口不能被登录验证、CSRF 校验或防火墙拦截;
  • 接收通知后按协议返回成功标识;
  • 验签使用支付宝公钥;
  • HTTPS 证书有效。

本地调试时需要使用可以从公网访问的测试地址。如果公网隧道地址在项目重启后发生变化,也要同步更新 notify_url。

浏览器和网络侧排查

如果服务端返回的 HTML 表单完整,而且请求已经进入支付宝域名,可以按以下顺序尝试:

  1. 使用无痕窗口打开;
  2. 暂时禁用广告拦截和隐私保护插件;
  3. 清除支付宝沙箱域名的 Cookie 和缓存;
  4. 更换浏览器;
  5. 关闭代理或 VPN 后重试;
  6. 改用手机热点等其他网络;
  7. 检查系统日期、时间和时区;
  8. 在开发者工具中查看加载失败的 JS、CSS 和接口请求。

系统时间偏差过大,可能导致 HTTPS 或签名相关请求异常。公司代理或安全软件也可能拦截收银台所需的脚本资源。

推荐的定位顺序

可以按照以下顺序排查:

  1. 查看服务端日志,确认支付接口没有异常;
  2. 确认 pageExecute(request).getBody() 返回了非空 HTML;
  3. 确认 Controller 原样返回 HTML,没有将其解析为视图;
  4. 核对沙箱网关、appId、密钥和当前运行环境;
  5. 换用新的 out_trade_no;
  6. 在顶层窗口直接打开支付页面,避开 iframe 和 Ajax 注入;
  7. 检查浏览器 Network 和 Console;
  8. 更换浏览器、网络和设备复测;
  9. 完成上述检查后,再判断是否为支付宝沙箱临时故障。

仅凭”之前正常、今天空白”不能直接判断沙箱服务发生了故障。先确认空白响应来自业务系统还是支付宝页面,再检查服务端异常、HTTP 响应和浏览器控制台信息,通常就能缩小问题范围。

备注:内容仅供参考。