PAYATHON 2026

支付宝沙箱Demo付款成功但页面提示网络系统繁忙

支付老李

结论

沙箱买家余额已经扣减,通常表示支付宝侧的交易已经创建,当前状态可能是成功,也可能仍在处理中。页面显示“网络系统繁忙”不等于付款失败,常见原因包括:

  • 支付宝沙箱环境临时异常或正在维护,同步跳转页面未能正常显示。
  • 商户的 return_url 处理失败,浏览器跳回商户页面后发生异常。
  • 支付宝无法访问 notify_url,或者通知验签、业务处理失败,导致本地订单状态没有更新。
  • 商户系统因为没有收到同步返回,错误地将订单判定为支付失败。
  • 重复使用同一个 out_trade_no,导致订单状态与页面逻辑不一致。

不要根据付款后的页面提示判断支付结果。应使用 out_trade_no 调用 alipay.trade.query,查询支付宝侧的实际交易状态,再结合异步通知日志继续排查。

先确认哪一层出现异常

支付过程包含三个彼此独立的环节:

  1. 用户在支付宝收银台完成付款。
  2. 支付宝通过 notify_url 异步通知商户服务器。
  3. 支付宝通过 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 进行补偿确认。

排查沙箱环境异常

沙箱的稳定性不一定与正式环境相同。如果代码此前运行正常,问题集中出现在某个时间段,而且交易查询、密钥和回调配置都没有异常,就需要考虑沙箱服务临时不可用的可能。

可以按以下步骤确认:

  1. 保存问题发生时间、out_trade_no、trade_no,以及接口返回的 code、sub_code 和 sub_msg。
  2. 使用全新的 out_trade_no 再测试一次,排除重复订单号的干扰。
  3. 等待一段时间后重试,避免连续高频提交。
  4. 使用 alipay.trade.query 核对旧订单的状态。
  5. 检查支付宝开放平台控制台是否发布了沙箱公告或异常提示。
  6. 如果问题持续存在,携带请求时间和交易标识向支付宝技术支持反馈。

仅凭“周日上午”不能断定支付宝当时正在维护。没有公告或接口证据时,沙箱波动只能作为一种可能原因,不能直接下结论。

建议的修复方案

支付流程可以调整为:

创建并保存本地订单
        ↓
使用唯一 out_trade_no 发起支付
        ↓
用户完成付款
        ↓
异步通知验签并幂等更新订单
        ↓
未收到通知时主动调用 alipay.trade.query 补偿
        ↓
前端只展示后端确认后的订单状态

同时需要注意:

  • 每笔业务订单都应使用唯一的 out_trade_no。
  • 不要因为同步页面报错就重新扣款或重新创建业务订单。
  • 异步通知可能重复到达,数据库更新必须保证幂等。
  • 日志中不要输出应用私钥、完整签名配置或买家敏感信息。
  • 正式环境和沙箱环境的 app_id、支付宝公钥、买家账号及网关必须配套使用。
  • 页面报错后先查询交易状态,再决定是否重试,以免产生重复交易。

针对这一现象,建议先用 out_trade_no 查询支付宝侧的交易状态,再检查 notify_url 日志和本地订单更新逻辑,最后确认是否由沙箱临时异常引起。

备注:内容仅供参考。