二、步入正题
修改 WechatNativeController 编写申请退款接口
/** * 申请退款 * @param orderNo 订单号 * @param refundsNo 退款单号(交易订单号) * @param reason 退款原因 */ @PostMapping("/refunds/{orderNo}/{refundsNo}/{reason}") public R refunds(@PathVariable String orderNo, @PathVariable String refundsNo, @PathVariable String reason) { log.info("申请退款"); wxPayService.refund(orderNo, reason, refundsNo); return R.ok(); }
修改 wxPayService 创建 refund 方法
/** * 申请退款 * * @param orderNo 订单号 * @param reason 退款原因 * @param refundsNo 退款单号 */ @SneakyThrows public void refund(String orderNo, String reason, String refundsNo) { // ............ }
前面思考提到的需要查询该订单是否存在和状态要支付成功的
log.info("校验开始"); PaymentInfo paymentInfo = paymentInfoService.lambdaQuery().eq(PaymentInfo::getOrderNo, orderNo) .eq(PaymentInfo::getTradeState, WxTradeState.SUCCESS.getType()).one(); if (null == paymentInfo) { throw new RuntimeException("未查询到该订单,请稍后再试!"); }
在校验客户输入的交易订单号是否正确我这里就判断后四位咯
// 判断是否是本人的订单 String transactionNo = paymentInfo.getTransactionId().substring(paymentInfo.getTransactionId().length() - 4); if (!transactionNo.equals(refundsNo)) { throw new RuntimeException("这笔可能不是你的订单哦.请核实支付成功后微信通知当中的交易订单号后四位!"); }
搞定后我们还需要思考一下,支付这么重要的退款环节我们是不是得要记录一下? 退款啊我直接裂开没赚到钱~
那么我们就查看一下之前文章提到的退款记录表,有同学可能直接懵逼直达车前往第三章从零玩转系列之微信支付实战基础框架搭建当中的创建三层结构所提到过
CREATE TABLE `t_refund_info` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '款单id', `order_no` varchar(50) DEFAULT NULL COMMENT '商户订单编号', `refund_no` varchar(50) DEFAULT NULL COMMENT '商户退款单编号', `refund_id` varchar(50) DEFAULT NULL COMMENT '支付系统退款单号', `total_fee` int(11) DEFAULT NULL COMMENT '原订单金额(分)', `refund` int(11) DEFAULT NULL COMMENT '退款金额(分)', `reason` varchar(50) DEFAULT NULL COMMENT '退款原因', `refund_status` varchar(10) DEFAULT NULL COMMENT '退款状态', `content_return` text COMMENT '申请退款返回参数', `content_notify` text COMMENT '退款结果通知参数', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
搞定后我们就处理创建退款单记录并且保存到数据库当中
修改 refundInfoService 创建 createRefundByOrderNo 方法
log.info("创建退款单记录"); //根据订单编号创建退款单 RefundInfo refundsInfo = refundInfoService.createRefundByOrderNo(orderNo, reason); log.info("调用退款API");
处理创建退款单逻辑
// 根据订单号生成退款订单 RefundInfo refundInfo = new RefundInfo(); refundInfo.setOrderNo(orderNo);//订单编号 refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号 refundInfo.setTotalFee();//原订单金额(分) refundInfo.setRefund();//退款金额(分) refundInfo.setReason(reason);//退款原因
创建实体把参数注入进去剩下的我们还缺少订单金额 我们传递了个参数是订单编号 那么我们想想看这个在哪里搞?
调用订单服务获取金额信息💰
注入订单服务IOC
/** * 订单信息服务 */ private final OrderInfoService orderInfoService;
发起查询订单信息接口
// 根据订单号获取订单信息 OrderInfo orderInfo = orderInfoService.lambdaQuery().eq(OrderInfo::getOrderNo, orderNo).one();
最终插入数据库
/** * 创建退款订单根据订单号 * * @param orderNo 订单号 * @param reason 退款原因 * @return {@link RefundInfo} */ public RefundInfo createRefundByOrderNo(String orderNo, String reason) { // 根据订单号获取订单信息 OrderInfo orderInfo = orderInfoService.lambdaQuery().eq(OrderInfo::getOrderNo, orderNo).one(); // 根据订单号生成退款订单 RefundInfo refundInfo = new RefundInfo(); refundInfo.setOrderNo(orderNo);//订单编号 refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号 refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分) refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分) refundInfo.setReason(reason);//退款原因 //保存退款订单 baseMapper.insert(refundInfo); return refundInfo; }
调用退款API
**请求URL:**https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
**请求方式:**POST
请求参数:
参数名 变量 类型[长度限制] 必填 描述
微信支付订单号 transaction_id string[1, 32] 二选一 body原支付交易对应的微信订单号 示例值:1217752501201407033233368018
商户订单号 out_trade_no string[6, 32] 二选一 body原支付交易对应的商户订单号 示例值:1217752501201407033233368018
商户退款单号 out_refund_no string[1, 64] 是 body商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 示例值:1217752501201407033233368018
退款原因 reason string[1, 64] 否 不一定要填写
金额信息 amount object 是 body订单金额信息
* 退款金额 refund int 是 退款金额,单位为分,只能为整数,不能超过原订单支付金额。示例值:888
* 原订单金额 total int 是 原支付交易的订单总金额,单位为分,只能为整数。示例值:888
发起API前言
- 组装调用API
- 组装请求参数
- 解析返回的响应数据
- 更新订单状态和更新退款单将本次的返回json保存
- 根据前面提到的退款完成后微信会发起一个退款回调信息的处理
WxApiType 请求API 枚举 基础项目搭建的内容不要忘记咯
// 调用统一下单API String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType()); HttpPost httpPost = new HttpPost(url); // 请求body参数 Map<String, Object> paramsMap = new HashMap<>(); paramsMap.put("out_trade_no", orderNo);//订单编号 paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号 paramsMap.put("reason", reason);//退款原因 paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址 Map<String, Object> amountMap = new HashMap<>(); amountMap.put("refund", refundsInfo.getRefund());// 退款金额 amountMap.put("total", refundsInfo.getTotalFee());// 原订单金额 amountMap.put("currency", "CNY");// 退款币种 paramsMap.put("amount", amountMap); //将参数转换成json字符串 String jsonParams = JSONUtil.toJsonStr(paramsMap); log.info("请求参数 ===> {}", jsonParams); StringEntity entity = new StringEntity(jsonParams, "utf-8"); entity.setContentType("application/json");//设置请求报文格式 httpPost.setEntity(entity);//将请求报文放入请求对象 httpPost.setHeader("Accept", "application/json");//设置响应报文格式
组装完毕执行请求
// 完成签名并执行请求,并完成验签 try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) { //解析响应结果 JSONObject resultMap = buildBodyParams(response, JSONObject.class); log.info("退款返回结果 ===> {}", resultMap); // 更新订单状态 orderInfoService.lambdaUpdate().eq(OrderInfo::getOrderNo, orderNo).set(OrderInfo::getOrderStatus, OrderStatus.REFUND_PROCESSING.getType()).update(); // 更新退款单 refundInfoService.updateRefund(resultMap); }
修改 refundInfoService 创建 updateRefund 方法保存本次退款响应的部分重要信息
退款返回参数:
更新逻辑
- 根据我们创建的退款单的退款单号为条件
- 填充数据库对应字段参数
- 判断当前是否为退款返回的响应而不是退款回调的响应参数
- 执行更新语句
首先说一下 序号三的问题
可以看到申请退款接口返回的响应状态字段是 status
可以看到退款回调接口返回的响应状态字段是 refund_status
由此可以看出来我们需要做不同的处理因为到时候要调用我们这个更新退款单的方法代码逻辑都是一样的直接共用即可
完整的处理逻辑
/** * 更新退款单 * * @param resultMap 退款响应结果 */ public void updateRefund(JSONObject resultMap) { // 根据退款单编号修改退款单 QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("refund_no", resultMap.getStr("out_refund_no")); // 设置要修改的字段 RefundInfo refundInfo = new RefundInfo(); refundInfo.setRefundId(resultMap.getStr("refund_id"));//微信支付退款单号 // 查询退款和申请退款中的返回参数 if (resultMap.get("status") != null) { refundInfo.setRefundStatus(resultMap.getStr("status"));//退款状态 refundInfo.setContentReturn(resultMap.toString());//将全部响应结果存入数据库的content字段 } // 退款回调中的回调参数 if (resultMap.get("refund_status") != null) { refundInfo.setRefundStatus(resultMap.getStr("refund_status"));//退款状态 refundInfo.setContentNotify(resultMap.toString());//将全部响应结果存入数据库的content字段 } //更新退款单 baseMapper.update(refundInfo, queryWrapper); }