PAYATHON 2026

如何根据 trade_no 查询支付宝交易的所有退款记录?

支付小周

结论

支付宝通常没有“仅凭 trade_no 实时列出一笔交易全部退款明细”的接口。

alipay.trade.fastpay.refund.query 查询的是单笔退款,需要通过退款时提交的 out_request_no 定位具体请求。比较可靠的处理方式是:

  1. 商户为每次退款生成唯一的 out_request_no。
  2. 在本地保存 trade_no、out_request_no、退款金额和处理状态。
  3. 需要查看全部退款时,按 trade_no 查询本地退款流水。
  4. 对结果不明确的记录,调用支付宝退款查询接口逐笔确认。
  5. 定期使用支付宝账单对账,处理接口超时、系统故障等异常。

还要注意,TRADE_CLOSED 既可能表示交易已经全额退款,也可能表示未支付交易超时关闭或被主动关闭。不能只根据这个状态认定交易已经全额退款。

为什么不能只通过 trade_no 查询

一笔支付宝交易可以发起多次部分退款。例如,一笔 100 元的交易可能产生三次退款:

trade_noout_request_no退款金额
2026xxxREFUND-00120.00
2026xxxREFUND-00230.00
2026xxxREFUND-00310.00

这三次退款属于同一笔交易,但每次都是独立的退款请求。支付宝通过 out_request_no 区分这些请求,退款查询接口也以单次退款为查询粒度。

trade_no 只能定位原交易,不能确定要查询的是其中哪次退款,因此无法替代 out_request_no。

推荐的数据设计

商户系统应维护单独的退款流水表,不要只在订单表中记录累计退款金额。

CREATE TABLE payment_refund (
    id BIGINT PRIMARY KEY,
    trade_no VARCHAR(64) NOT NULL,
    out_trade_no VARCHAR(64) NOT NULL,
    out_request_no VARCHAR(64) NOT NULL,
    refund_amount DECIMAL(18, 2) NOT NULL,
    refund_reason VARCHAR(256),
    refund_status VARCHAR(32) NOT NULL,
    alipay_response TEXT,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    UNIQUE KEY uk_out_request_no (out_request_no),
    KEY idx_trade_no (trade_no)
);

refund_status 可以按业务需要设计,例如:

  • PROCESSING:退款正在处理,或者结果暂时无法确定
  • SUCCESS:退款成功
  • FAILED:退款失败
  • CLOSED:退款请求已关闭

这些状态名称是商户系统的内部设计,不是支付宝接口的返回值。更新状态时,应以实际接口响应为准。

退款处理步骤

1. 为每次退款生成唯一请求号

每次退款都要生成新的 out_request_no,例如:

REFUND-20260908-000001

如果同一次退款因超时需要重试,必须继续使用原来的 out_request_no。重新生成请求号可能导致支付宝将其识别为另一笔退款请求。

2. 调用退款接口前先保存本地记录

先写入一条状态为 PROCESSING 的退款流水,再调用 alipay.trade.refund。即使调用过程中出现超时,系统也知道应该查询哪个 out_request_no。

String outRequestNo = "REFUND-20260908-000001";

refundRepository.insert(
    tradeNo,
    outTradeNo,
    outRequestNo,
    new BigDecimal("20.00"),
    "PROCESSING"
);

AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
request.setBizContent("""
{
  "trade_no": "%s",
  "refund_amount": "20.00",
  "refund_reason": "商品退货",
  "out_request_no": "%s"
}
""".formatted(tradeNo, outRequestNo));

AlipayTradeRefundResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    refundRepository.markSuccess(
        outRequestNo,
        response.getBody()
    );
} else {
    /*
     * 不能把网络超时或未知响应直接视为退款失败。
     * 对结果不明确的请求,应保留 PROCESSING 状态并继续查询。
     */
    refundRepository.keepProcessing(
        outRequestNo,
        response.getBody()
    );
}

实际项目还要区分两类情况:支付宝明确返回业务失败,以及请求超时、连接中断或响应无法解析。只有退款结果明确后,才适合把记录更新为最终状态。

3. 对结果不明确的退款逐笔查询

使用原交易号和退款请求号调用 alipay.trade.fastpay.refund.query:

AlipayTradeFastpayRefundQueryRequest request =
    new AlipayTradeFastpayRefundQueryRequest();

request.setBizContent("""
{
  "trade_no": "%s",
  "out_request_no": "%s"
}
""".formatted(tradeNo, outRequestNo));

AlipayTradeFastpayRefundQueryResponse response =
    alipayClient.execute(request);

if (response.isSuccess()) {
    refundRepository.markSuccess(
        outRequestNo,
        response.getBody()
    );
} else {
    /*
     * 根据实际 sub_code、sub_msg 和请求状态判断:
     * 继续重试、标记明确失败,或者转入人工处理。
     */
    refundRepository.recordQueryResult(
        outRequestNo,
        response.getBody()
    );
}

SDK 类名和响应字段可能因支付宝 Java SDK 版本不同而变化,应以当前接入产品的接口文档和 SDK 定义为准。

4. 根据 trade_no 查询全部退款

需要查看全部退款明细时,直接查询本地退款表:

SELECT
    trade_no,
    out_request_no,
    refund_amount,
    refund_status,
    created_at,
    updated_at
FROM payment_refund
WHERE trade_no = ?
ORDER BY created_at ASC;

累计成功退款金额可以这样计算:

SELECT COALESCE(SUM(refund_amount), 0) AS refunded_amount
FROM payment_refund
WHERE trade_no = ?
  AND refund_status = 'SUCCESS';

剩余可退款金额为:

剩余可退款金额 = 原交易可退款金额 - 已成功退款金额

计算时要以实际支付金额和支付宝产品规则为准,不能直接使用商品标价。优惠、红包、积分、服务费或组合支付都可能影响退款金额的组成。

历史退款没有保存 out_request_no 怎么办

如果旧系统没有保存退款请求号,通常无法只凭 trade_no 调用单笔退款查询接口,枚举出所有退款请求。可以采用以下补救办法:

  1. 从原有订单日志、退款接口日志或数据库备份中恢复 out_request_no。
  2. 下载支付宝交易账单或账务明细,根据交易号、退款时间和金额等信息进行核对。
  3. 把对账结果补录到本地退款流水表。
  4. 无法自动匹配的记录交由人工核对。

账单下载能力、账单字段和可查询时间范围取决于支付宝产品及商户权限,应以商户当前签约产品的开放平台文档为准。账单适合离线对账和数据修复,不适合作为实时退款列表接口。

状态判断注意事项

不要只用 TRADE_CLOSED 判断全额退款

交易关闭可能有多种原因,包括:

  • 未支付交易超时关闭;
  • 商户主动关闭未支付交易;
  • 支付成功后发生全额退款。

判断交易是否已经全额退款,至少要结合以下信息:

  • 交易是否曾经支付成功;
  • 本地成功退款总金额;
  • 原交易实际支付金额;
  • 支付宝查询结果和账单对账结果。

接口超时不等于退款失败

退款请求可能已经到达支付宝,只是响应在网络传输途中丢失,因此商户收到的是超时结果。如果此时换一个新的 out_request_no 再次提交,可能会重复发起退款。

应保留原来的 out_request_no,通过退款查询接口确认结果。确实需要重试时,也要继续使用同一个请求号。

退款金额不能只靠一个累计字段维护

如果只更新订单表中的 refunded_amount,并发退款时容易发生数据覆盖或超额退款。建议配合使用:

  • 独立退款流水;
  • 数据库事务;
  • 唯一的 out_request_no;
  • 行锁或乐观锁;
  • 剩余可退款金额校验;
  • 定期账单对账。

最终建议

支付宝接口负责执行退款和确认单笔退款结果,商户数据库负责保存并查询完整的退款记录。完整流程如下:

生成 out_request_no
    ↓
保存本地退款流水
    ↓
调用 alipay.trade.refund
    ↓
明确成功则更新状态
    ↓
结果不明确则调用 alipay.trade.fastpay.refund.query
    ↓
按 trade_no 从本地汇总全部退款
    ↓
通过支付宝账单定期对账

这套设计可以处理一笔交易多次部分退款,也能应对接口超时、重复请求、并发退款和历史数据核对。

备注:内容仅供参考。