支付宝扫码后显示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:
<form name="punchout_form" method="post" ...>
浏览器实际收到的内容应该是:
<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":"..."}。 - 是否出现
<form等转义内容。 - 网关、统一响应封装或异常处理器是否把结果改成了 JSON。
- 是否混入调试日志、警告信息或其他文本。
如果项目配置了全局返回对象包装,需要把支付跳转接口排除在包装逻辑之外。
更稳妥的跳转结构
二维码可以先指向商户自己的支付入口,而不是直接指向用途不明确的 SDK 接口。例如:
https://example.com/order/123/pay
该入口按以下流程处理:
- 校验订单状态和金额。
- 在服务端构造支付宝支付请求。
- 获取
pageExecute()返回的 HTML。 - 以
text/html;charset=UTF-8返回表单。 - 由表单自动跳转到支付宝收银台。
支付接口不能直接信任二维码 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 的访问限制。可以先使用一个不包含支付逻辑的最小自动提交表单,验证页面渲染链路。
备注:内容仅供参考。
