如何根据 trade_no 查询支付宝交易的所有退款记录?
结论
支付宝通常没有“仅凭 trade_no 实时列出一笔交易全部退款明细”的接口。
alipay.trade.fastpay.refund.query 查询的是单笔退款,需要通过退款时提交的 out_request_no 定位具体请求。比较可靠的处理方式是:
- 商户为每次退款生成唯一的
out_request_no。 - 在本地保存
trade_no、out_request_no、退款金额和处理状态。 - 需要查看全部退款时,按
trade_no查询本地退款流水。 - 对结果不明确的记录,调用支付宝退款查询接口逐笔确认。
- 定期使用支付宝账单对账,处理接口超时、系统故障等异常。
还要注意,TRADE_CLOSED 既可能表示交易已经全额退款,也可能表示未支付交易超时关闭或被主动关闭。不能只根据这个状态认定交易已经全额退款。
为什么不能只通过 trade_no 查询
一笔支付宝交易可以发起多次部分退款。例如,一笔 100 元的交易可能产生三次退款:
| trade_no | out_request_no | 退款金额 |
|---|---|---|
| 2026xxx | REFUND-001 | 20.00 |
| 2026xxx | REFUND-002 | 30.00 |
| 2026xxx | REFUND-003 | 10.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 调用单笔退款查询接口,枚举出所有退款请求。可以采用以下补救办法:
- 从原有订单日志、退款接口日志或数据库备份中恢复
out_request_no。 - 下载支付宝交易账单或账务明细,根据交易号、退款时间和金额等信息进行核对。
- 把对账结果补录到本地退款流水表。
- 无法自动匹配的记录交由人工核对。
账单下载能力、账单字段和可查询时间范围取决于支付宝产品及商户权限,应以商户当前签约产品的开放平台文档为准。账单适合离线对账和数据修复,不适合作为实时退款列表接口。
状态判断注意事项
不要只用 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 从本地汇总全部退款
↓
通过支付宝账单定期对账
这套设计可以处理一笔交易多次部分退款,也能应对接口超时、重复请求、并发退款和历史数据核对。
备注:内容仅供参考。
