申请退款

更新时间:2025.01.16

在交易完成后的一年内(以支付成功时间为起点+365天计算),若因用户或子商户(也叫特约商户)方面导致需进行订单退款,服务商可通过此接口将支付金额的全部或部分原路退还至用户。详细介绍参考:订单退款-产品介绍

注意:

1、一笔订单最多支持50次部分退款(若需多次部分退款,请更换商户退款单号并间隔1分钟后再次调用)。

2、在申请退款失败后进行重试时,请务必使用原商户退款单号,以避免因重复退款而导致的资金损失。

3、同一服务商商户号下,此接口调用成功的频率限制为150QPS,而调用失败报错时的频率限制为6QPS。

4、申请退款接口返回成功仅表示退款单已受理成功,具体的退款结果需依据退款结果通知查询退款的返回信息为准。

5、若一个月前的订单申请退款时返回报错“频率限制,1个月之前的订单请降低申请频率再重试”,请调整退款时间,再使用原参数进行重试。

接口说明

支持商户:【普通服务商】

请求方式:【POST】/v3/refund/domestic/refunds

请求域名:【主域名】https://api.mch.weixin.qq.com 使用该域名将访问就近的接入点

     【备域名】https://api2.mch.weixin.qq.com 使用该域名将访问异地的接入点 ,指引点击查看

请求参数

Header HTTP头参数

Authorization  必填 string

请参考签名认证生成认证信息


Accept  必填 string

请设置为application/json


Content-Type  必填 string

请设置为application/json


body 包体参数

sub_mchid  必填 string(32)

【子商户号】 服务商下单时传入的子商户号sub_mchid。


transaction_id  选填 string(32)

【微信支付订单号】 微信支付侧订单的唯一标识,订单支付成功后,查询订单支付成功回调通知会返回该参数。
transaction_id和out_trade_no必须二选一进行传参。


out_trade_no  选填 string(32)

【商户订单号】 服务商下单时传入的服务商系统内部订单号。

transaction_id和out_trade_no必须二选一进行传参。


out_refund_no  必填 string(64)

【商户退款单号】 服务商系统内部的退款单号,服务商系统内部唯一,只能是数字、大小写字母_-|*@ ,同一商户退款单号多次请求只退一笔。不可超过64个字节数。


reason  选填 string(80)

【退款原因】 若商户传入,会在下发给用户的退款消息中体现退款原因,不可超过80个字节长度。
注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因。参考退款通知UI示意图


notify_url  选填 string(256)

【退款结果回调url】异步接收微信支付退款结果通知的回调地址,需按照notify_url填写注意事项规范填写。 如果参数中传了notify_url,则服务商平台上配置的回调地址(服务商平台-交易中心-退款管理-退款配置)将不会生效,优先回调当前传的这个地址。


funds_account  选填 string

【退款资金来源】 若传递此参数则使用对应的资金账户退款。

可选取值:

  • AVAILABLE: 仅对旧资金流商户适用(请参考旧资金流介绍区分),传此枚举指定从可用余额账户出资,否则默认使用未结算资金退款。

  • UNSETTLED: 仅对出行预付押金退款适用,指定从未结算资金出资。


amount  必填 object

【金额信息】订单金额信息

属性

goods_detail  选填 array[GoodsDetail]

【退款商品】 请填写需要指定退款的商品信息,所指定的商品信息需要与下单时传入的单品列表goods_detail中的对应商品信息一致 ,如无需按照指定商品退款,本字段不填。

属性

请求示例

Java
Go
curl

需配合微信支付工具库 WXPayUtility 使用,请参考 Java 

1package com.java.demo;
2
3import com.google.gson.annotations.Expose;
4import com.google.gson.annotations.SerializedName;
5import com.java.utils.WXPayUtility; // 引用微信支付工具库 参考:https://pay.weixin.qq.com/doc/v3/partner/4014985777
6import java.io.IOException;
7import java.io.UncheckedIOException;
8import java.security.PrivateKey;
9import java.security.PublicKey;
10import java.util.ArrayList;
11import java.util.HashMap;
12import java.util.List;
13import java.util.Map;
14import okhttp3.MediaType;
15import okhttp3.OkHttpClient;
16import okhttp3.Request;
17import okhttp3.RequestBody;
18import okhttp3.Response;
19
20/**
21 * 退款申请
22 */
23public class Create {
24  private static String HOST = "https://api.mch.weixin.qq.com";
25  private static String METHOD = "POST";
26  private static String PATH = "/v3/refund/domestic/refunds";
27
28  public static void main(String[] args) {
29    // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
30    Create client = new Create(
31        "填入 商户号",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
32        "填入 商户API证书序列号",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
33        "填入 商户API证书私钥文件路径", // 商户API证书私钥文件路径,本地文件路径
34        "填入 微信支付公钥ID",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
35        "填入 微信支付公钥文件路径"     // 微信支付公钥文件路径,本地文件路径
36    );
37
38    CreateRequest request = new CreateRequest();
39    request.subMchid = "1900000109";
40    request.transactionId = "1217752501201407033233368018";
41    request.outTradeNo = "1217752501201407033233368018";
42    request.outRefundNo = "1217752501201407033233368018";
43    request.reason = "商品已售完";
44    request.notifyUrl = "https://weixin.qq.com";
45    request.fundsAccount = ReqFundsAccount.AVAILABLE;
46    request.amount = new AmountReq();
47    request.amount.refund = 888L;
48    request.amount.from = new ArrayList<>();
49    {
50      FundsFromItem item0 = new FundsFromItem();
51      item0.account = Account.AVAILABLE;
52      item0.amount = 444L;
53      request.amount.from.add(item0);
54    };
55    request.amount.total = 888L;
56    request.amount.currency = "CNY";
57    request.goodsDetail = new ArrayList<>();
58    {
59      GoodsDetail item0 = new GoodsDetail();
60      item0.merchantGoodsId = "1217752501201407033233368018";
61      item0.wechatpayGoodsId = "1001";
62      item0.goodsName = "iPhone6s 16G";
63      item0.unitPrice = 528800L;
64      item0.refundAmount = 528800L;
65      item0.refundQuantity = 1;
66      request.goodsDetail.add(item0);
67    };
68    try {
69      Refund response = client.run(request);
70
71      // TODO: 请求成功,继续业务逻辑
72      System.out.println(response);
73    } catch (WXPayUtility.ApiException e) {
74      // TODO: 请求失败,根据状态码执行不同的逻辑
75      e.printStackTrace();
76    }
77  }
78
79  public Refund run(CreateRequest request) {
80    String uri = PATH;
81    String reqBody = WXPayUtility.toJson(request);
82
83    Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
84    reqBuilder.addHeader("Accept", "application/json");
85    reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
86    reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
87    reqBuilder.addHeader("Content-Type", "application/json");
88    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
89    reqBuilder.method(METHOD, requestBody);
90    Request httpRequest = reqBuilder.build();
91
92    // 发送HTTP请求
93    OkHttpClient client = new OkHttpClient.Builder().build();
94    try (Response httpResponse = client.newCall(httpRequest).execute()) {
95      String respBody = WXPayUtility.extractBody(httpResponse);
96      if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
97        // 2XX 成功,验证应答签名
98        WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
99                                      httpResponse.headers(), respBody);
100
101        // 从HTTP应答报文构建返回数据
102        return WXPayUtility.fromJson(respBody, Refund.class);
103      } else {
104        throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
105      }
106    } catch (IOException e) {
107      throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
108    }
109  }
110
111  private final String mchid;
112  private final String certificateSerialNo;
113  private final PrivateKey privateKey;
114  private final String wechatPayPublicKeyId;
115  private final PublicKey wechatPayPublicKey;
116
117  public Create(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
118    this.mchid = mchid;
119    this.certificateSerialNo = certificateSerialNo;
120    this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
121    this.wechatPayPublicKeyId = wechatPayPublicKeyId;
122    this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
123  }
124
125  public enum Status {
126    @SerializedName("SUCCESS")
127    SUCCESS,
128    @SerializedName("CLOSED")
129    CLOSED,
130    @SerializedName("PROCESSING")
131    PROCESSING,
132    @SerializedName("ABNORMAL")
133    ABNORMAL
134  }
135
136  public enum Account {
137    @SerializedName("AVAILABLE")
138    AVAILABLE,
139    @SerializedName("UNAVAILABLE")
140    UNAVAILABLE
141  }
142
143  public enum PromotionType {
144    @SerializedName("COUPON")
145    COUPON,
146    @SerializedName("DISCOUNT")
147    DISCOUNT
148  }
149
150  public static class GoodsDetail {
151    @SerializedName("merchant_goods_id")
152    public String merchantGoodsId;
153
154    @SerializedName("wechatpay_goods_id")
155    public String wechatpayGoodsId;
156
157    @SerializedName("goods_name")
158    public String goodsName;
159
160    @SerializedName("unit_price")
161    public Long unitPrice;
162
163    @SerializedName("refund_amount")
164    public Long refundAmount;
165
166    @SerializedName("refund_quantity")
167    public Integer refundQuantity;
168  }
169
170  public static class CreateRequest {
171    @SerializedName("sub_mchid")
172    public String subMchid;
173
174    @SerializedName("transaction_id")
175    public String transactionId;
176
177    @SerializedName("out_trade_no")
178    public String outTradeNo;
179
180    @SerializedName("out_refund_no")
181    public String outRefundNo;
182
183    @SerializedName("reason")
184    public String reason;
185
186    @SerializedName("notify_url")
187    public String notifyUrl;
188
189    @SerializedName("funds_account")
190    public ReqFundsAccount fundsAccount;
191
192    @SerializedName("amount")
193    public AmountReq amount;
194
195    @SerializedName("goods_detail")
196    public List<GoodsDetail> goodsDetail;
197  }
198
199  public enum Channel {
200    @SerializedName("ORIGINAL")
201    ORIGINAL,
202    @SerializedName("BALANCE")
203    BALANCE,
204    @SerializedName("OTHER_BALANCE")
205    OTHER_BALANCE,
206    @SerializedName("OTHER_BANKCARD")
207    OTHER_BANKCARD
208  }
209
210  public static class Amount {
211    @SerializedName("total")
212    public Long total;
213
214    @SerializedName("refund")
215    public Long refund;
216
217    @SerializedName("from")
218    public List<FundsFromItem> from;
219
220    @SerializedName("payer_total")
221    public Long payerTotal;
222
223    @SerializedName("payer_refund")
224    public Long payerRefund;
225
226    @SerializedName("settlement_refund")
227    public Long settlementRefund;
228
229    @SerializedName("settlement_total")
230    public Long settlementTotal;
231
232    @SerializedName("discount_refund")
233    public Long discountRefund;
234
235    @SerializedName("currency")
236    public String currency;
237
238    @SerializedName("refund_fee")
239    public Long refundFee;
240  }
241
242  public enum ReqFundsAccount {
243    @SerializedName("AVAILABLE")
244    AVAILABLE,
245    @SerializedName("UNSETTLED")
246    UNSETTLED
247  }
248
249  public enum FundsAccount {
250    @SerializedName("UNSETTLED")
251    UNSETTLED,
252    @SerializedName("AVAILABLE")
253    AVAILABLE,
254    @SerializedName("UNAVAILABLE")
255    UNAVAILABLE,
256    @SerializedName("OPERATION")
257    OPERATION,
258    @SerializedName("BASIC")
259    BASIC,
260    @SerializedName("ECNY_BASIC")
261    ECNY_BASIC
262  }
263
264  public static class Promotion {
265    @SerializedName("promotion_id")
266    public String promotionId;
267
268    @SerializedName("scope")
269    public PromotionScope scope;
270
271    @SerializedName("type")
272    public PromotionType type;
273
274    @SerializedName("amount")
275    public Long amount;
276
277    @SerializedName("refund_amount")
278    public Long refundAmount;
279
280    @SerializedName("goods_detail")
281    public List<GoodsDetail> goodsDetail;
282  }
283
284  public static class Refund {
285    @SerializedName("refund_id")
286    public String refundId;
287
288    @SerializedName("out_refund_no")
289    public String outRefundNo;
290
291    @SerializedName("transaction_id")
292    public String transactionId;
293
294    @SerializedName("out_trade_no")
295    public String outTradeNo;
296
297    @SerializedName("channel")
298    public Channel channel;
299
300    @SerializedName("user_received_account")
301    public String userReceivedAccount;
302
303    @SerializedName("success_time")
304    public String successTime;
305
306    @SerializedName("create_time")
307    public String createTime;
308
309    @SerializedName("status")
310    public Status status;
311
312    @SerializedName("funds_account")
313    public FundsAccount fundsAccount;
314
315    @SerializedName("amount")
316    public Amount amount;
317
318    @SerializedName("promotion_detail")
319    public List<Promotion> promotionDetail;
320  }
321
322  public static class FundsFromItem {
323    @SerializedName("account")
324    public Account account;
325
326    @SerializedName("amount")
327    public Long amount;
328  }
329
330  public static class AmountReq {
331    @SerializedName("refund")
332    public Long refund;
333
334    @SerializedName("from")
335    public List<FundsFromItem> from;
336
337    @SerializedName("total")
338    public Long total;
339
340    @SerializedName("currency")
341    public String currency;
342  }
343
344  public enum PromotionScope {
345    @SerializedName("GLOBAL")
346    GLOBAL,
347    @SerializedName("SINGLE")
348    SINGLE
349  }
350}

//pay.weixin.qq.com/doc/v3/partner/4013080340

应答参数

200 OK

refund_id  必填 string(32)

【微信支付退款单号】申请退款受理成功时,该笔退款单在微信支付侧生成的唯一标识。


out_refund_no  必填 string(64)

【商户退款单号】 服务商申请退款时传的服务商系统内部退款单号。


transaction_id  必填 string(32)

【微信支付订单号】微信支付侧订单的唯一标识。


out_trade_no  必填 string(32)

【商户订单号】 服务商下单时传入的服务商系统内部订单号。


channel  必填 string

【退款渠道】 订单退款渠道
以下枚举:

  • ORIGINAL: 原路退款

  • BALANCE: 退回到余额

  • OTHER_BALANCE: 原账户异常退到其他余额账户

  • OTHER_BANKCARD: 原银行卡异常退到其他银行卡(发起异常退款成功后返回)


user_received_account  必填 string(64)

【退款入账账户】 取当前退款单的退款入账方,有以下几种情况:
1)退回银行卡:{银行名称}{卡类型}{卡尾号}
2)退回支付用户零钱:支付用户零钱
3)退还商户:商户基本账户商户结算银行账户
4)退回支付用户零钱通:支付用户零钱通
5)退回支付用户银行电子账户:支付用户银行电子账户
6)退回支付用户零花钱:支付用户零花钱
7)退回用户经营账户:用户经营账户
8)退回支付用户来华零钱包:支付用户来华零钱包
9)退回企业支付商户:企业支付商户


success_time  选填 string(64)

【退款成功时间】

1、定义:退款成功的时间,该字段在退款状态status为SUCCESS(退款成功)时返回。

2、格式遵循rfc3339标准格式:yyyy-MM-DDTHH:mm:ss+TIMEZONEyyyy-MM-DD 表示年月日;T 字符用于分隔日期和时间部分;HH:mm:ss 表示具体的时分秒;TIMEZONE 表示时区(例如,+08:00 对应东八区时间,即北京时间)。

示例:2015-05-20T13:29:35+08:00 表示北京时间2015年5月20日13点29分35秒。


create_time  必填 string(64)

【退款创建时间】

1、定义:提交退款申请成功,微信受理退款申请单的时间。

2、格式遵循rfc3339标准格式:yyyy-MM-DDTHH:mm:ss+TIMEZONEyyyy-MM-DD 表示年月日;T 字符用于分隔日期和时间部分;HH:mm:ss 表示具体的时分秒;TIMEZONE 表示时区(例如,+08:00 对应东八区时间,即北京时间)。

示例:2015-05-20T13:29:35+08:00 表示北京时间2015年5月20日13点29分35秒。


status  必填 string

【退款状态】退款单的退款处理状态。

  • SUCCESS: 退款成功

  • CLOSED: 退款关闭

  • PROCESSING: 退款处理中

  • ABNORMAL: 退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往服务商平台-交易中心,手动处理此笔退款,可参考: 退款异常的处理,或者通过发起异常退款接口进行处理。
    注:状态流转说明请参考状态流转图


funds_account  必填 string

【资金账户】 退款所使用资金对应的资金账户类型

  • UNSETTLED: 未结算资金

  • AVAILABLE: 可用余额

  • UNAVAILABLE: 不可用余额

  • OPERATION: 运营账户

  • BASIC: 基本账户(含可用余额和不可用余额)

  • ECNY_BASIC: 数字人民币基本账户


amount  必填 object

【金额信息】订单退款金额信息

属性

promotion_detail  选填 array[object]

【优惠退款详情】 订单各个代金券的退款详情,订单使用了代金券且代金券发生退款时返回。

属性

应答示例

200 OK

1{
2  "refund_id" : "50000000382019052709732678859",
3  "out_refund_no" : "1217752501201407033233368018",
4  "transaction_id" : "1217752501201407033233368018",
5  "out_trade_no" : "1217752501201407033233368018",
6  "channel" : "ORIGINAL",
7  "user_received_account" : "招商银行信用卡0403",
8  "success_time" : "2020-12-01T16:18:12+08:00",
9  "create_time" : "2020-12-01T16:18:12+08:00",
10  "status" : "SUCCESS",
11  "funds_account" : "UNSETTLED",
12  "amount" : {
13    "total" : 100,
14    "refund" : 100,
15    "from" : [
16      {
17        "account" : "AVAILABLE",
18        "amount" : 444
19      }
20    ],
21    "payer_total" : 90,
22    "payer_refund" : 90,
23    "settlement_refund" : 100,
24    "settlement_total" : 100,
25    "discount_refund" : 10,
26    "currency" : "CNY",
27    "refund_fee" : 100
28  },
29  "promotion_detail" : [
30    {
31      "promotion_id" : "109519",
32      "scope" : "GLOBAL",
33      "type" : "COUPON",
34      "amount" : 5,
35      "refund_amount" : 100,
36      "goods_detail" : [
37        {
38          "merchant_goods_id" : "1217752501201407033233368018",
39          "wechatpay_goods_id" : "1001",
40          "goods_name" : "iPhone6s 16G",
41          "unit_price" : 528800,
42          "refund_amount" : 528800,
43          "refund_quantity" : 1
44        }
45      ]
46    }
47  ]
48}

 

错误码

公共错误码

状态码

错误码

描述

解决方案

400

PARAM_ERROR

参数错误

请根据错误提示正确传入参数

400

INVALID_REQUEST

HTTP 请求不符合微信支付 APIv3 接口规则

请参阅 接口规则

401

SIGN_ERROR

验证不通过

请参阅 签名常见问题

500

SYSTEM_ERROR

系统异常,请稍后重试

请稍后重试

业务错误码

状态码

错误码

描述

解决方案

400

INVALID_REQUEST

请求参数符合参数格式,但不符合业务规则

此状态代表退款申请失败,商户可根据具体的错误提示做相应的处理。

401

SIGN_ERROR

签名错误

请检查签名参数和方法是否都符合签名算法要求,参考:如何生成签名

403

NOT_ENOUGH

余额不足

此状态代表退款申请失败,商户账户余额不足。

403

USER_ACCOUNT_ABNORMAL

退款请求失败

此状态代表退款申请失败,商户可自行处理退款。

404

MCH_NOT_EXISTS

MCHID不存在

请检查商户号是否正确,商户号获取方式请参考服务商模式开发必要参数说明

404

RESOURCE_NOT_EXISTS

订单号不存在

请检查你的订单号是否正确且是否已支付,未支付的订单不能发起退款

429

FREQUENCY_LIMITED

频率限制

该笔退款未受理,请降低频率后重试

500

SYSTEM_ERROR

系统超时

请不要更换商户退款单号,请使用相同参数再次调用API。

 

 

反馈
咨询
目录
置顶