支付宝退款接口多次返回 aop.ACQ.SYSTEM_ERROR
结论
code=20000、sub_code=aop.ACQ.SYSTEM_ERROR 表示支付宝在处理退款请求时发生了系统级异常,但仅凭这两个字段无法判断具体的业务原因。
这个问题在不同时间重试仍然失败,同一商品重新支付后也无法退款,而其他订单可以正常处理。因此,更可能与该商品对应的退款请求数据、订单属性、资金状态或风控校验有关,而不是普通的短时网络波动。
不要持续无条件重试。先查询退款结果,排除退款已经受理、但响应异常的情况,再逐项对比正常订单和异常订单的请求参数。如果仍然无法定位,应携带 trace_id、订单号和请求时间,提交支付宝技术支持排查内部链路。
如何理解返回结果
响应中的主要字段如下:
code: 20000
msg: Service Currently Unavailable
sub_code: aop.ACQ.SYSTEM_ERROR
sub_msg: 系统异常
refund_fee: 0.00
send_back_fee: 0.00
这类响应没有给出明确的退款成功结果。不过,refund_fee=0.00 也不能单独证明退款已经失败。调用方收到异常响应时,服务端是否已经开始处理,仍需通过查询接口确认。
日志中的两条 trace_id 需要保留:
0600828f174117902694672348797
060d1ea6174117877065416292646
支付宝技术支持可以根据这些信息定位对应的服务端调用链。
可能原因
1. 退款请求中的商品数据异常
如果退款请求传入了 goods_detail,而异常总是出现在某个商品上,应先检查该商品生成的字段:
goods_idalipay_goods_idgoods_namequantitypricegoods_categorycategories_treeshow_url
检查时要特别留意空字符串、内容过长、特殊控制字符、金额精度、数量与单价不匹配,以及序列化后的字段类型错误。
普通退款通常不要求传入 goods_detail。如果业务允许,可以创建新的测试订单,不传 goods_detail,进行一次小额验证。不要直接用真实的失败订单反复试验,否则可能产生重复退款。
2. 订单资金或交易状态特殊
异常订单可能带有普通订单没有的属性,例如:
- 使用了红包、优惠券、商家券或平台补贴;
- 存在分账、结算、资金冻结或特殊资金渠道;
- 已经发生过部分退款;
- 订单处于关闭、争议或其他处理中状态;
- 退款金额超过当前可退金额;
- 支付产品、收款账号或资金路由与正常订单不同。
即使商品相同,重新支付生成的新订单也可能再次命中相同的优惠、资金路由或业务规则。因此,重新下单后仍然失败,并不能单独证明问题出在商品字段上。
3. out_request_no 使用不当
每笔退款请求都应使用稳定且唯一的 out_request_no。同一次退款发生重试时,应继续使用原来的退款请求号。如果每次重试都生成新号码,支付宝可能会将这些请求识别为多笔退款。
还需要检查以下情况:
- 不同订单误用了相同的
out_request_no; - 同一个
out_request_no对应的退款金额发生变化; - 本地请求超时后,又生成新的退款单再次调用;
- 多个任务同时处理同一笔退款。
4. 支付宝内部异常或风控拦截
aop.ACQ.SYSTEM_ERROR 是通用系统异常。调用方无法根据这条响应确认支付宝内部具体在哪个环节失败。
如果请求参数、交易状态和幂等控制都没有问题,而且故障可以在特定订单或商品上稳定复现,就需要由支付宝根据 trace_id 查询内部日志。
建议的排查步骤
第一步:停止无条件重复退款
为同一笔业务退款建立唯一记录,并始终使用同一个 out_request_no。收到超时、20000 或其他结果不确定的响应后,先查询退款结果,再判断是否需要重试。
第二步:查询退款结果
调用 alipay.trade.fastpay.refund.query,传入原支付订单号和退款时使用的 out_request_no。
{
"out_trade_no": "20250305194600",
"out_request_no": "REFUND_20250305194600_001"
}
如果查询确认退款成功,应更新本地状态,不能再次发起退款。只有明确查询不到这笔退款请求时,才能进入重试或人工处理流程。
第三步:查询原交易状态
调用 alipay.trade.query,检查以下内容:
- 交易是否存在;
- 当前交易状态;
- 实收金额;
- 已退款金额;
- 当前剩余可退金额;
- 支付渠道及优惠信息。
退款金额应以当前可退金额为准,不能只根据本地订单金额计算。
第四步:对比完整请求参数
逐字段对比一笔正常订单和一笔异常订单的退款请求,特别检查:
app_id
seller_id
out_trade_no / trade_no
refund_amount
out_request_no
refund_reason
goods_detail
refund_royalty_parameters
query_options
对比对象应是发送给支付宝前的最终请求 JSON,而不只是业务数据库里的原始数据。
第五步:检查商户侧日志和并发控制
建议保存以下信息:
- 接口名称;
app_id;out_trade_no和支付宝trade_no;out_request_no;- 请求时间及毫秒时间戳;
- 完整业务参数,敏感信息需要脱敏;
- 原始响应;
trace_id;- SDK 异常堆栈;
- 查询接口返回的结果。
同时确认定时任务、消息消费者和人工操作没有并发发起同一笔退款。
第六步:提交支付宝技术支持
如果查询结果仍不明确,或者同类订单能够稳定复现,应提交工单并附上以下信息:
接口:alipay.trade.refund
商户 app_id:脱敏后提供,工单中可填写完整值
商户订单号:20250305194600、20250305200713
退款请求号:对应的 out_request_no
请求时间:精确到秒
trace_id:
0600828f174117902694672348797
060d1ea6174117877065416292646
返回码:20000
子错误码:aop.ACQ.SYSTEM_ERROR
工单中还应注明其他订单可以正常退款,以及问题与某类商品或订单属性存在稳定关联,帮助技术支持缩小排查范围。
Java 处理示例
下面的代码用于说明处理思路。具体类名和可用方法可能因支付宝 Java SDK 版本而异,应以项目实际使用的 SDK 为准。
String outTradeNo = "20250305194600";
String outRequestNo = "REFUND_20250305194600_001";
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo(outTradeNo);
model.setOutRequestNo(outRequestNo);
model.setRefundAmount("100.00");
model.setRefundReason("用户申请退款");
request.setBizModel(model);
AlipayTradeRefundResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
// 记录支付宝返回信息,并将本地退款单更新为成功。
markRefundSuccess(outRequestNo, response.getRefundFee());
} else if ("20000".equals(response.getCode())
|| "aop.ACQ.SYSTEM_ERROR".equals(response.getSubCode())) {
// 此时结果不确定,不应立即生成新的 out_request_no 再次退款。
markRefundPending(outRequestNo);
// 后续使用原 out_request_no 调用退款查询接口。
queryRefundResult(outTradeNo, outRequestNo);
} else {
// 明确的业务失败应记录 code、sub_code、sub_msg,并按具体原因处理。
markRefundFailed(
outRequestNo,
response.getCode(),
response.getSubCode(),
response.getSubMsg()
);
}
退款查询示例:
AlipayTradeFastpayRefundQueryRequest queryRequest =
new AlipayTradeFastpayRefundQueryRequest();
AlipayTradeFastpayRefundQueryModel queryModel =
new AlipayTradeFastpayRefundQueryModel();
queryModel.setOutTradeNo(outTradeNo);
queryModel.setOutRequestNo(outRequestNo);
queryRequest.setBizModel(queryModel);
AlipayTradeFastpayRefundQueryResponse queryResponse =
alipayClient.execute(queryRequest);
if (queryResponse.isSuccess()) {
// 应结合 SDK 实际返回字段判断退款结果和退款金额。
markRefundSuccess(outRequestNo, queryResponse.getRefundAmount());
} else {
// 查询失败不等于退款失败,保留待确认状态并记录原始响应。
markRefundPending(outRequestNo);
}
重试策略
只有查询确认尚未退款,而且当前错误属于可重试类型时,才能进行有限次数的重试。可以采用指数退避,并保持原 out_request_no 不变,例如:
第 1 次:延迟 5 秒
第 2 次:延迟 30 秒
第 3 次:延迟 2 分钟
仍失败:转人工处理或提交支付宝工单
具体间隔和重试次数应根据业务时效确定,不能将这些示例数值视为支付宝的官方要求。
注意事项
- 不要只根据
msg或sub_msg判断最终退款状态。 - 不要因为响应中的
refund_fee=0.00就创建新的退款请求。 - 同一次退款重试必须保持
out_request_no、退款金额和退款目标一致。 - 确认支付宝订单号
trade_no与商户订单号out_trade_no的对应关系,避免查错订单。 - 签名成功只表示响应可以通过验证,不表示退款业务已经成功。
- 不要公开传播日志中的签名、密钥、用户身份信息和完整支付数据。
- 如果只有包含
goods_detail的请求失败,应重点检查商品字段。如果退款请求没有传入商品明细,则应排查交易资金结构、优惠、分账、订单状态和支付宝内部链路。
备注:内容仅供参考。
