PAYATHON 2026

支付宝支付后如何通过接口原路退款?

支付老李

结论

支付宝支付成功后,应通过支付宝开放平台的 alipay.trade.refund(统一收单交易退款)接口办理退款。只要退款关联的是原支付交易,支付宝就会按照该交易支持的退款路径处理。商户无须也不应自行向客户的支付宝账户转账。

退款时,可以使用支付宝交易号 trade_no 或商户订单号 out_trade_no 定位原交易,并通过 refund_amount 指定退款金额。如果需要部分退款或多次退款,每次都必须设置唯一的 out_request_no,避免发生重复退款。

调用流程

  1. 保存支付成功后返回的支付宝交易号 trade_no。
  2. 在服务端校验退款订单、退款金额和当前退款状态。
  3. 调用 alipay.trade.refund。
  4. 检查接口返回的业务结果。
  5. 如果请求超时、网络异常或结果无法确定,调用 alipay.trade.fastpay.refund.query 查询最终状态。
  6. 确认退款成功后,再更新本地订单和退款记录。

退款接口必须由服务端调用。应用私钥、商户密钥等敏感信息不能放在浏览器或 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 对应的证书客户端初始化方式进行配置,不能把证书内容直接当作普通支付宝公钥使用。

备注:内容仅供参考。