支付宝沙箱手机网站支付无法拉起并返回 404 Not Found
结论
仅凭这个 404 页面,还不能认定支付宝沙箱服务异常。
错误出现在支付宝沙箱的内部 H5 落地页:
mobileclientgw-sandbox.dl.alipaydev.com/h5pay/landing/index.html
不过,请求中有一项信息明显异常:
referer=https%3A%2F%2F%2F
解码后是:
https:///
这不是合法的来源地址。通常是商户侧的域名、return_url、跳转地址或 URL 拼接配置为空、缺少主机名,也可能是错误地进行了二次编码。这类参数异常会影响支付宝内部路由生成或定位收银台页面,最终可能返回 404。
建议先检查支付请求的生成过程,不要直接访问或自行拼接报错页面中的 h5pay/landing/index.html 地址。
建议的排查顺序
1. 确认使用的是沙箱网关
手机网站支付请求应提交到沙箱开放平台网关:
https://openapi.alipaydev.com/gateway.do
不要混用正式环境网关、正式应用 ID、正式密钥和沙箱配置。以下配置必须来自同一套沙箱应用:
app_id- 应用私钥
- 支付宝公钥
- 网关地址
- 签名方式
报错 URL 中的 targetDispatchSystem=unitradeprod 是支付宝内部路由参数。不能仅凭其中的 prod,就判断商户请求进入了正式环境。
2. 检查 return_url 和站点域名
先检查最终传给 SDK 的 return_url。它必须是完整、合法的绝对 URL,例如:
https://example.com/alipay/return
以下写法都可能引发问题:
https:///
https://
/alipay/return
example.com/alipay/return
如果地址通过环境变量拼接生成,需要确认域名变量不是空字符串。例如:
String returnUrl = siteBaseUrl + "/alipay/return";
当 siteBaseUrl 为空或只有 https:// 时,拼接出来的地址就是无效的。
反向代理配置也需要检查。应用部署在 Nginx、网关或负载均衡器之后时,如果程序通过请求对象自动获取域名,可能无法取得正确的 Host 或协议。更稳妥的做法是用配置项保存完整的外部访问地址,不要临时根据当前请求推导支付回跳域名。
3. 不要自行拼接支付宝内部落地页
下面的地址是支付宝处理支付请求后生成的临时页面:
/h5pay/landing/index.html?h5_request_token=...
其中的 h5_request_token 通常具有时效性,还可能与当前会话、请求参数或生成时间有关。请勿:
- 手工拼接该 URL;
- 保存后重复使用;
- 修改其中的查询参数;
- 对整个 URL 再做一次 URL 编码;
- 将旧请求中的 token 用于新订单。
商户程序应调用 alipay.trade.wap.pay,再将支付宝 SDK 返回的 HTML 表单原样输出给浏览器。
Java 调用示例
下面省略了具体的 SDK 版本,类名和调用方式采用支付宝 Java SDK 的常见接口:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipaydev.com/gateway.do",
sandboxAppId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://example.com/alipay/return");
request.setNotifyUrl("https://example.com/alipay/notify");
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo("ORDER_20240524_001");
model.setTotalAmount("0.01");
model.setSubject("沙箱支付测试");
model.setProductCode("QUICK_WAP_WAY");
model.setQuitUrl("https://example.com/order");
request.setBizModel(model);
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
String formHtml = response.getBody();
// Web 框架中应以 text/html 返回,且不要转义表单内容。
httpServletResponse.setContentType("text/html;charset=UTF-8");
httpServletResponse.getWriter().write(formHtml);
httpServletResponse.getWriter().flush();
} else {
throw new IllegalStateException(
"支付宝请求失败,code=" + response.getCode()
+ ", subCode=" + response.getSubCode()
+ ", msg=" + response.getMsg()
+ ", subMsg=" + response.getSubMsg()
);
}
其中需要特别确认的参数是:
model.setProductCode("QUICK_WAP_WAY");
还要确保订单号唯一、金额格式正确,并记录支付宝 SDK 返回的 code、sub_code、msg、sub_msg 和请求 ID,以便排查问题。
检查返回 HTML 的处理方式
pageExecute() 通常返回一段自动提交的 HTML 表单。服务端必须将它作为 HTML 返回,不能包装成 JSON:
{
"html": "<form name=\"punchout_form\" ...>"
}
也不能先转义 HTML,再将其直接展示出来:
<form name="punchout_form">
如果前端通过 AJAX 获取表单,再用 innerHTML 注入页面,浏览器通常不会自动执行其中的 <script>,表单也可能无法提交。更简单可靠的做法是让浏览器直接访问服务端的支付发起接口,由该接口输出支付宝返回的完整 HTML。
例如:
window.location.href = "/payment/alipay/wap?orderNo=ORDER_20240524_001";
检查 URL 编码
return_url、notify_url 和业务参数应统一交给 SDK 签名和编码,不要预先对参数整体多次执行 URLEncoder.encode()。
尤其要避免下面的写法:
String encodedReturnUrl =
URLEncoder.encode(
URLEncoder.encode(returnUrl, StandardCharsets.UTF_8),
StandardCharsets.UTF_8
);
重复编码会将:
https://example.com/alipay/return
逐步转换成包含 %25 的字符串,进而造成验签、跳转或内部解析异常。
报错地址的 query_params 中出现 %252F、%253D,并不一定说明商户侧进行了重复编码,因为支付宝内部也可能再次封装参数。判断是否存在编码问题,应以商户提交到开放平台网关的原始请求为准。
检查浏览器和网络环境
完成参数检查后,可以按以下方式交叉测试:
- 使用手机系统浏览器直接打开支付发起接口。
- 分别在 Wi-Fi 和移动网络下测试。
- 清除浏览器缓存并重新创建订单。
- 不要重复打开已经生成的收银台 URL。
- 如果当前使用 App WebView 测试,改用系统浏览器验证。
- 检查代理、抓包软件、公司网络网关或 DNS 是否改写了请求。
- 确认设备时间正常,以免临时令牌或签名因时间偏差失效。
如果系统浏览器能够正常拉起支付页面,而 App 内的 WebView 不行,问题通常出在 WebView 的跳转策略、外部协议处理、Cookie、User-Agent 或重定向限制上,不应直接归因于支付宝沙箱整体故障。
如何判断是否为沙箱侧异常
以下条件全部满足后,才有较充分的理由怀疑支付宝沙箱出现了临时故障:
- 沙箱网关、应用 ID 和密钥配置正确;
return_url、notify_url、quit_url都是合法地址;- 使用 SDK 直接调用
alipay.trade.wap.pay; - 没有自行拼接或重复编码支付宝地址;
- 每次测试都会创建新订单,并重新生成支付表单;
- 多个浏览器、设备和网络都能稳定复现;
- 同一套业务代码切换到其他确认可用的测试配置后,表现不同;
- SDK 请求成功,但支付宝生成的落地页持续返回同类 404。
这时可以保存以下信息,并提交给支付宝技术支持:
- 沙箱应用
app_id; - 接口名称
alipay.trade.wap.pay; out_trade_no;- 完整的错误时间和时区;
- SDK 返回的响应码、子响应码及请求 ID;
- 脱敏后的网关请求参数;
- 404 页面中的
Server、Date和 URL; - 复现步骤及所用的浏览器环境。
不要在工单、日志或截图中公开商户私钥、完整签名、用户敏感信息或可重复使用的支付令牌。
根据当前错误信息,最需要先处理的是异常的 referer=https:///。检查站点基础 URL、return_url 的生成逻辑和代理转发配置后,重新创建订单,再通过 SDK 生成新的支付表单。这样比反复访问现有的 404 地址更有助于定位问题。
备注:内容仅供参考。
