Appearance
还记得WxPayCallbackUtil回调接口工具类吗,当时是用于支付成功的回调,我们接下来向里面添加一个退款成功的回调方法,在添加前我们先来做一个准备操作。
退款返回数据对象
java
@Data
@Slf4j
public class WxchatCallbackRefundData {
/**
* 商户订单号
*/
private String orderId;
/**
* 商户退款单号,out_refund_no
*/
private String refundId;
/**
* 微信支付系统生成的订单号
*/
private String transactionId;
/**
* 微信支付系统生成的退款订单号
*/
private String transactionRefundId;
/**
* 退款渠道
* ORIGINAL:原路退款
* BALANCE:退回到余额
* OTHER_BALANCE:原账户异常退到其他余额账户
* OTHER_BANKCARD:原银行卡异常退到其他银行卡
*/
private String channel;
/**
* 退款成功时间
* 当前退款成功时才有此返回值
*/
private Date successTime;
/**
* 退款状态
* 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。
* SUCCESS:退款成功
* CLOSED:退款关闭
* PROCESSING:退款处理中
* ABNORMAL:退款异常
*/
private String status;
/**
* 退款金额
*/
private BigDecimal refundMoney;
public Date getSuccessTime() {
return successTime;
}
public void setSuccessTime(String successTime) {
// Hutool工具包的方法,自动识别一些常用格式的日期字符串
this.successTime = DateUtil.parse(successTime);
}
}
退款业务处理接口
还记得之前订单支付的回调接口的回调函数吗?这是用来处理业务的,相对于订单支付,退款的处理要略微复杂些。如果用户是银行卡支付,但是此时银行卡冻结了,那么我们就需要特殊处理。
微信官方会返回给我一个字段status,我们不能指望项目组的成员使用此方式时,都会进行是否退款成功的判断,以及退款失败的业务处理,因此我们使用自定义接口来达到限制的目的。
java
/**
* 退款处理接口,为了防止项目开发人员,不手动判断退款失败的情况
* 退款失败:退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款
*/
public interface WechatRefundCallback {
/**
* 退款成功处理情况
*/
void success(WxchatCallbackRefundData refundData);
/**
* 退款失败处理情况
*/
void fail(WxchatCallbackRefundData refundData);
}
微信退款回调方法
我们来看微信回调工具类,添加的方法吧。
java
@Slf4j
public class WxPayCallbackUtil {
/**
* 微信支付申请退款回调方法
*
* @param verifier 证书
* @param wxPayConfig 微信配置
* @param refundCallback 回调方法,用于处理业务逻辑,包含退款成功处理于退款失败处理
* @return json格式的string数据,直接返回给微信
*/
public static String wxPayRefundCallback(HttpServletRequest request, HttpServletResponse response, Verifier verifier, WxPayConfig wxPayConfig, WechatRefundCallback refundCallback) {
Gson gson = new Gson();
// 1.处理通知参数
final String body = HttpUtils.readData(request);
HashMap<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
// 2.签名验证
WechatPayValidatorForRequest wechatForRequest = new WechatPayValidatorForRequest(verifier, body, (String) bodyMap.get("id"));
try {
if (!wechatForRequest.validate(request)) {
// 通知验签失败
response.setStatus(500);
final HashMap<String, Object> map = new HashMap<>();
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
} catch (IOException e) {
e.printStackTrace();
}
// 3.获取明文数据
String plainText = decryptFromResource(bodyMap, wxPayConfig);
HashMap<String, Object> plainTextMap = gson.fromJson(plainText, HashMap.class);
log.info("退款plainTextMap:{}", plainTextMap);
// 4.封装微信返回的数据
WxchatCallbackRefundData refundData = getRefundCallbackData(plainTextMap);
if ("SUCCESS".equals(refundData.getStatus())) {
// 执行业务逻辑
refundCallback.success(refundData);
} else {
// 特殊情况退款失败业务处理,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款
refundCallback.fail(refundData);
}
// 5.成功应答
response.setStatus(200);
final HashMap<String, Object> resultMap = new HashMap<>();
resultMap.put("code", "SUCCESS");
resultMap.put("message", "成功");
return gson.toJson(resultMap);
}
private static WxchatCallbackRefundData getRefundCallbackData(HashMap<String, Object> plainTextMap) {
Gson gson = new Gson();
WxchatCallbackRefundData refundData = new WxchatCallbackRefundData();
String successTime = String.valueOf(plainTextMap.get("success_time"));
if (StringUtils.isNoneBlank(successTime)) {
refundData.setSuccessTime(successTime);
}
refundData.setOrderId(String.valueOf(plainTextMap.get("out_trade_no")));
refundData.setRefundId(String.valueOf(plainTextMap.get("out_refund_no")));
refundData.setTransactionId(String.valueOf(plainTextMap.get("transaction_id")));
refundData.setTransactionRefundId(String.valueOf(plainTextMap.get("refund_id")));
refundData.setChannel(String.valueOf(plainTextMap.get("channel")));
final String status = String.valueOf(plainTextMap.get("status"));
refundData.setStatus(status);
String amount = String.valueOf(plainTextMap.get("amount"));
HashMap<String, Object> amountMap = gson.fromJson(amount, HashMap.class);
String refundMoney = String.valueOf(amountMap.get("refund"));
refundData.setRefundMoney(new BigDecimal(refundMoney).movePointLeft(2));
log.info("refundData:{}", refundData);
return refundData;
}
/**
* 对称解密
*/
private static String decryptFromResource(HashMap<String, Object> bodyMap, WxPayConfig wxPayConfig) {
// 通知数据
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
// 数据密文
String ciphertext = resourceMap.get("ciphertext");
// 随机串
String nonce = resourceMap.get("nonce");
// 附加数据
String associateData = resourceMap.get("associated_data");
AesUtil aesUtil = new AesUtil(wxPayConfig.getKey().getBytes(StandardCharsets.UTF_8));
try {
return aesUtil.decryptToString(associateData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
} catch (GeneralSecurityException e) {
e.printStackTrace();
throw new DefaultException("解密失败");
}
}
}
使用方法
这里的使用方法,使用的不是函数式接口,这个用lambda是无法解决的,那么我们看看是怎样使用的吧。
java
@Autowired
private WxPayConfig wxPayConfig;
@Autowired
private CloseableHttpClient wxPayClient;
@Autowired
private Verifier verifier;
java
@ApiOperation("微信退款回调接口")
@PostMapping("/wx/refund/callback")
public String refundWechatCallback(HttpServletRequest request, HttpServletResponse response) {
return WxPayCallbackUtil.wxPayRefundCallback(request, response, verifier, wxPayConfig, new WechatRefundCallback() {
@Override
public void success(WxchatCallbackRefundData refundData) {
// TODO 退款成功的业务逻辑,例如更改订单状态为退款成功等
}
@Override
public void fail(WxchatCallbackRefundData refundData) {
// TODO 特殊情况下退款失败业务处理,例如银行卡冻结需要人工退款,此时可以邮件或短信提醒管理员,并携带退款单号等关键信息
}
});
}