WAP页面支付执行page_execute后为何返回form表单
结论
page_execute 返回 <form> 表单通常是正常的。WAP 支付接口不会以 JSON 形式直接返回支付页面。SDK 会生成一段包含支付参数的 HTML 表单,再通过 JavaScript 自动提交到支付宝收银台。
如果最终页面显示 JSON 字段,通常需要检查以下环节:
- 后端是否把表单包装进 JSON,导致前端将其作为普通文本显示;
- 前端是否通过 Ajax 请求支付接口,没有触发完整的页面跳转;
- 接口响应头是否为
text/html; - 表单提交后,支付宝是否返回了参数校验或签名错误的 JSON;
- 网关、应用模式、产品接口或运行环境是否匹配。
正确的处理方式是让浏览器直接接收并渲染 SDK 返回的 HTML。如果必须由前端接收这段 HTML,则需要将它写入页面,再提交其中的表单。
为什么返回 form 表单
WAP 支付的典型流程如下:
- 商户服务端构造
alipay.trade.wap.pay请求。 - SDK 对请求参数进行签名。
pageExecute生成一个指向支付宝网关的 HTML<form>。- 浏览器加载这段 HTML。
- 表单自动提交,浏览器进入支付宝收银台。
- 支付完成后,支付宝根据
return_url跳回商户页面,并通过notify_url异步通知支付结果。
所以,pageExecute 返回的既不是支付结果,也不是收银台页面本身,而是一个用于进入收银台的”中转页面”。
返回内容通常类似:
<form id="alipaysubmit" name="alipaysubmit"
action="https://openapi.alipay.com/gateway.do?charset=UTF-8"
method="POST">
<input type="hidden" name="app_id" value="...">
<input type="hidden" name="method" value="alipay.trade.wap.pay">
<input type="hidden" name="sign" value="...">
</form>
<script>
document.forms['alipaysubmit'].submit();
</script>
具体字段和 HTML 结构由 SDK 生成,不建议自行拆分、修改或重新拼装。
推荐的后端处理方式
后端支付入口应直接输出 pageExecute 返回的 HTML,不要再包装成 JSON。
以 Java SDK 的常见调用方式为例:
@PostMapping(value = "/pay", produces = "text/html;charset=UTF-8")
@ResponseBody
public String pay() throws AlipayApiException {
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://example.com/pay/return");
request.setNotifyUrl("https://example.com/pay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609130001",
"total_amount": "0.01",
"subject": "测试订单",
"product_code": "QUICK_WAP_WAY"
}
""");
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"创建支付请求失败,code=" + response.getCode()
+ ", subCode=" + response.getSubCode()
+ ", msg=" + response.getMsg()
+ ", subMsg=" + response.getSubMsg()
);
}
return response.getBody();
}
使用 PHP SDK 时,处理原则相同:
<?php
$request = new AlipayTradeWapPayRequest();
$request->setNotifyUrl('https://example.com/pay/notify');
$request->setReturnUrl('https://example.com/pay/return');
$request->setBizContent(json_encode([
'out_trade_no' => 'ORDER_202609130001',
'total_amount' => '0.01',
'subject' => '测试订单',
'product_code' => 'QUICK_WAP_WAY',
], JSON_UNESCAPED_UNICODE));
$html = $aop->pageExecute($request);
header('Content-Type: text/html; charset=UTF-8');
echo $html;
不同版本 SDK 的方法大小写和响应对象可能不同,应以项目实际依赖为准。
前端应该如何发起支付
最稳妥的方式是使用普通页面跳转,让浏览器直接访问后端支付入口:
window.location.href = `/pay?orderNo=${encodeURIComponent(orderNo)}`;
也可以提交普通表单:
<form action="/pay" method="post">
<input type="hidden" name="orderNo" value="ORDER_202609130001">
<button type="submit">立即支付</button>
</form>
后端返回 text/html 后,浏览器会解析并自动提交表单,随后跳转到支付宝收银台。
不建议使用下面这种写法:
fetch('/pay', {
method: 'POST'
})
.then(response => response.json())
.then(data => {
document.body.innerText = data;
});
pageExecute 返回的是 HTML,不是 JSON。调用 response.json() 会导致解析失败。即使后端把 HTML 包装成 JSON,直接赋给 innerText 也只会显示字符串,表单不会执行。
如果现有架构必须通过 Ajax 获取表单,可以把 HTML 写入临时容器,再手动提交:
const response = await fetch('/pay', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ orderNo })
});
const html = await response.text();
const container = document.createElement('div');
container.style.display = 'none';
container.innerHTML = html;
document.body.appendChild(container);
const form = container.querySelector('form');
if (!form) {
throw new Error('支付接口未返回有效表单');
}
form.submit();
这种方式可能受前端框架、CSP、HTML 清理策略和 WebView 行为影响。只要能使用普通页面跳转,就应优先选择普通跳转。
为什么页面会显示 JSON 字段
首先要确认页面显示的是哪一种 JSON。
显示的是业务接口自己的 JSON
例如:
{
"code": 0,
"message": "success",
"data": "<form>...</form>"
}
这说明后端把表单放进了统一响应对象。浏览器访问接口时只会显示 JSON,不会自动执行 data 字段中的 HTML 字符串。
应直接返回表单:
return response.getBody();
不要写成:
return Result.success(response.getBody());
同时确认响应头为:
Content-Type: text/html; charset=UTF-8
显示的是支付宝返回的错误 JSON
如果自动表单已经提交,之后出现的内容包含 code、msg、sub_code、sub_msg 等字段,通常说明请求已经到达支付宝网关,但未通过校验。
此时应检查:
app_id是否属于当前应用;- 私钥、公钥和签名类型是否匹配;
- 正式环境与沙箱网关是否混用;
method是否为alipay.trade.wap.pay;product_code是否符合 WAP 支付要求;out_trade_no、total_amount、subject是否有效;- 参数是否在签名后被再次修改;
- 字符集是否前后一致;
- 应用是否具备对应产品权限。
不能只看顶层 msg,还应记录 sub_code 和 sub_msg。排查日志时,私钥、完整签名和用户信息等敏感内容需要脱敏。
为什么没有直接唤醒支付宝 App
WAP 支付首先会进入移动端收银台,但不保证每次都能直接唤醒支付宝 App。具体行为可能受以下因素影响:
- 手机是否安装支付宝;
- 当前浏览器是否允许跳转到外部 App;
- 是否运行在微信等限制外部跳转的内置浏览器中;
- 系统如何处理 URL Scheme 或通用链接;
- 用户是否关闭了相关权限;
- 支付宝收银台根据运行环境选择了哪种支付方式。
因此,直接唤醒 App 和先进入支付宝 H5 收银台,再由用户选择打开 App,都可能是正常现象。商户端不应修改 SDK 表单,强行拼接唤醒链接。
还需要注意的事项
return_url 只负责支付完成后的页面跳转,不能作为判断支付成功的唯一依据。用户可能关闭页面,网络也可能中断,跳转参数还可能被伪造。
订单状态应以支付宝异步通知或主动查询结果为准。处理 notify_url 时,需要完成以下工作:
- 验证通知签名;
- 校验商户订单号、应用标识和金额;
- 保证重复通知时的处理逻辑幂等;
- 只有业务处理成功后,才返回支付宝要求的成功响应;
- 不要根据前端传回的”支付成功”直接修改订单状态。
pageExecute 返回表单本身不是异常。后端直接输出 HTML,前端使用页面跳转,并确保请求参数和签名正确后,浏览器就会提交表单并进入支付宝收银台。如果提交后仍然显示 JSON,应先确认 JSON 来自业务接口还是支付宝,再分别检查响应封装或具体错误码。
备注:内容仅供参考。
