PAYATHON 2026

电脑网站支付订单过期后未收到 TRADE_CLOSED 回调

支付小周

结论

这两类状态不需要分别配置回调地址。电脑网站支付产生的交易状态通知,都会发送到调用 alipay.trade.page.pay 时设置的 notify_url。既然已经能正常收到 TRADE_SUCCESS,异步通知地址和基础网络配置就是有效的。

还要注意以下几点:

  • WAIT_BUYER_PAY 只是交易状态,不能可靠地表示“用户已经扫码”。支付宝不保证所有中间状态都会按照顺序逐一通知。
  • timeout_express 表示订单可以付款的时限,不能当作商户系统的精确定时器。订单超时后,支付宝侧的关闭处理可能有延迟,因此本地关单不能只依赖 TRADE_CLOSED 通知。
  • 开放平台中的“应用网关”和“消息接收地址”不是电脑网站支付交易状态通知的配置项,无需为这两个状态另行订阅。

检查支付请求

每次调用 alipay.trade.page.pay 时,都应传入异步通知地址。只在本地业务系统中保存该地址是不够的。

以下为 Java SDK 示例:

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

request.setReturnUrl("https://www.example.com/alipay/return");
request.setNotifyUrl("https://www.example.com/alipay/notify");

request.setBizContent("""
{
  "out_trade_no": "MERCHANT_ORDER_20201023_001",
  "product_code": "FAST_INSTANT_TRADE_PAY",
  "total_amount": "100.00",
  "subject": "测试订单",
  "timeout_express": "30m"
}
""");

String form = alipayClient.pageExecute(request).getBody();

timeout_express 必须写在 biz_content 中。可用格式以及最小、最大取值,应以当前接入产品的支付宝开放平台文档为准,不要传入该接口不支持的格式。

如果同一地址已经收到支付成功通知,通常不需要修改 notify_url。此时应检查超时订单在支付宝侧是否确实已经变为 TRADE_CLOSED。

正确处理异步通知

通知接口应先保存原始请求并验证签名,再按 trade_status 分别处理,不能只接收 TRADE_SUCCESS。

@PostMapping("/alipay/notify")
@ResponseBody
public String alipayNotify(HttpServletRequest request) throws Exception {
    Map<String, String> params = new HashMap<>();

    for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
        params.put(entry.getKey(), String.join(",", entry.getValue()));
    }

    boolean verified = AlipaySignature.rsaCheckV1(
        params,
        ALIPAY_PUBLIC_KEY,
        "UTF-8",
        "RSA2"
    );

    if (!verified) {
        return "failure";
    }

    String appId = params.get("app_id");
    String outTradeNo = params.get("out_trade_no");
    String tradeNo = params.get("trade_no");
    String tradeStatus = params.get("trade_status");

    if (!EXPECTED_APP_ID.equals(appId)) {
        return "failure";
    }

    // 还应校验 out_trade_no、seller_id、金额和币种等业务字段。

    switch (tradeStatus) {
        case "WAIT_BUYER_PAY":
            // 可记录支付宝侧已创建交易,但不要据此判断用户一定完成了扫码。
            break;

        case "TRADE_SUCCESS":
        case "TRADE_FINISHED":
            // 使用幂等方式更新支付状态。
            break;

        case "TRADE_CLOSED":
            // 仅在本地订单尚未支付时更新为关闭状态。
            break;

        default:
            // 记录未知状态,避免直接丢弃。
            break;
    }

    return "success";
}

通知参数应在判断状态前完整保存。一种常见情况是,业务代码只记录支付成功分支,其他状态虽然已经到达,却没有留下日志。

处理完成后,接口必须直接返回以下纯文本:

success

不要返回 JSON、HTML 或附加任何其他字符。如果处理失败,应返回 failure,由支付宝按照通知重试机制再次发送。

超时订单的推荐处理方式

商户系统需要自行维护订单截止时间,并结合主动查询和关单完成处理:

  1. 创建订单时保存本地过期时间,同时向支付宝传入合法的 timeout_express。
  2. 本地订单到期后,调用 alipay.trade.query 查询支付宝侧的交易状态。
  3. 如果状态仍是 WAIT_BUYER_PAY,调用 alipay.trade.close 主动关闭交易。
  4. 根据关单接口的响应或再次查询的结果,更新本地订单。
  5. 后续收到异步通知时,仍按幂等方式处理。

查询示例:

AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
queryRequest.setBizContent("""
{
  "out_trade_no": "MERCHANT_ORDER_20201023_001"
}
""");

AlipayTradeQueryResponse queryResponse =
    alipayClient.execute(queryRequest);

主动关单示例:

AlipayTradeCloseRequest closeRequest = new AlipayTradeCloseRequest();
closeRequest.setBizContent("""
{
  "out_trade_no": "MERCHANT_ORDER_20201023_001"
}
""");

AlipayTradeCloseResponse closeResponse =
    alipayClient.execute(closeRequest);

调用关单接口后,不一定会立即收到 TRADE_CLOSED 通知。判断订单状态时,还应参考关单接口的响应和后续查询结果。

排查建议

可以针对给出的支付宝订单号,在支付宝商家后台或通过 alipay.trade.query 查询实际状态和关闭时间。本地截止时间已到,并不代表支付宝侧已经关闭了交易。

还应逐项检查:

  • 对应的支付请求是否确实传入了 notify_url。
  • 通知入口是否记录了全部 POST 参数,而不是只记录成功状态。
  • Web 服务器、网关或 WAF 是否拦截了部分请求。
  • 通知接口是否超时、发生重定向或返回了非 200 状态码。
  • 验签代码是否错误过滤了 TRADE_CLOSED 或 WAIT_BUYER_PAY。
  • 是否混淆了商户订单号 out_trade_no 和支付宝交易号 trade_no。
  • 回调中的 app_id、商户身份和订单金额是否与本地订单一致。
  • 订单是否因为支付成功、全额退款或其他操作进入了不同的最终状态。

支付通知可以推动本地状态更新,但不能作为唯一依据。可靠的订单系统还需要配合本地超时任务、交易查询和主动关单。

备注:内容仅供参考。