PAYATHON 2026

电脑网站调用page.pay后form表单提交出现404错误

支付老李

结论

page.pay 能正常返回,只说明支付请求已生成,不代表支付已经成功。提交返回的 form 后出现 404,首先要确认是哪一个地址返回了 404:

  • 尚未进入支付宝收银台就出现 404,应检查 form 的 action、支付宝网关地址,以及前端是否改写了返回内容。
  • 完成支付后才出现 404,应检查 return_url 是否配置错误、对应路由是否存在,以及该地址能否从公网访问。
  • notify_url 配置错误通常不会让浏览器显示 404,但会导致支付结果的异步通知失败。

建议让服务端直接输出 SDK 返回的完整表单,不要解析、拼接或修改表单内容。同时确认正式环境和沙箱环境使用的网关、应用及密钥配置相互匹配。

先确认 404 出现在哪个阶段

打开浏览器开发者工具,在 “网络(Network)” 中找到真正返回 404 的请求地址。

提交表单后立即出现 404

先看请求是否发往支付宝网关。常见的正式环境网关形式为:

https://openapi.alipay.com/gateway.do

沙箱环境需要使用对应的沙箱网关。具体地址应以当前支付宝开放平台文档和 SDK 配置为准,不要自行拼接。

如果返回 404 的地址仍是自己的网站,例如:

https://www.example.com/gateway.do

通常说明 form action 使用了相对地址、网关配置不完整,或者表单在传递过程中被修改。

支付完成后出现 404

如果已经进入支付宝收银台,完成支付后才跳转到 404 页面,应检查 return_url。例如:

https://www.example.com/pay/alipay/return

逐项确认:

  • 路由确实存在;
  • 域名、端口和协议正确;
  • 地址可以从公网访问;
  • 反向代理已将该路径转发到应用;
  • 应用没有把 GET 回跳请求错误地限制为 POST;
  • 路径大小写及末尾斜杠与实际路由一致。

检查服务端网关配置

以 Java SDK 的常见调用方式为例,传给客户端的网关地址应是完整的绝对 URL:

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

还要核对以下配置:

  • appId 是否属于当前环境;
  • 正式应用是否使用正式网关;
  • 沙箱应用是否使用沙箱网关;
  • 应用私钥和支付宝公钥是否配套;
  • 签名算法是否与平台配置一致;
  • 网关地址中是否有空格、换行、重复路径或错误端口。

不要把网关地址写成开放平台后台地址、应用域名或文档页面地址。

原样输出 SDK 返回的表单

AlipayTradePagePayResponse#getBody() 通常返回一段可以自动提交的 HTML 表单。服务端应将其作为 HTML 原样写入响应:

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl("https://www.example.com/pay/alipay/return");
request.setNotifyUrl("https://www.example.com/pay/alipay/notify");

request.setBizContent("""
{
  "out_trade_no": "ORDER_202609130001",
  "total_amount": "99.00",
  "subject": "订单支付",
  "product_code": "FAST_INSTANT_TRADE_PAY"
}
""");

AlipayTradePagePayResponse response = alipayClient.pageExecute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        "page.pay 调用失败:" + response.getCode() + " / " + response.getSubMsg()
    );
}

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

不要对 response.getBody() 做这些处理:

  • 提取表单字段后自行重组;
  • 对整段 HTML 再做 URL 编码;
  • 通过模板引擎进行 HTML 转义;
  • 删除隐藏字段;
  • 修改 action;
  • 把表单当作普通文本或 JSON 返回,再直接插入页面。

如果模板渲染后得到的是下面这种内容:

<form name="punchout_form" method="post" action="...">

说明 HTML 已被转义,浏览器不会把它识别为真正的表单。

前后端分离时的处理方式

如果后端通过 JSON 返回表单字符串,前端要确认该字符串没有被二次编码。可以创建一个临时页面,再写入完整 HTML:

const html = response.data.form;

const paymentWindow = window.open("", "_blank");
if (!paymentWindow) {
  throw new Error("支付窗口被浏览器拦截");
}

paymentWindow.document.open();
paymentWindow.document.write(html);
paymentWindow.document.close();

更直接的做法是让浏览器访问后端的支付发起地址,由后端响应完整表单:

window.location.href = "/api/pay/alipay/page";

对应接口直接返回 text/html,可以减少转义、截断和框架过滤带来的问题。

如果必须使用 innerHTML,还要注意某些浏览器或前端框架不会执行通过 innerHTML 插入的 <script>。这会让 SDK 表单中的自动提交脚本失效,此时需要手动找到表单并提交:

const container = document.createElement("div");
container.innerHTML = response.data.form;
document.body.appendChild(container);

const form = container.querySelector("form");
if (!form) {
  throw new Error("支付宝返回内容中未找到 form");
}

form.submit();

检查 return_url 和 notify_url

这两个地址的用途不同:

request.setReturnUrl("https://www.example.com/pay/alipay/return");
request.setNotifyUrl("https://www.example.com/pay/alipay/notify");
  • return_url 是支付结束后的浏览器同步跳转地址,通常需要支持 GET 请求。
  • notify_url 是支付宝服务器发送异步通知的地址,通常需要支持 POST 请求。

示例路由:

@GetMapping("/pay/alipay/return")
public String alipayReturn(HttpServletRequest request) {
    // 验签后查询订单状态,再展示支付结果
    return "pay/result";
}

@PostMapping("/pay/alipay/notify")
@ResponseBody
public String alipayNotify(HttpServletRequest request) {
    // 验签并更新订单;处理成功后按接口要求返回成功响应
    return "success";
}

不要只根据 return_url 中的参数把订单标记为已支付。同步跳转可能被用户关闭,也可能被伪造。最终支付状态应结合签名验证、异步通知和主动查询来确认。

检查反向代理和应用路由

如果 404 来自商户网站,还要检查 Nginx、网关和应用上下文路径。假设应用的实际接口是:

/pay/alipay/return

如果 Nginx 转发时删除或重复添加了 /api,最终请求可能变成:

/api/api/pay/alipay/return

可重点检查类似配置:

location /api/ {
    proxy_pass http://127.0.0.1:8080/;
}

proxy_pass 是否带末尾 / 会影响路径的拼接方式,需要结合实际请求地址和后端路由判断。

推荐排查顺序

  1. 在 Network 中找到真正返回 404 的 URL。
  2. 确认错误发生在进入收银台之前,还是支付完成回跳之后。
  3. 查看返回表单中的 method 和 action,确认 action 是正确的支付宝绝对地址。
  4. 确认正式环境、沙箱环境和应用配置没有混用。
  5. 让服务端原样输出 pageExecute() 返回的 body。
  6. 排除模板转义、JSON 二次编码以及前端脚本未执行的问题。
  7. 验证 return_url 对应的路由是否存在并支持 GET。
  8. 验证 notify_url 是否能从公网访问并支持 POST。
  9. 检查 Nginx、网关和应用路由对路径的转换。
  10. 结合 SDK 返回的 code、subCode、msg、subMsg 和服务端日志继续定位。

排查时可以记录订单号、请求时间、网关地址及 SDK 返回的错误信息,但不要在日志或截图中暴露应用私钥、签名原文和完整敏感参数。

备注:内容仅供参考。