H5 接入支付提示跨域,沙盒 GET 请求持续报错如何解决?
结论
alipay.trade.app.pay 是原生 App 支付接口,不能直接用于 H5 页面。浏览器通过 AJAX、fetch 或前端拼接 GET 请求访问支付宝网关,通常会遇到跨域限制。这个问题无法靠前端添加 CORS 请求头解决。
H5 支付应使用 alipay.trade.wap.pay。业务服务器负责生成签名和支付请求,浏览器再通过表单提交或页面跳转进入支付宝收银台。
为什么会出现跨域错误
常见原因有以下几种:
-
使用了错误的接口
alipay.trade.app.pay面向原生 App。服务端调用后得到的是供支付宝客户端 SDK 使用的订单字符串,并不是 H5 浏览器可以直接打开的支付页面。 -
前端直接访问支付宝网关
浏览器的同源策略会限制跨域 AJAX 请求。支付宝支付网关不是向任意网页开放的 CORS API,因此即使参数和签名都正确,请求也可能先被浏览器拦截。
-
使用 GET 拼接支付参数
GET 请求容易出现 URL 编码错误、参数过长、
+被转为空格、签名值被二次编码等问题。支付页面类接口更适合通过表单 POST 提交。 -
混用了沙盒和正式环境的配置
沙盒应用必须使用沙盒网关和沙盒账号。正式应用参数、正式网关、沙盒密钥和沙盒账号不能混用。
正确的调用流程
H5 支付流程如下:
H5 页面
→ 请求业务服务器创建订单
→ 业务服务器生成 alipay.trade.wap.pay 请求并签名
→ 返回支付表单或支付跳转地址
→ 浏览器跳转到支付宝收银台
→ 支付宝异步通知业务服务器
→ 业务服务器验签并更新订单
私钥、签名过程和订单金额都必须放在服务端,不能写入 JavaScript 或暴露给浏览器。
服务端请求参数示例
下面的示例只用于说明参数结构。签名应通过支付宝官方 SDK,或经过验证的服务端签名实现来生成:
<?php
$params = [
'app_id' => '你的沙盒应用 APPID',
'method' => 'alipay.trade.wap.pay',
'format' => 'JSON',
'charset' => 'utf-8',
'sign_type' => 'RSA2',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'notify_url' => 'https://example.com/payment/alipay/notify',
'return_url' => 'https://example.com/payment/result',
'biz_content' => json_encode([
'out_trade_no' => 'ORDER_202609090001',
'total_amount' => '0.01',
'subject' => '测试订单',
'product_code' => 'QUICK_WAP_WAY',
'quit_url' => 'https://example.com/order'
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
];
// 使用支付宝服务端 SDK 按照 RSA2 规则生成 sign
$params['sign'] = createAlipaySign($params);
不要直接照搬示例中的订单号、金额和地址。out_trade_no 应由业务系统生成,并确保在商户侧唯一。
通过表单进入收银台
服务端生成签名后,可以返回一个自动提交的 HTML 表单:
<form id="alipay-form"
method="post"
action="https://openapi-sandbox.dl.alipaydev.com/gateway.do">
<input type="hidden" name="app_id" value="沙盒应用 APPID">
<input type="hidden" name="method" value="alipay.trade.wap.pay">
<input type="hidden" name="format" value="JSON">
<input type="hidden" name="charset" value="utf-8">
<input type="hidden" name="sign_type" value="RSA2">
<input type="hidden" name="timestamp" value="2026-09-09 12:00:00">
<input type="hidden" name="version" value="1.0">
<input type="hidden" name="notify_url"
value="https://example.com/payment/alipay/notify">
<input type="hidden" name="return_url"
value="https://example.com/payment/result">
<input type="hidden" name="biz_content"
value='{"out_trade_no":"ORDER_202609090001","total_amount":"0.01","subject":"测试订单","product_code":"QUICK_WAP_WAY"}'>
<input type="hidden" name="sign" value="服务端生成的签名">
</form>
<script>
document.getElementById('alipay-form').submit();
</script>
实际开发时,最好直接使用服务端 SDK 提供的页面支付或表单生成功能,以免手工处理待签名字符串、参数排序和 URL 编码时出错。
如果必须使用 alipay.trade.app.pay
只有原生 App 接入支付宝客户端 SDK 时,才应使用这个接口:
- App 请求业务服务器创建支付订单。
- 服务器调用
alipay.trade.app.pay,生成签名后的订单字符串。 - 服务器将订单字符串返回给 App。
- App 把订单字符串传给支付宝客户端 SDK。
- 服务器根据异步通知确认最终支付状态。
即使采用这种方式,也不能让 WebView 或浏览器直接跨域请求支付宝网关。
排查步骤
1. 检查调用位置
先确认请求是否由下面这类前端代码直接发出:
fetch('https://openapi-sandbox.dl.alipaydev.com/gateway.do?...')
或者:
axios.get('https://openapi-sandbox.dl.alipaydev.com/gateway.do', {
params: paymentParams
})
如果是,请停止前端直调,改为请求自己的业务服务器。
2. 检查接口类型
H5 收银台使用:
alipay.trade.wap.pay
原生 App 使用:
alipay.trade.app.pay
3. 检查沙盒配置
逐项核对以下内容:
app_id是否属于当前沙盒应用;- 网关是否为当前开放平台文档提供的沙盒网关;
- 应用私钥是否与上传的应用公钥匹配;
- 验签时使用的是否为支付宝公钥;
sign_type是否与密钥及签名实现一致;- 沙盒支付是否使用了沙盒账号。
沙盒网关地址可能随着平台配置或文档调整而变化,应以当前开放平台控制台和官方文档显示的地址为准。
4. 检查签名和编码
生成签名前,需要排除 sign 参数,并按照支付宝接口要求构造待签名字符串。还要核对:
charset是否统一为utf-8;biz_content是否为合法 JSON;- JSON 内容是否在签名后被修改;
- 签名是否经过重复 URL 编码;
- GET 查询串中的
+是否被错误转换为空格; - 参数值前后是否混入空格或换行。
5. 区分跳转回调和支付通知
return_url 只是用户支付后浏览器跳转的地址。用户可能关闭页面,也可能伪造查询参数,因此不能通过它判断支付是否成功。
订单状态应以 notify_url 收到的异步通知为准。服务端收到通知后,需要:
- 验证签名;
- 校验
app_id; - 校验商户订单号;
- 校验金额和币种;
- 确认交易状态;
- 做幂等处理,避免重复更新订单;
- 按照接口要求返回成功响应。
注意事项
不要通过关闭浏览器安全策略、安装跨域插件或使用公共代理来”解决”支付跨域问题。这些做法无法修正错误的接入流程,还可能造成密钥、订单和用户数据泄露。
如果改用 alipay.trade.wap.pay,并由服务端生成表单后仍然报错,应检查支付宝返回的业务错误码和 sub_msg,不能只看浏览器控制台里的跨域信息。跨域通常说明调用入口不对。签名、应用配置或参数有误时,则需要根据网关返回的具体内容分别排查。
备注:内容仅供参考。
