PAYATHON 2026

退款期间支付宝异步回调中的订单状态是什么?

支付小周

结论

支付宝重试发送的支付异步通知,通常仍会带上原支付事件的状态,例如 TRADE_SUCCESS,不会自动变成“退款”。

“退款”是下游系统中的业务订单状态,并不是支付成功通知里 trade_status 的标准取值。退款完成后,也不应依赖迟到或重复的支付回调更新退款状态。

原因

支付回调重试是为了让下游系统收到并确认此前的支付结果。如果下游没有按支付宝的要求返回成功响应,支付宝可能会继续发送同一条通知。

这里需要分清两类状态:

  • 支付状态:如 WAIT_BUYER_PAY、TRADE_SUCCESS、TRADE_FINISHED、TRADE_CLOSED
  • 业务状态:如“待支付”“已支付”“退款中”“已退款”

即使已经发生退款,之前未被成功确认的支付通知仍可能到达。这条通知表达的是“这笔交易曾经支付成功”,不能用来判断订单当前是否已经退款。

在部分产品或交易场景中,全额退款后主动查询交易,可能会得到 TRADE_CLOSED,也可能需要结合退款金额等字段判断退款结果。但此前支付通知的重试内容不一定随之变化,具体字段应以实际使用的支付宝产品和接口文档为准。

正确处理方式

1. 支付回调必须幂等

可以用支付宝交易号 trade_no 或商户订单号 out_trade_no 作为幂等依据。重复收到同一支付通知时,不要重复发货、重复记账,也不要覆盖处于后续阶段的业务状态。

2. 支付回调不能覆盖退款状态

如果本地订单已经处于“退款中”或“已退款”,之后又收到 TRADE_SUCCESS,只需记录这条支付通知已处理,不能把订单重新改成“已支付”。

状态应按单一方向流转,例如:

待支付 -> 已支付 -> 退款中 -> 已退款

不能出现:

已退款 -> 已支付

3. 通过退款流程确认退款结果

调用退款接口后,应根据接口返回结果更新退款记录。如果所用产品提供异步退款结果通知,还需要处理对应的通知。必要时,可调用退款查询接口进行补偿查询。

不要用支付成功回调判断退款是否完成。

4. 完成验签和业务校验后返回成功

处理支付宝通知时,至少要完成以下步骤:

  1. 验证通知签名。
  2. 校验 app_id。
  3. 确认 out_trade_no 属于当前商户。
  4. 校验 seller_id、金额、币种等关键字段。
  5. 幂等处理业务。
  6. 处理成功后,按接口要求返回 success。

代码示例

下面用 Java 伪代码说明如何保护订单状态。数据库字段和支付宝 SDK 的具体调用方式需按实际项目调整。

@Transactional
public String handlePayNotify(Map<String, String> params) {
    boolean valid = AlipaySignature.rsaCheckV1(
        params,
        alipayPublicKey,
        "UTF-8",
        "RSA2"
    );

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

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

    Order order = orderRepository.findByOutTradeNoForUpdate(outTradeNo);
    if (order == null) {
        return "failure";
    }

    // 还应校验 app_id、seller_id、total_amount 等字段。
    validateNotify(params, order);

    // 同一支付宝通知或同一支付结果只处理一次。
    if (notifyRepository.existsByTradeNoAndStatus(tradeNo, tradeStatus)) {
        return "success";
    }

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {

        // 退款中、已退款等更靠后的状态不能被迟到的支付通知覆盖。
        if (order.getStatus() == OrderStatus.PENDING_PAYMENT) {
            order.markPaid(tradeNo);
            orderRepository.save(order);
        }
    }

    notifyRepository.save(
        new PayNotifyRecord(outTradeNo, tradeNo, tradeStatus)
    );

    return "success";
}

还可以使用带条件的 SQL 更新状态,避免并发覆盖:

UPDATE orders
SET status = 'PAID',
    alipay_trade_no = :trade_no,
    paid_at = CURRENT_TIMESTAMP
WHERE out_trade_no = :out_trade_no
  AND status = 'PENDING_PAYMENT';

如果订单已经处于 REFUNDING 或 REFUNDED 状态,这条 SQL 不会更新任何记录,因此重复的支付通知无法把订单状态改回 PAID。

注意事项

  • 不要直接把支付宝的 trade_status 当作本地订单状态。
  • TRADE_SUCCESS 表示曾发生支付成功事件,不代表当前一定没有退款。
  • 退款接口调用成功和退款资金最终处理完成可能并不是同一件事,需要根据具体接口的返回字段判断。
  • 支付回调、退款操作和退款结果可能同时发生,可以使用数据库条件更新、行锁或版本号控制状态竞争。
  • 即使业务上无需再次处理某条重复通知,也要在完成验签和必要校验后正确返回 success,否则支付宝可能继续重试。
  • 发生部分退款时,支付交易仍可能保持支付成功状态。本地需要分别记录累计退款金额、退款单号和退款状态,不能只依赖一个订单状态字段。

备注:内容仅供参考。