PAYATHON 2026

WAP页面支付执行page_execute后为何返回form表单

支付老李

结论

page_execute 返回 <form> 表单通常是正常的。WAP 支付接口不会以 JSON 形式直接返回支付页面。SDK 会生成一段包含支付参数的 HTML 表单,再通过 JavaScript 自动提交到支付宝收银台。

如果最终页面显示 JSON 字段,通常需要检查以下环节:

  • 后端是否把表单包装进 JSON,导致前端将其作为普通文本显示;
  • 前端是否通过 Ajax 请求支付接口,没有触发完整的页面跳转;
  • 接口响应头是否为 text/html;
  • 表单提交后,支付宝是否返回了参数校验或签名错误的 JSON;
  • 网关、应用模式、产品接口或运行环境是否匹配。

正确的处理方式是让浏览器直接接收并渲染 SDK 返回的 HTML。如果必须由前端接收这段 HTML,则需要将它写入页面,再提交其中的表单。

为什么返回 form 表单

WAP 支付的典型流程如下:

  1. 商户服务端构造 alipay.trade.wap.pay 请求。
  2. SDK 对请求参数进行签名。
  3. pageExecute 生成一个指向支付宝网关的 HTML <form>。
  4. 浏览器加载这段 HTML。
  5. 表单自动提交,浏览器进入支付宝收银台。
  6. 支付完成后,支付宝根据 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 来自业务接口还是支付宝,再分别检查响应封装或具体错误码。

备注:内容仅供参考。