支付宝支付后如何通过接口原路退款?
结论
支付宝支付成功后,应通过支付宝开放平台的 alipay.trade.refund(统一收单交易退款)接口办理退款。只要退款关联的是原支付交易,支付宝就会按照该交易支持的退款路径处理。商户无须也不应自行向客户的支付宝账户转账。
退款时,可以使用支付宝交易号 trade_no 或商户订单号 out_trade_no 定位原交易,并通过 refund_amount 指定退款金额。如果需要部分退款或多次退款,每次都必须设置唯一的 out_request_no,避免发生重复退款。
调用流程
- 保存支付成功后返回的支付宝交易号
trade_no。 - 在服务端校验退款订单、退款金额和当前退款状态。
- 调用
alipay.trade.refund。 - 检查接口返回的业务结果。
- 如果请求超时、网络异常或结果无法确定,调用
alipay.trade.fastpay.refund.query查询最终状态。 - 确认退款成功后,再更新本地订单和退款记录。
退款接口必须由服务端调用。应用私钥、商户密钥等敏感信息不能放在浏览器或 App 客户端中。
主要请求参数
| 参数 | 是否必填 | 说明 |
|---|---|---|
trade_no | 二选一 | 支付宝交易号,建议优先使用 |
out_trade_no | 二选一 | 商户原支付订单号 |
refund_amount | 是 | 退款金额,不能超过该交易当前可退金额 |
out_request_no | 建议填写 | 商户退款请求号,用于标识一次退款请求 |
refund_reason | 否 | 退款原因 |
trade_no 和 out_trade_no 通常任选其一即可。如果系统已经保存了 trade_no,建议直接使用,这样可以更准确地定位原支付交易。
Java 调用示例
下面的示例使用支付宝 Java SDK。具体的依赖版本、客户端初始化方式和证书配置,应以项目实际使用的支付宝 SDK 和开放平台配置为准。
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import java.math.BigDecimal;
public class AlipayRefundService {
private final AlipayClient alipayClient;
public AlipayRefundService(
String appId,
String merchantPrivateKey,
String alipayPublicKey) {
this.alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
}
public AlipayTradeRefundResponse refund(
String tradeNo,
String refundRequestNo,
BigDecimal refundAmount) throws AlipayApiException {
if (tradeNo == null || tradeNo.isBlank()) {
throw new IllegalArgumentException("tradeNo 不能为空");
}
if (refundRequestNo == null || refundRequestNo.isBlank()) {
throw new IllegalArgumentException("refundRequestNo 不能为空");
}
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("退款金额必须大于 0");
}
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setTradeNo(tradeNo);
model.setOutRequestNo(refundRequestNo);
model.setRefundAmount(refundAmount.toPlainString());
model.setRefundReason("用户申请退款");
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
request.setBizModel(model);
AlipayTradeRefundResponse response = alipayClient.execute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"支付宝退款受理失败,code=" + response.getCode()
+ ", subCode=" + response.getSubCode()
+ ", message=" + response.getSubMsg()
);
}
return response;
}
}
调用示例:
AlipayRefundService refundService = new AlipayRefundService(
"应用APP_ID",
"商户应用私钥",
"支付宝公钥"
);
AlipayTradeRefundResponse response = refundService.refund(
"支付宝交易号",
"REFUND_20260909_000001",
new BigDecimal("100.00")
);
System.out.println("退款响应:" + response.getBody());
System.out.println("支付宝交易号:" + response.getTradeNo());
System.out.println("退款金额:" + response.getRefundFee());
System.out.println("资金是否发生变化:" + response.getFundChange());
如果没有保存支付宝交易号,也可以改用商户订单号:
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo("PAY_20260909_000001");
model.setOutRequestNo("REFUND_20260909_000001");
model.setRefundAmount("100.00");
model.setRefundReason("用户申请退款");
不要同时传入错误的 trade_no 和 out_trade_no,否则支付宝可能无法定位交易。
如何判断退款结果
HTTP 请求成功不代表退款已经成功,还需要检查接口返回的业务结果:
if (response.isSuccess()) {
// 接口业务处理成功,继续记录返回的退款信息
} else {
// 记录 code、subCode、subMsg,并按业务规则处理
}
还需要关注这些字段:
code:支付宝网关业务结果码。sub_code:失败时返回的具体业务错误码。sub_msg:失败原因说明。refund_fee:本次退款金额。fund_change:本次调用是否实际引起资金变化。
如果请求超时、连接中断,或者无法确认支付宝是否已经处理,不能直接生成新的退款请求号再次发起退款。应使用原来的 out_request_no 查询或重试,以免同一笔退款被处理多次。
查询退款结果
使用 alipay.trade.fastpay.refund.query 查询退款结果时,需要提供原交易标识和退款请求号。
import com.alipay.api.domain.AlipayTradeFastpayRefundQueryModel;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
AlipayTradeFastpayRefundQueryModel queryModel =
new AlipayTradeFastpayRefundQueryModel();
queryModel.setTradeNo("支付宝交易号");
queryModel.setOutRequestNo("REFUND_20260909_000001");
AlipayTradeFastpayRefundQueryRequest queryRequest =
new AlipayTradeFastpayRefundQueryRequest();
queryRequest.setBizModel(queryModel);
AlipayTradeFastpayRefundQueryResponse queryResponse =
alipayClient.execute(queryRequest);
if (queryResponse.isSuccess()) {
System.out.println("退款查询成功:" + queryResponse.getBody());
} else {
System.out.println(
"退款查询失败,code=" + queryResponse.getCode()
+ ", subCode=" + queryResponse.getSubCode()
+ ", message=" + queryResponse.getSubMsg()
);
}
业务实现建议
建议为退款记录单独建表,不要只在支付订单上增加一个简单的退款状态。表中至少应保存:
- 原商户订单号;
- 支付宝交易号;
- 商户退款请求号;
- 申请退款金额;
- 已成功退款金额;
- 退款状态;
- 支付宝响应码及原始响应;
- 创建时间和完成时间。
退款接口还需要检查以下条件:
本次退款金额 > 0
累计退款金额 + 本次退款金额 <= 原订单实付金额
原订单确实支付成功
退款请求号在商户系统内唯一
同一退款请求不能并发执行
处理金额时,建议使用 BigDecimal 或以“分”为单位的整数,不要使用 double,否则可能产生精度问题。
注意事项
- “原路退款”由支付宝根据原交易和支付资金路径处理,商户不能通过退款接口指定其他支付宝账户。
- 退款金额不能超过当前可退金额。是否支持部分退款、退款期限以及到账时间,取决于签约产品、交易状态和支付宝规则。
out_request_no是控制退款幂等的重要依据,同一次退款请求应始终使用同一个编号。- 多次部分退款时,每次退款都应使用不同的
out_request_no。 - 支付宝接口返回失败后,不要先把本地订单标记为退款成功。
- 网络超时不代表退款失败。应先查询结果,再决定是否重试。
- 私钥、支付宝公钥或证书必须安全地保存在服务端,不能写入前端代码,也不能提交到公开代码仓库。
- 正式环境使用
https://openapi.alipay.com/gateway.do。沙箱环境的网关、应用和账号配置应与正式环境隔离。 - 如果项目使用支付宝证书模式,应按照 SDK 对应的证书客户端初始化方式进行配置,不能把证书内容直接当作普通支付宝公钥使用。
备注:内容仅供参考。
