退款期间支付宝异步回调中的订单状态是什么?
结论
支付宝重试发送的支付异步通知,通常仍会带上原支付事件的状态,例如 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. 完成验签和业务校验后返回成功
处理支付宝通知时,至少要完成以下步骤:
- 验证通知签名。
- 校验
app_id。 - 确认
out_trade_no属于当前商户。 - 校验
seller_id、金额、币种等关键字段。 - 幂等处理业务。
- 处理成功后,按接口要求返回
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,否则支付宝可能继续重试。 - 发生部分退款时,支付交易仍可能保持支付成功状态。本地需要分别记录累计退款金额、退款单号和退款状态,不能只依赖一个订单状态字段。
备注:内容仅供参考。
