Appearance
不得不说,申请退款和创建订单是非常相似的,流程都一样,只是请求的数据变了,那么我们第一步就是封装请求数据成对象形式,方便后续调用。
1. 申请退款请求对象
微信支付订单号,微信支付订单号和商家订单号二选一,这个是必不可少的,原订单金额也是必填的,微信会做二次验证。
java
@Data
public class WeChatRefundParam {
/**
* 微信支付订单号,微信支付订单号和商家订单号二选一
*/
private String transactionId;
/**
* 商家订单号,对应 out_trade_no,
*/
private String orderId;
/**
* 商户退款单号,对应out_refund_no
*/
private String refundOrderId;
/**
* 退款原因,选填
*/
private String reason;
/**
* 回调地址
*/
private WxNotifyType notify;
/**
* 退款金额
*/
private BigDecimal refundMoney;
/**
* 原订单金额,必填
*/
private BigDecimal totalMoney;
}
2. 将请求参数封装成Map集合
java
/**
* 封装微信支付申请退款请求参数
* @param param 微信支付申请退款请求参数
* @return 封装后的map微信支付申请退款请求参数对象
*/
private static Map<String, Object> getRefundParams(WxPayConfig wxPayConfig, WeChatRefundParam param) {
Map<String, Object> paramsMap = new HashMap<>();
if (StringUtils.isNoneBlank(param.getTransactionId())) {
paramsMap.put("transaction_id", param.getTransactionId());
} else if (StringUtils.isNoneBlank(param.getOrderId())) {
paramsMap.put("out_trade_no", param.getOrderId());
} else {
throw new DefaultException("微信支付订单号和商户订单号必须填写一个");
}
paramsMap.put("out_refund_no", param.getRefundOrderId());
if (StringUtils.isNoneBlank(param.getReason())) {
paramsMap.put("reason", param.getReason());
}
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(param.getNotify().getType()));
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("refund", param.getRefundMoney().multiply(new BigDecimal("100")).intValue());
amountMap.put("total", param.getTotalMoney().multiply(new BigDecimal("100")).intValue());
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
return paramsMap;
}
3. 申请退款工具类
剩下的就和创建订单一样了,我也不罗嗦了,可以拿去直接用。
java
@Slf4j
public class WxPayRefundUtil {
/**
* 发起微信退款申请
*
* @param wxPayConfig 微信配置信息
* @param param 微信支付申请退款请求参数
* @param wxPayClient 微信请求客户端()
* @return 微信支付二维码地址
*/
public static String refundPay(WxPayConfig wxPayConfig, WeChatRefundParam param, CloseableHttpClient wxPayClient) {
// 1.获取请求参数的Map格式
Map<String, Object> paramsMap = getRefundParams(wxPayConfig, param);
// 2.获取请求对象
HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.DOMESTIC_REFUNDS, paramsMap);
// 3.完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = wxPayClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
throw new DefaultException("微信支付请求失败");
}
// 4.解析response对象
HashMap<String, String> resultMap = resolverResponse(response);
log.info("发起退款参数:{}",resultMap);
if (resultMap != null) {
// 返回微信支付退款单号
return resultMap.get("refund_id");
}
return null;
}
/**
* 解析响应数据
*
* @param response 发送请求成功后,返回的数据
* @return 微信返回的参数
*/
private static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
try {
// 1.获取请求码
int statusCode = response.getStatusLine().getStatusCode();
// 2.获取返回值 String 格式
final String bodyAsString = EntityUtils.toString(response.getEntity());
Gson gson = new Gson();
if (statusCode == 200) {
// 3.如果请求成功则解析成Map对象返回
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
return resultMap;
} else {
if (StringUtils.isNoneBlank(bodyAsString)) {
log.error("微信支付请求失败,提示信息:{}", bodyAsString);
// 4.请求码显示失败,则尝试获取提示信息
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
throw new DefaultException(resultMap.get("message"));
}
log.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
// 其他异常,微信也没有返回数据,这就需要具体排查了
throw new IOException("request failed");
}
} catch (Exception e) {
e.printStackTrace();
throw new DefaultException(e.getMessage());
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取请求对象(Post请求)
*
* @param wxPayConfig 微信配置类
* @param apiType 接口请求地址
* @param paramsMap 请求参数
* @return Post请求对象
*/
private static HttpPost getHttpPost(WxPayConfig wxPayConfig, WxApiType apiType, Map<String, Object> paramsMap) {
// 1.设置请求地址
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(apiType.getType()));
// 2.设置请求数据
Gson gson = new Gson();
String jsonParams = gson.toJson(paramsMap);
// 3.设置请求信息
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
return httpPost;
}
/**
* 封装微信支付申请退款请求参数
*
* @param param 微信支付申请退款请求参数
* @return 封装后的map微信支付申请退款请求参数对象
*/
private static Map<String, Object> getRefundParams(WxPayConfig wxPayConfig, WeChatRefundParam param) {
Map<String, Object> paramsMap = new HashMap<>();
if (StringUtils.isNoneBlank(param.getTransactionId())) {
paramsMap.put("transaction_id", param.getTransactionId());
} else if (StringUtils.isNoneBlank(param.getOrderId())) {
paramsMap.put("out_trade_no", param.getOrderId());
} else {
throw new DefaultException("微信支付订单号和商户订单号必须填写一个");
}
paramsMap.put("out_refund_no", param.getRefundOrderId());
if (StringUtils.isNoneBlank(param.getReason())) {
paramsMap.put("reason", param.getReason());
}
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(param.getNotify().getType()));
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("refund", param.getRefundMoney().multiply(new BigDecimal("100")).intValue());
amountMap.put("total", param.getTotalMoney().multiply(new BigDecimal("100")).intValue());
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
return paramsMap;
}
}
4.申请退款使用
java
@ApiOperation("退款申请测试")
@GetMapping("/refund/{orderId}")
public String refund(@PathVariable String orderId) {
WeChatRefundParam param = new WeChatRefundParam();
param.setOrderId(orderId);
String refundOrderId = IdWorker.getIdStr();
log.info("refundOrderId:{}",refundOrderId);
param.setRefundOrderId(refundOrderId);
param.setReason("商品售罄");
param.setNotify(WxNotifyType.TEST_REFUND_NOTIFY);
param.setRefundMoney(new BigDecimal("0.01"));
param.setTotalMoney(new BigDecimal("0.01"));
return WxPayRefundUtil.refundPay(wxPayConfig,param,wxPayClient);
}
当交易发生之后一年内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付金额退还给买家,微信支付将在收到退款请求并且验证成功之后,将支付款按原路退还至买家账号上。
DANGER
注意:
1、交易时间超过一年的订单无法提交退款
2、微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
4、每个支付订单的部分退款次数不能超过50次
5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
6、申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果
7、一个月之前的订单申请退款频率限制为:5000/min
8、同一笔订单多次退款的请求需相隔1分钟