业务示例代码
更新时间:2025.06.131. 目标
通过本教程的学习,你应该可以:
2. 业务处理流程
2.1 申请退款
当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家账户上。
申请退款需要3类信息:
商户信息
子商户号
原支付订单信息
微信支付订单号或者商户订单号
原订单金额
退款信息
商户退款单号:标识该服务商商户下的退款单,同一个服务商商户下不同商户退款单号表示不同的退款单
退款原因
退款金额
退款出资信息
退款回调URL
退款商品信息
1package com.tencent.pay.refund; 2 3import com.java.demo.Create; // 使用合作伙伴申请退款接口https://pay.weixin.qq.com/doc/v3/partner/4013080625 4 5public class WXPayRefund { 6 7 public static void main(String[] args) { 8 Create ceateRefund = Create( 9 "填入 商户号", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 10 "填入 商户API证书序列号", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 11 "填入 商户API证书私钥文件路径", // 商户API证书私钥文件路径,本地文件路径 12 "填入 微信支付公钥ID", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 13 "填入 微信支付公钥文件路径" // 微信支付公钥文件路径,本地文件路径 14 ); 15 16 CreateRequest createRefundRequest = new CreateRequest(); 17 // 商户信息 18 createRefundRequest.subMchid = "1900000109"; 19 20 // 原支付订单信息 21 createRefundRequest.transactionId = "4200000020202506035017900000"; 22 createRefundRequest.amount = new AmountReq(); 23 createRefundRequest.amount.total = 800L; 24 25 // 退款信息 26 // 商户退款单号生成方式商户视实际情况生成,需要保证服务商商户下全局唯一 27 createRefundRequest.outRefundNo = "4200000020202506035017900000_1"; 28 createRefundRequest.reason = "成团退差价"; 29 createRefundRequest.amount.refund = 200L; 30 31 // 退款成功,退款关闭,退款异常时需要微信支付通知商户商户,可以加上回调URL,退款到这几个状态时会调用这个URL通知商户 32 createRefundRequest.notifyUrl = "https://weixin.qq.com"; 33 34 // 退款资金来源 35 // 1. 对于出行预付押金退款 36 // - 不填写表示已结退款,从出行商户基本户出资退款 37 // - 填写UNSETTLED,表示未结退款,从押金账户出资退款 38 // 2. 对于子商户为老资金流的商户(老资金流也叫旧资金流,旧资金流如何区分见:https://kf.qq.com/faq/161223N7fi2E161223EvY3Ir.html) 39 // - 不填写表示未结退款,从未结账户出资退款 40 // - 填写AVAILABLE,表示从可用余额出资退款 41 // createRefundRequest.fundsAccount = ReqFundsAccount.AVAILABLE; 42 43 // 退款商品信息 44 createRefundRequest.goodsDetail = new ArrayList<>(); 45 { 46 GoodsDetail goodDetail = new GoodsDetail(); 47 goodDetail.merchantGoodsId = "1217752501201407033233368018"; 48 goodDetail.wechatpayGoodsId = "1001"; 49 goodDetail.goodsName = "iPhone6s 16G"; 50 goodDetail.unitPrice = 528800L; 51 goodDetail.refundAmount = 528800L; 52 goodDetail.refundQuantity = 1L; 53 goodDetail.goodsDetail.add(item0); 54 }; 55 56 57 Refund refund; 58 try { 59 refund = ceateRefund.run(createRefundRequest); 60 } catch (WXPayUtility.ApiException e) { 61 // 申请退款处理异常处理逻辑 62 if (e.getErrorCode() == "SYSTEM_ERROR") { 63 // 错误:系统错误 64 // 解决方式:稍后原单重试 65 // 描述:微信支付系统失败,一定要原单重试,系统失败直接立即重试大概率还会是系统失败,建议等1分钟后再重试 66 } else if (e.getErrorCode() == "FREQUENCY_LIMITED") { 67 // 错误:限频报错 68 // 解决方式:稍后原单重试 69 // 描述: 退款接口频率限制,一定要原单重试,直接立即重试大概率还会是系统失败,建议等1分钟后再重试 70 } else if (e.getErrorCode() == "MCH_NOT_EXISTS") { 71 // 错误:子商户不存在 72 // 解决方式:检查输入的子商户号是否填写正确,修改正确之后原单重试 73 // 描述:输入子商户账户不存在,需要填写存在的账户之后重试 74 } else if (e.getErrorCode() == "USER_ACCOUNT_ABNORMAL") { 75 // 错误:用户账户异常 76 // 解决方式:退款受理失败,商户可以自行处理退款,也可以登录商户平台进入交易中心发起异常退款 77 // 描述:用户注销或者被管控了,这笔订单已经不能发起退款,这个错误就不需要重试了 78 } else if (e.getErrorCode() == "NOT_ENOUGH") { 79 // 错误:账户余额不足 80 // 解决方式:商户充值之后原单重试 81 // 描述:商户账户余额不足,导致退款失败,商户充值之后一定要原单重试 82 } else if (e.getErrorCode() == "NO_AUTH") { 83 // 错误:无退款权限 84 // 解决方式: 85 // 1. 检查商户是否是被处罚:登录商户平台进入账户中心-违约记录查询是否违约记录,如果有按照上面的指引解决违约记录之后原单重试 86 // 2. 检查子商户是否已授权服务商商户API退款权限:登录商户平台进入产品中心-特约商户授权产品,在特约商户列表中看看该子商户是否存在并且授权状态为“已授权”, 87 // 如果不存在或者授权状态不是“已授权”就需要发起授权邀请,等子商户审批之后重新查询一遍,授权状态为“已授权”之后再原单重试 88 // 描述:服务商商户被处罚或者服务商商户和子商户没有父子受理关系 89 } else if (e.getErrorCode() == "SIGN_ERROR") { 90 // 错误:签名错误 91 // 解决方式:检查服务商商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 92 // 描述:签名报错,需要确认签名材料和签名流程是否正确 93 } else if (e.getErrorCode() == "PARAM_ERROR") { 94 // 错误:参数错误 95 // 解决方式:按照报错返回的message,重新输入请求参数 96 // 描述:参数的类型,长度,或者必填选项没有填写等 97 } else if (e.getErrorCode() == "INVALID_REQUEST") { 98 // 错误:请求非法,请求参数正确,但是不符合退款业务规则 99 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后再原单重试 100 // 描述:不符合业务规则的场景如: 101 // - 交易时间超过365天,不允许发起退款 102 // - 一笔订单退款次数已经超过50次了,这笔订单不允许发起新的退款 103 // - 申请退款的金额超过了这笔订单剩余可退金额,不允许申请退款 104 } else { 105 // 其他类型错误:稍等一会后原单重试 106 } 107 108 } 109 } 110}
2.2 查询退款
上面申请退款成功之后,说明微信支付成功受理了这笔退款,并不是已经退款到账,商户可以通过查询退款接口查询退款结果
退款单的状态机:
1package com.tencent.pay.refund; 2 3import com.java.demo.Create; // 使用合作伙伴申请退款接口https://pay.weixin.qq.com/doc/v3/partner/4013080625 4import com.java.demo.QueryByOutRefundNo; // 使用合作伙伴查询单笔退款(按商户退款单号)接口,见:https://pay.weixin.qq.com/doc/v3/partner/4013080626 5import com.java.demo.CreateAbnormalRefund; // 使用合作伙伴发起异常退款接口,见:https://pay.weixin.qq.com/doc/v3/partner/4013080627 6 7public class WXPayRefund { 8 9 public static void main(String[] args) { 10 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 11 QueryByOutRefundNo client = new QueryByOutRefundNo( 12 "填入 商户号", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 13 "填入 商户API证书序列号", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 14 "填入 商户API证书私钥文件路径", // 商户API证书私钥文件路径,本地文件路径 15 "填入 微信支付公钥ID", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 16 "填入 微信支付公钥文件路径" // 微信支付公钥文件路径,本地文件路径 17 ); 18 19 QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest(); 20 request.outRefundNo = "4200000020202506035017900000_1"; 21 request.subMchid = "1900000109"; 22 23 Refund refund = client.run(request); 24 switch (refund.status) { 25 case SUCCESS: 26 // 终态:退款成功(退款到账) 27 // 更新商户自己的业务单据状态为成功 28 UpdateRefund2Succ(); 29 break; 30 case CLOSED: 31 // 终态:退款关闭 32 // 更新商户自己的业务单据状态为关闭 33 UpdateRefund2Closed(); 34 // 如果还需要退款,需要换单(换outRefundNo)重新申请退款 35 Create refund = Create( 36 "填入 商户号", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 37 "填入 商户API证书序列号", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 38 "填入 商户API证书私钥文件路径", // 商户API证书私钥文件路径,本地文件路径 39 "填入 微信支付公钥ID", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 40 "填入 微信支付公钥文件路径" // 微信支付公钥文件路径,本地文件路径 41 ); 42 43 CreateRequest createRefundRequest = new CreateRequest(); 44 // 其他参数和之前申请退款参数一样,只改商户退款单号 45 createRefundRequest.outRefundNo = "4200000020202506035017900000_2"; 46 refund.run(createRefundRequest); 47 48 break; 49 case PROCESSING: 50 // 非终态:退款处理中 - 还未到账,需要隔一段时间再来查询 51 break; 52 case ABNORMAL: 53 // 非终态:退款异常 - 用户异常、用户被管控等原因不能原路退 54 // 可以用两种处理方式: 55 // 1. 商户登录商户平台进入交易中心发起异常退款,把钱退到用户指定的银行卡或者商户的银行 56 // 2. 请求异常退款API 57 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 58 CreateAbnormalRefund createAbnormalRefund = new CreateAbnormalRefund( 59 "填入 商户号", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 60 "填入 商户API证书序列号", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 61 "填入 商户API证书私钥文件路径", // 商户API证书私钥文件路径,本地文件路径 62 "填入 微信支付公钥ID", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 63 "填入 微信支付公钥文件路径" // 微信支付公钥文件路径,本地文件路径 64 ); 65 CreateAbnormalRefundRequest abnormalRefundRequest = new AbnormalRefundRequest(); 66 abnormalRefundRequest.subMchId = "1900000109"; 67 abnormalRefundRequest.outRefundNo = "4200000020202506035017900000_1"; 68 abnormalRefundRequest.type = AbnormalReceiveType.USER_BANK_CARD; 69 abnormalRefundRequest.bankType = "ICBC_DEBIT"; 70 abnormalRefundRequest.bankAccount = createAbnormalRefund.encrypt("请填 用户的银行卡号"); 71 abnormalRefundRequest.realName = createAbnormalRefund.encrypt("请填 收款用户姓名"); 72 Refund refund = createAbnormalRefund.abnormalRefund(refund4Create.refundId, abnormalRefundRequest); 73 // 发起异常退款成功之后,退款单会进入退款中,还是需要通过查单接口查询退款单的状态 74 break; 75 default: 76 // 非法状态 77 throw new IllegalArgumentException("微信支付退款单状态非法"); 78 } 79 } catch (WXPayUtility.ApiException e) { 80 // 查询退款处理异常处理逻辑 81 if (e.getErrorCode() == "RESOURCE_NOT_EXISTS") { 82 // 错误:退款单不存在 83 // 解决方式:原参数申请退款 84 // 描述:退款受理没成功,原参数发起退款申请即可 85 } else if (e.getErrorCode() == "SYSTEM_ERROR") { 86 // 错我:系统错误 87 // 解决方式:稍后原单重试 88 // 描述:微信支付系统失败,直接立即重试大概率还会是系统失败,建议等1分钟后再重试 89 } else if (e.getErrorCode() == "FREQUENCY_LIMITED") { 90 // 错误:限频报错 91 // 处理逻辑:稍后原单重试 92 // 描述: 退款接口频率限制,直接立即重试大概率还会是系统失败,建议等1分钟后再重试 93 } else if (e.getErrorCode() == "MCH_NOT_EXISTS") { 94 // 错误:子商户不存在 95 // 处理逻辑:检查输入的子商户号是否填写正确,修改正确之后原单重试 96 // 描述:输入子商户账户不存在,需要填写存在的账户之后重试 97 } else if (e.getErrorCode() == "NO_AUTH") { 98 // 错误:无退款权限 99 // 处理逻辑:检查服务商商户是否退款权限,服务商商户和子商户是否有父子受理关系,需要开通服务商商户的退款权限和添加子商户的父子受理权限才能发起退款 100 // 描述:服务商商户无退款权限或者服务商商户和子商户没有父子受理关系 101 } else if (e.getErrorCode() == "SIGN_ERROR") { 102 // 错误:签名错误 103 // 处理逻辑:检查服务商商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 104 // 描述:签名报错,需要确认签名材料和签名流程是否正确 105 } else if (e.getErrorCode() == "PARAM_ERROR") { 106 // 错误:参数错误 107 // 处理逻辑:按照报错返回的message,重新输入请求参数 108 // 描述:参数的类型,长度,或者必填选项没有填写等 109 } else { 110 // 其他类型错误:稍等一会后原单重试 111 } 112 } 113 } 114}
2.3 退款结果通知
如果商户申请退款时设置了notifyUrl,或者在「合作伙伴平台-交易中心-交易管理-退款管理-退款配置」中设置了通知URL,那么当退款状态变更为SUCCESS、CLOSED或者ABNORMAL后,系统会通过HTTP POST方法回调到这个URL上
如果退款申请是设置了notifyUrl,同时退款配置中也设置了通知URL,已退款申请的URL为准
1RefundNotifyResult refundNotifyResult; 2try { 3 // 1. 解析微信支付回调包体,refundNotifyRawResult为商户接收到微信回调的原始信息 4 refundNotifyResult = refund.parseRefundNotify(refundNotifyRawResult); 5 // 2. 解析完回调包体之后,可以先返回给微信支付200或者204,表示回调成功 6 // 商户再异步处理包体逻辑,这样子能保证微信回调不会超时,微信支付超时多次之后不会再回调 7 // 处理方式如先吧refundNotifyResult抛到消息队列,然后返回微信支付成功,最后消息队列消费者可以异步处理退款回调信息 8 // 9 send2MQ(refundNotifyResult); 10} catch (Exception e) { 11 // 处理回调异常 12} 13 14 15// 消费者处理逻辑 16public static bool handleRefundNotify(RefundNotifyResult refundNotifyResult) { 17 switch (refundNotifyResult.status) { 18 // 这里处理状态逻辑和上面的查询退款单的逻辑保持一致即可 19 } 20}
文档是否有帮助