PAYATHON 2026

支付宝扫码后显示form代码而非付款页面怎么解决?

支付阿杰

结论

手机网站支付接口返回的是一段自动提交的 HTML <form>。商户服务端需要将它作为 text/html 页面直接响应。

扫码后如果看到表单代码,通常是因为响应头被设置成了 text/plain 或 application/json,也可能是框架转义了 HTML,导致支付宝内置 WebView 将表单当作普通文本显示。

如果二维码是供用户扫码付款使用的,不建议直接把手机网站支付接口地址生成二维码。此类场景更适合使用支付宝当面付的 alipay.trade.precreate 接口,再根据接口返回的 qr_code 生成付款二维码。

手机网站支付场景的处理方法

1. 正确返回支付宝 SDK 生成的表单

以 Java SDK 为例,pageExecute() 返回完整的 HTML 表单。接口应直接输出这段内容,并明确设置响应类型:

AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
request.setBizContent("""
{
  "out_trade_no": "202609080001",
  "total_amount": "0.01",
  "subject": "测试订单",
  "product_code": "QUICK_WAP_WAY"
}
""");

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

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

在 Spring MVC 中,可以直接返回 HTML:

@GetMapping(
    value = "/pay",
    produces = MediaType.TEXT_HTML_VALUE
)
@ResponseBody
public String pay() throws AlipayApiException {
    AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
    request.setReturnUrl(returnUrl);
    request.setNotifyUrl(notifyUrl);
    request.setBizContent("""
    {
      "out_trade_no": "202609080001",
      "total_amount": "0.01",
      "subject": "测试订单",
      "product_code": "QUICK_WAP_WAY"
    }
    """);

    return alipayClient.pageExecute(request).getBody();
}

也可以通过 ResponseEntity 显式指定响应头:

return ResponseEntity.ok()
    .contentType(MediaType.TEXT_HTML)
    .body(form);

2. 不要把表单包装成 JSON

以下写法会把 HTML 变成 JSON 字符串,支付宝内置页面不会自动执行其中的表单:

return Map.of("form", form);

同样不要转义 HTML:

&lt;form name="punchout_form" method="post" ...&gt;

浏览器实际收到的内容应该是:

<form name="punchout_form" method="post" action="...">
    ...
</form>
<script>
    document.forms[0].submit();
</script>

响应内容不能是带引号的 JSON,也不能是经过转义的文本。

3. 检查实际响应

使用浏览器开发者工具或其他 HTTP 调试工具检查支付入口接口,先确认响应头:

Content-Type: text/html;charset=UTF-8

再检查响应正文:

  • <form> 标签是否为原始 HTML。
  • 是否被包装为 {"form":"..."}。
  • 是否出现 &lt;form 等转义内容。
  • 网关、统一响应封装或异常处理器是否把结果改成了 JSON。
  • 是否混入调试日志、警告信息或其他文本。

如果项目配置了全局返回对象包装,需要把支付跳转接口排除在包装逻辑之外。

更稳妥的跳转结构

二维码可以先指向商户自己的支付入口,而不是直接指向用途不明确的 SDK 接口。例如:

https://example.com/order/123/pay

该入口按以下流程处理:

  1. 校验订单状态和金额。
  2. 在服务端构造支付宝支付请求。
  3. 获取 pageExecute() 返回的 HTML。
  4. 以 text/html;charset=UTF-8 返回表单。
  5. 由表单自动跳转到支付宝收银台。

支付接口不能直接信任二维码 URL 中传入的金额、商品名称等参数。服务端应根据订单号重新读取订单信息。

如果目标是“支付宝扫码付款”

手机网站支付和扫码支付是两种不同的接入方式。如果希望用户用支付宝扫描二维码后直接进入付款确认页,可以使用当面付预创建接口:

alipay.trade.precreate

调用成功后,该接口会返回 qr_code。商户应根据这个值生成二维码,不要把自己的手机网站支付接口地址直接转成二维码。

示意代码:

AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl(notifyUrl);
request.setBizContent("""
{
  "out_trade_no": "202609080001",
  "total_amount": "0.01",
  "subject": "测试订单"
}
""");

AlipayTradePrecreateResponse response =
    alipayClient.execute(request);

if (response.isSuccess()) {
    String qrCodeContent = response.getQrCode();
    // 使用 qrCodeContent 生成二维码
}

是否可以使用该接口,取决于商户已签约的支付宝产品以及沙箱支持情况。请以当前开放平台控制台中的产品权限和接口文档为准。

注意事项

  • return_url 只处理用户支付后的页面跳转,不能代替服务端异步通知。
  • 支付结果应通过 notify_url 接收,同时完成签名验证、订单号校验、金额校验和幂等处理。
  • 生产环境的支付入口应使用 HTTPS。
  • 不要在页面或二维码中暴露应用私钥。
  • 沙箱客户端和正式支付宝客户端的行为可能不同。如果页面显示原始 <form>,应先检查响应类型和内容是否被转义。
  • 修正 Content-Type 后,如果支付宝内置页面仍未跳转,应检查 CSP、安全网关重写、302 跳转异常,以及支付宝内置 WebView 的访问限制。可以先使用一个不包含支付逻辑的最小自动提交表单,验证页面渲染链路。

备注:内容仅供参考。