支付宝沙箱Demo付款成功但页面提示网络系统繁忙
结论
沙箱买家余额已经扣减,通常表示支付宝侧的交易已经创建,当前状态可能是成功,也可能仍在处理中。页面显示“网络系统繁忙”不等于付款失败,常见原因包括:
- 支付宝沙箱环境临时异常或正在维护,同步跳转页面未能正常显示。
- 商户的
return_url处理失败,浏览器跳回商户页面后发生异常。 - 支付宝无法访问
notify_url,或者通知验签、业务处理失败,导致本地订单状态没有更新。 - 商户系统因为没有收到同步返回,错误地将订单判定为支付失败。
- 重复使用同一个
out_trade_no,导致订单状态与页面逻辑不一致。
不要根据付款后的页面提示判断支付结果。应使用 out_trade_no 调用 alipay.trade.query,查询支付宝侧的实际交易状态,再结合异步通知日志继续排查。
先确认哪一层出现异常
支付过程包含三个彼此独立的环节:
- 用户在支付宝收银台完成付款。
- 支付宝通过
notify_url异步通知商户服务器。 - 支付宝通过
return_url将浏览器跳回商户页面。
余额扣减只能说明第一个环节大概率已经完成。即使同步跳转或异步通知处理失败,支付宝侧的交易仍可能已经成功。
先记录并检查本次支付的以下信息:
- 商户订单号
out_trade_no - 支付宝交易号
trade_no - 应用 ID
app_id - 支付网关地址
- 异步通知原始参数
- 同步返回原始参数
- 商户服务器异常日志
如果发起支付前没有保存 out_trade_no,本地订单创建流程就需要调整。正确流程是先生成并持久化商户订单,再向支付宝发起支付请求。
使用交易查询确认实际状态
使用 out_trade_no 调用 alipay.trade.query。以下是 Java SDK 示例:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi-sandbox.dl.alipaydev.com/gateway.do",
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent(
"{\"out_trade_no\":\"" + outTradeNo + "\"}"
);
AlipayTradeQueryResponse response = alipayClient.execute(request);
System.out.println("code=" + response.getCode());
System.out.println("msg=" + response.getMsg());
System.out.println("subCode=" + response.getSubCode());
System.out.println("subMsg=" + response.getSubMsg());
System.out.println("tradeStatus=" + response.getTradeStatus());
System.out.println("tradeNo=" + response.getTradeNo());
网关地址应以当前支付宝开放平台沙箱页面提供的配置为准。如果 SDK 或沙箱环境给出了其他地址,应使用对应环境的实际配置,不要混用正式环境网关。
重点检查 trade_status:
TRADE_SUCCESS:支付成功,应补偿更新本地订单。TRADE_FINISHED:交易已经结束,可根据具体业务确认完成。WAIT_BUYER_PAY:订单已经创建,但支付宝尚未确认付款完成。TRADE_CLOSED:交易已经关闭。- 查询不到交易:检查
app_id、网关环境和out_trade_no是否一致。
只有查询接口明确返回成功,才能可靠判断支付宝侧的交易状态。页面文案不能作为支付结果的依据。
检查异步通知处理
如果本地没有生成付款信息,应优先检查 notify_url。
支付宝服务器必须能直接访问这个地址。下面两类地址通常不能用于沙箱回调:
http://localhost:8080/alipay/notify
http://127.0.0.1:8080/alipay/notify
在本地调试时,需要提供公网可访问的回调地址,或通过安全的内网穿透方式把请求转发到本机。还要逐项检查:
notify_url是否完整。- 接口是否受到登录验证、验证码或 CSRF 机制拦截。
- 防火墙或反向代理是否拦截了支付宝请求。
- 接口是否接受
POST表单参数。 - 验签时使用的是否为支付宝公钥,而非应用公钥。
- 字符集是否与配置一致。
- 业务处理成功后是否输出纯文本
success。
Java 异步通知处理可以按下面的方式组织:
@PostMapping("/alipay/notify")
@ResponseBody
public String alipayNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
if (values != null && values.length > 0) {
params.put(key, values[0]);
}
});
try {
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
String notifyId = params.get("notify_id");
// 必须再次核对 app_id、seller_id、订单金额及商户订单号。
// 更新操作应保证幂等,避免重复通知造成重复记账。
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
updateOrderIdempotently(
outTradeNo,
tradeNo,
totalAmount,
notifyId
);
}
return "success";
} catch (Exception e) {
// 记录完整异常和通知参数,但不要记录商户私钥等敏感信息。
return "failure";
}
}
通过验签后也不能立即更新订单,还应至少核对以下字段:
app_id
seller_id
out_trade_no
total_amount
trade_status
比较 total_amount 时,建议使用 BigDecimal,不要直接用 double 判断金额:
BigDecimal notifiedAmount = new BigDecimal(totalAmount);
BigDecimal orderAmount = order.getTotalAmount();
if (notifiedAmount.compareTo(orderAmount) != 0) {
return "failure";
}
检查同步返回页面
如果支付宝侧查询结果是交易成功,异步通知也已正常处理,但浏览器仍显示“网络系统繁忙”,问题很可能出在同步跳转或沙箱收银台页面。
检查 return_url 对应接口是否存在以下情况:
- 参数验签异常。
- 空指针或数据库查询异常。
- 把同步返回作为唯一的支付依据。
- 页面模板渲染失败。
- 出现 HTTP 500、重定向循环或 HTTPS 证书问题。
- 根据尚未更新的本地订单状态直接显示失败页面。
同步返回可以用来向用户展示结果,但不能作为最终记账依据。可以采用以下逻辑:
@GetMapping("/alipay/return")
public String alipayReturn(
HttpServletRequest request,
Model model) {
String outTradeNo = request.getParameter("out_trade_no");
// 此处可以验签并展示结果,但最终订单状态应以异步通知
// 或 alipay.trade.query 的结果为准。
Order order = orderService.findByOutTradeNo(outTradeNo);
model.addAttribute("order", order);
return "payment-result";
}
如果用户返回时本地订单仍处于“待支付”状态,页面可以短暂轮询商户后端,由后端查询订单状态。必要时,后端可调用 alipay.trade.query 进行补偿确认。
排查沙箱环境异常
沙箱的稳定性不一定与正式环境相同。如果代码此前运行正常,问题集中出现在某个时间段,而且交易查询、密钥和回调配置都没有异常,就需要考虑沙箱服务临时不可用的可能。
可以按以下步骤确认:
- 保存问题发生时间、
out_trade_no、trade_no,以及接口返回的code、sub_code和sub_msg。 - 使用全新的
out_trade_no再测试一次,排除重复订单号的干扰。 - 等待一段时间后重试,避免连续高频提交。
- 使用
alipay.trade.query核对旧订单的状态。 - 检查支付宝开放平台控制台是否发布了沙箱公告或异常提示。
- 如果问题持续存在,携带请求时间和交易标识向支付宝技术支持反馈。
仅凭“周日上午”不能断定支付宝当时正在维护。没有公告或接口证据时,沙箱波动只能作为一种可能原因,不能直接下结论。
建议的修复方案
支付流程可以调整为:
创建并保存本地订单
↓
使用唯一 out_trade_no 发起支付
↓
用户完成付款
↓
异步通知验签并幂等更新订单
↓
未收到通知时主动调用 alipay.trade.query 补偿
↓
前端只展示后端确认后的订单状态
同时需要注意:
- 每笔业务订单都应使用唯一的
out_trade_no。 - 不要因为同步页面报错就重新扣款或重新创建业务订单。
- 异步通知可能重复到达,数据库更新必须保证幂等。
- 日志中不要输出应用私钥、完整签名配置或买家敏感信息。
- 正式环境和沙箱环境的
app_id、支付宝公钥、买家账号及网关必须配套使用。 - 页面报错后先查询交易状态,再决定是否重试,以免产生重复交易。
针对这一现象,建议先用 out_trade_no 查询支付宝侧的交易状态,再检查 notify_url 日志和本地订单更新逻辑,最后确认是否由沙箱临时异常引起。
备注:内容仅供参考。
