支付宝沙箱支付出现504 Gateway Time-out等错误
结论
只配置 appid、appPrivateKey、alipayPublicKey 和 notifyUrl,并不能保证沙箱支付成功。
遇到 504 Gateway Time-out 或“系统有点忙,一会儿再试试”,一般从两方面排查:
- 支付宝沙箱网关暂时异常、响应超时或正在维护。如果请求已经进入支付宝页面,而且错误由支付宝返回,更可能是这类问题。
- 请求环境与签名配置不一致。常见情况包括网关地址错误、密钥不匹配、
sign_type不一致、请求参数不合法,以及误把应用公钥填成支付宝公钥。
可以先用最小支付请求验证签名和网关,再检查网络、业务参数与异步通知地址。不要看到页面提示“系统繁忙”就认定代码有问题,也不要反复更换密钥碰运气。
先确认沙箱网关
沙箱环境应请求:
https://openapi-sandbox.dl.alipaydev.com/gateway.do
正式环境通常使用:
https://openapi.alipay.com/gateway.do
沙箱 appid、沙箱账号和沙箱密钥只能用于沙箱环境。把正式网关和沙箱配置混在一起,通常会引发验签、应用状态或权限错误。
使用支付宝 SDK 时,需要明确设置沙箱网关:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi-sandbox.dl.alipaydev.com/gateway.do",
appId,
appPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
不同版本 SDK 的构造方法可能有所不同,具体应以当前版本的官方 SDK 接口为准。无论使用哪个版本,网关、字符集和签名算法都要保持一致。
检查公钥和私钥是否配对
配置时最容易混淆的是几种公钥的用途:
appPrivateKey是应用私钥,由商户自行保存,用来给请求签名。- 应用公钥与应用私钥配对,需要上传到支付宝开放平台。
alipayPublicKey是支付宝公钥,由开放平台提供,用来验证支付宝响应和通知的签名。
SDK 的 alipayPublicKey 参数必须填写支付宝公钥,不能填写应用公钥。
还需要逐项确认:
- 密钥属于当前沙箱
appid,不是其他应用的密钥。 - 开放平台中的应用公钥与本地
appPrivateKey配对。 - 平台配置为
RSA2时,代码中的sign_type也使用RSA2。 - 私钥没有缺行,也没有因空格或复制操作而损坏、截断。
- SDK 需要的是原始 Base64 密钥还是 PEM 格式,应按对应语言 SDK 的要求传入。
PEM 私钥通常类似:
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
如果 SDK 只接受去掉头尾的 Base64 内容,就不能直接传入完整的 PEM 文本。反过来也一样。
使用最小参数发起支付
排查时先删去可选参数,只保留必要字段。以电脑网站支付为例:
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl("https://example.com/pay/return");
request.setNotifyUrl("https://example.com/pay/notify");
request.setBizContent("""
{
"out_trade_no": "TEST202609090001",
"total_amount": "0.01",
"subject": "沙箱支付测试",
"product_code": "FAST_INSTANT_TRADE_PAY"
}
""");
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
String form = response.getBody();
System.out.println(form);
} else {
System.err.println("code=" + response.getCode());
System.err.println("subCode=" + response.getSubCode());
System.err.println("msg=" + response.getMsg());
System.err.println("subMsg=" + response.getSubMsg());
System.err.println("body=" + response.getBody());
}
out_trade_no 在商户侧必须唯一。重复使用已经支付或状态异常的订单号,可能引发重复订单或交易状态错误。每次排查最好生成一个新订单号。
如果调用的是手机网站支付、App 支付或当面付,需要改用对应的 API 和正确的 product_code,不能直接照搬电脑网站支付的参数。
确认 504 出现在哪一层
504 Gateway Time-out 不一定来自支付宝,首先要确认响应是由谁返回的。
请求尚未到达支付宝
如果 504 页面带有 Nginx、网关、CDN 或本公司域名等特征,问题可能出在自己的反向代理或后端服务,例如:
- 应用连接支付宝接口时超时。
- DNS、代理、防火墙或出口网络异常。
- Nginx、Ingress 或 API Gateway 的读取超时设置过短。
- 后端线程阻塞,没有及时向浏览器返回支付表单或跳转地址。
排查时可以记录:
请求时间:
请求网关:
HTTP 状态码:
响应头:
响应体:
支付宝接口 code:
支付宝接口 sub_code:
支付宝接口 msg:
支付宝接口 sub_msg:
不要只记一条“支付失败”。定位参数、权限和签名问题时,code 与 sub_code 更有用。
已经跳转到支付宝页面
如果浏览器已经进入支付宝沙箱域名,之后才显示“系统有点忙”或 504,请求大概率已经到达支付宝。可以依次尝试:
- 换一个新的
out_trade_no。 - 退出沙箱买家账号,然后重新登录。
- 清除浏览器 Cookie,或者使用无痕窗口。
- 等一段时间再试,避免短时间内连续提交。
- 更换网络或直接请求沙箱网关,排除本地代理的影响。
- 查询订单状态,确认支付宝是否已经创建交易。
- 保存请求时间、
appid、接口名称及支付宝返回内容,必要时交给支付宝技术支持排查。
沙箱环境偶尔会有服务不稳定的情况。不过,只有排除签名、参数、网关和网络问题后,才能较有把握地判断故障来自沙箱服务。
验证网络连通性
应从实际部署支付服务的机器测试沙箱网关,不要只在个人电脑上测试:
curl -I --connect-timeout 10 --max-time 30 \
"https://openapi-sandbox.dl.alipaydev.com/gateway.do"
这个命令只能检查 DNS、TLS 和基础 HTTP 连通性,不能证明业务请求一定能够成功。即使返回 4xx,只要服务器及时响应,通常也说明基础网络是通的。如果连接持续超时,应检查:
- 服务器 DNS。
- HTTPS 出站访问策略。
- 防火墙或安全组。
- HTTP/HTTPS 代理配置。
- TLS 证书链和系统时间。
- 容器或云主机的出口网络。
notifyUrl 不是支付页面的跳转地址
notifyUrl 用于接收支付宝服务器发送的异步通知,不负责将用户浏览器跳回商户网站。浏览器在支付完成后的跳转地址通常是 returnUrl。
合格的 notifyUrl 需要满足以下条件:
- 支付宝服务器可以通过公网访问。
- 不使用
localhost、127.0.0.1或内网 IP。 - 尽量使用有效的 HTTPS 证书。
- 不依赖浏览器 Cookie 或登录状态。
- 能够接收支付宝实际使用的请求方法和表单参数。
- 验签成功并完成业务处理后,返回纯文本
success。 - 同一条通知重复到达时,不会重复入账。
notifyUrl 无法访问,通常会触发支付宝重试异步通知,而不是直接导致支付页面出现 504。因此,不能只围绕通知地址排查。
通知处理的核心逻辑可以写成:
Map<String, String> params = extractAlipayParameters(request);
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
response.getWriter().write("failure");
return;
}
String appIdFromNotify = params.get("app_id");
String outTradeNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
// 还应核对 app_id、订单号、订单金额和收款方信息。
// 更新订单时必须保证幂等。
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
response.getWriter().write("success");
} else {
response.getWriter().write("failure");
}
更新订单时不能只看 trade_status。至少还要验证签名、app_id、订单号和金额。
查询订单是否已经创建
请求超时后,不要马上重新支付。网络超时只能说明调用方没有及时收到结果,不能证明支付宝没有处理这次请求。
可以通过 alipay.trade.query,按商户订单号查询:
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent("""
{
"out_trade_no": "TEST202609090001"
}
""");
AlipayTradeQueryResponse response = alipayClient.execute(request);
System.out.println("code=" + response.getCode());
System.out.println("subCode=" + response.getSubCode());
System.out.println("tradeStatus=" + response.getTradeStatus());
System.out.println("body=" + response.getBody());
如果能够查到交易,应按当前交易状态继续处理,不要再创建同一笔订单。确认查不到交易后,再换用新的订单号测试。
建议的排查顺序
- 确认请求地址是沙箱网关。
- 确认
appid和所有密钥来自同一个沙箱应用。 - 确认 SDK 中填写的是支付宝公钥,而不是应用公钥。
- 确认平台与代码使用相同的
sign_type,通常是RSA2。 - 使用全新的
out_trade_no和最小业务参数测试。 - 完整记录 HTTP 状态、响应体、
code、sub_code、msg和sub_msg。 - 从实际部署机器测试沙箱网关的连通性。
- 请求超时后,调用
alipay.trade.query核实交易状态。 - 检查
notifyUrl是否能从公网访问,以及通知验签逻辑是否正确。 - 如果更换网络和时间后仍能稳定复现,且签名与参数都没有异常,再带上完整的请求时间和响应信息联系支付宝技术支持。
生产环境还需要设置合理的连接超时和读取超时,并在请求超时后查询订单状态。支付接口不能把“没有收到响应”直接视为“支付失败”,否则可能造成重复下单或订单状态不一致。
备注:内容仅供参考。
