1.5 微信Native支付
文章目录
- 用户退款
- 一、 申请退款
- 1.1 数据库表
- 1.2 Controller
- 1.3 Service
- 1.3.1 创建退款单记录
- 1.3.2 更新订单状态与更新退款单
- 1.3.3 调用退款API
- 1.3.4 效果图
- 二、查询退款API
- 三、退款结果通知
- 3.1 Controller
- 3.2 Service处理退款单
- 四、账单
- 4.1 申请账单API
- 4.2 下载账单API
用户退款
其实用户退款的流程和用户支付的流程是差不多的,所以可以参考支付的代码进行编写
1.3 微信Native支付 -下单、定时查单、取消订单、签名-CSDN博客
1.4 内网穿透与通知、查询用户订单-CSDN博客
一、 申请退款
官方退款请求
1.1 数据库表
@Data
@TableName("t_refund_info")
public class RefundInfo extends BaseEntity{private String orderNo;//商品订单编号private String refundNo;//退款单编号private String refundId;//支付系统退款单号private Integer totalFee;//原订单金额(分)private Integer refund;//退款金额(分)private String reason;//退款原因private String refundStatus;//退款单状态private String contentReturn;//申请退款返回参数private String contentNotify;//退款结果通知参数
}
1.2 Controller
@ApiOperation("申请退款")
@PostMapping("/refunds/{orderNo}/{reason}")
public R refunds(@PathVariable String orderNo,@PathVariable String reason){log.info("申请亏款");wxPayService.refund(orderNo,reason);return R.ok();
}
1.3 Service
1.3.1 创建退款单记录
log.info("创建退款单记录");
//根据订单编号创建退款单
RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);
/*** 根据订单号创建退款订单* @param orderNo* @return*/
@Override
public RefundInfo createRefundByOrderNo(String orderNo, String reason) {//根据订单号获取订单信息OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);//根据订单号生成退款订单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;
}
1.3.2 更新订单状态与更新退款单
//更新订单状态
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);//更新退款单
refundsInfoService.updateRefund(bodyAsString);
/*** 记录退款记录* @param content*/
@Override
public void updateRefund(String content) {//将json字符串转换成MapHashMap resultMap = JSONObject.parseObject(content, HashMap.class);//根据退款单编号修改退款单QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));//设置要修改的字段RefundInfo refundInfo = new RefundInfo();refundInfo.setRefundId( resultMap.get("refund_id").toString());//微信支付退款单号//查询退款和申请退款中的返回参数if(resultMap.get("status") != null){refundInfo.setRefundStatus( resultMap.get("status").toString());//退款状态refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段}//退款回调中的回调参数if(resultMap.get("refund_status") != null){refundInfo.setRefundStatus(resultMap.get("refund_status").toString());//退款状态refundInfo.setContentNotify(content);//将全部响应结果存入数据库的content字段}//更新退款单baseMapper.update(refundInfo, queryWrapper);
}
/*** 根据订单号更新订单状态*/
@Override
public void updateStatusByOrderNo(String outTradeNo, OrderStatus orderStatus) {log.info("更新订单状态 ===> {}", orderStatus.getType());QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("order_no", outTradeNo);OrderInfo orderInfo = new OrderInfo();orderInfo.setOrderStatus(orderStatus.getType());baseMapper.update(orderInfo, queryWrapper);
}
1.3.3 调用退款API
我们这个地方退款就是退全款,如果想退部分金额也可以,多次退款也可以
/*** 用户退款* @param orderNo 商户订单号* @param reason 退款原因*/@Transactional(rollbackFor = Exception.class)@Overridepublic void refund(String orderNo, String reason) throws Exception {log.info("创建退款单记录");//根据订单编号创建退款单RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);log.info("调用退款API");//调用统一下单APIString url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());HttpPost httpPost = new HttpPost(url);// 请求body参数Map 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 amountMap = new HashMap();amountMap.put("refund", refundsInfo.getRefund());//退款金额amountMap.put("total", refundsInfo.getTotalFee());//原订单金额amountMap.put("currency", "CNY");//退款币种paramsMap.put("amount", amountMap);//将参数转换成json字符串
// String jsonParams = gson.toJson(paramsMap);String jsonParams = JSONObject.toJSONString(paramsMap);log.info("请求参数 ===> {}" + jsonParams);StringEntity entity = new StringEntity(jsonParams,"utf-8");entity.setContentType("application/json");//设置请求报文格式httpPost.setEntity(entity);//将请求报文放入请求对象httpPost.setHeader("Accept", "application/json");//设置响应报文格式//完成签名并执行请求,并完成验签CloseableHttpResponse response = httpClient.execute(httpPost);try {//解析响应结果String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);}//更新订单状态orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);//更新退款单refundsInfoService.updateRefund(bodyAsString);} finally {response.close();}}
1.3.4 效果图
二、查询退款API
查询退款API和查询订单API几乎是一个样子的
我们规定,当商户平台的某个退款订单在五分钟之内没有收到退款通知,我们商户平台就会主动调用查询退款API,查查是怎么个事
注意商户退款单号是一个路径参数
/*** 查询退款接口调用* @param refundNo* @return*/
@Override
public String queryRefund(String refundNo) throws Exception {log.info("查询退款接口调用 ===> {}", refundNo);String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);url = wxPayConfig.getDomain().concat(url);//创建远程Get 请求对象HttpGet httpGet = new HttpGet(url);httpGet.setHeader("Accept", "application/json");//完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 查询退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);}return bodyAsString;} finally {response.close();}
}
自己写个接口测试一下
/*** 查询退款* @param refundNo* @return* @throws Exception*/
@ApiOperation("查询退款:测试用")
@GetMapping("/query-refund/{refundNo}")
public R queryRefund(@PathVariable String refundNo) throws Exception {log.info("查询退款");String result = wxPayService.queryRefund(refundNo);return R.ok().setMessage("查询成功").data("result", result);
}
三、退款结果通知
与之前写的很类似,只不过一个是支付通知,一个是退款通知1.4 内网穿透与通知、查询用户订单-CSDN博客
3.1 Controller
/*** 退款结果通知* 退款状态改变后,微信会把相关退款结果发送给商户。*/
@ApiOperation("退款结果通知")
@PostMapping("/refunds/notify")
public String refundsNotify(HttpServletRequest request, HttpServletResponse response){log.info("退款通知执行");Map<String, String> map = new HashMap<>();//应答对象try {//处理通知参数String body = HttpUtils.readData(request);HashMap<String, Object> bodyMap = JSONObject.parseObject(body, HashMap.class);String requestId = (String)bodyMap.get("id");log.info("支付通知的id ===> {}", requestId);//签名的验证WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest= new WechatPay2ValidatorForRequest(verifier, requestId, body);if(!wechatPay2ValidatorForRequest.validate(request)){log.error("通知验签失败");//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "通知验签失败");return JSONObject.toJSONString(map);}log.info("通知验签成功");//处理退款单wxPayService.processRefund(bodyMap);//成功应答response.setStatus(200);map.put("code", "SUCCESS");map.put("message", "成功");return JSONObject.toJSONString(map);} catch (Exception e) {e.printStackTrace();//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "失败");return JSONObject.toJSONString(map);}
}
3.2 Service处理退款单
/*** 处理退款单*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processRefund(Map<String, Object> bodyMap) throws Exception {log.info("退款单");JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(bodyMap));//解密报文String plainText = decryptFromResource(jsonObject);//将明文转换成mapHashMap plainTextMap = JSONObject.parseObject(plainText,HashMap.class);String orderNo = (String)plainTextMap.get("out_trade_no");if(lock.tryLock()){try {String orderStatus = orderInfoService.getOrderStatus(orderNo);if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {return;}//更新订单状态orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);//更新退款单refundsInfoService.updateRefund(plainText);} finally {//要主动释放锁lock.unlock();}}
}
四、账单
账单的作用就是方便商户进行对账
.html
现申请账单的url,虽然后将url当做下载交易/资金账单的参数向微信支付平台发起请求下载账单
4.1 申请账单API
下图所示是交易账单,交易账单更多的是针对微信用户
下图所示是资金账单,更多的是侧重资金流水
我们为了方便,下载交易账单和资金账单都写在一块,请求的时候添加一个type区分是申请交易账单还是下载资金账单
@ApiOperation("获取账单url:测试用")
@GetMapping("/querybill/{billDate}/{type}")
public R queryTradeBill(@PathVariable String billDate,@PathVariable String type) throws Exception {log.info("获取账单url");String downloadUrl = wxPayService.queryBill(billDate, type);return R.ok().setMessage("获取账单url成功").data("downloadUrl", downloadUrl);
}
Service层处理
/*** 申请账单* @param billDate* @param type* @return* @throws Exception*/
@Override
public String queryBill(String billDate, String type) throws Exception {log.warn("申请账单接口调用 {}", billDate);String url = "";if("tradebill".equals(type)){url = WxApiType.TRADE_BILLS.getType();}else if("fundflowbill".equals(type)){url = WxApiType.FUND_FLOW_BILLS.getType();}else{throw new RuntimeException("不支持的账单类型");}url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);//创建远程Get 请求对象HttpGet httpGet = new HttpGet(url);httpGet.addHeader("Accept", "application/json");//使用wxPayClient发送请求得到响应CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 申请账单返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("申请账单异常, 响应码 = " + statusCode+ ", 申请账单返回结果 = " + bodyAsString);}//获取账单下载地址Map<String, String> resultMap = JSONObject.parseObject(bodyAsString, HashMap.class);return resultMap.get("download_url");} finally {response.close();}
}
我们复制上面的"downloadUrl": "=U62KXq-sD-MreORg6ZzSRIjztZAdN-LNcSlwOKIkhnizBe2jMYDnhWkB7xXfC_Gk"url是没有办法下载的,只能通过下载账单API进行下载
4.2 下载账单API
首先记得在配置类中增加一个wxPayNoSignClient,这个对象不需要验签,因为我们下载账单API需要跳过验签的流程
/*** 获取HttpClient,无需进行应答签名验证,跳过验签的流程*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){//获取商户私钥PrivateKey privateKey = getPrivateKey(privateKeyPath);//用于构造HttpClientWechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()//设置商户信息.withMerchant(mchId, mchSerialNo, privateKey)//无需进行签名验证、通过withValidator((response) -> true)实现.withValidator((response) -> true);// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新CloseableHttpClient httpClient = builder.build();log.info("== getWxPayNoSignClient END ==");return httpClient;
}
下载账单的接口
@GetMapping("/downloadbill/{billDate}/{type}")
public R downloadBill(@PathVariable String billDate,@PathVariable String type) throws Exception {log.info("下载账单");String result = wxPayService.downloadBill(billDate, type);return R.ok().data("result", result);
}
下载账单的具体实现,一定要记得跳过验签
@Resourceprivate CloseableHttpClient wxPayNoSignClient; //无需应答签名,这个地方要用这个对象/*** 下载账单* @param billDate* @param type* @return* @throws Exception*/@Overridepublic String downloadBill(String billDate, String type) throws Exception {log.warn("下载账单接口调用 {}, {}", billDate, type);//获取账单url地址String downloadUrl = this.queryBill(billDate, type);//创建远程Get 请求对象HttpGet httpGet = new HttpGet(downloadUrl);httpGet.addHeader("Accept", "application/json");//使用wxPayClient发送请求得到响应CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 下载账单返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("下载账单异常, 响应码 = " + statusCode+ ", 下载账单返回结果 = " + bodyAsString);}return bodyAsString;} finally {response.close();}}
在Swagger中进行测试
这个地方是前端将数据转换成文件的,在后端将数据转成文件然后传输给前端也可以
1.5 微信Native支付
文章目录
- 用户退款
- 一、 申请退款
- 1.1 数据库表
- 1.2 Controller
- 1.3 Service
- 1.3.1 创建退款单记录
- 1.3.2 更新订单状态与更新退款单
- 1.3.3 调用退款API
- 1.3.4 效果图
- 二、查询退款API
- 三、退款结果通知
- 3.1 Controller
- 3.2 Service处理退款单
- 四、账单
- 4.1 申请账单API
- 4.2 下载账单API
用户退款
其实用户退款的流程和用户支付的流程是差不多的,所以可以参考支付的代码进行编写
1.3 微信Native支付 -下单、定时查单、取消订单、签名-CSDN博客
1.4 内网穿透与通知、查询用户订单-CSDN博客
一、 申请退款
官方退款请求
1.1 数据库表
@Data
@TableName("t_refund_info")
public class RefundInfo extends BaseEntity{private String orderNo;//商品订单编号private String refundNo;//退款单编号private String refundId;//支付系统退款单号private Integer totalFee;//原订单金额(分)private Integer refund;//退款金额(分)private String reason;//退款原因private String refundStatus;//退款单状态private String contentReturn;//申请退款返回参数private String contentNotify;//退款结果通知参数
}
1.2 Controller
@ApiOperation("申请退款")
@PostMapping("/refunds/{orderNo}/{reason}")
public R refunds(@PathVariable String orderNo,@PathVariable String reason){log.info("申请亏款");wxPayService.refund(orderNo,reason);return R.ok();
}
1.3 Service
1.3.1 创建退款单记录
log.info("创建退款单记录");
//根据订单编号创建退款单
RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);
/*** 根据订单号创建退款订单* @param orderNo* @return*/
@Override
public RefundInfo createRefundByOrderNo(String orderNo, String reason) {//根据订单号获取订单信息OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);//根据订单号生成退款订单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;
}
1.3.2 更新订单状态与更新退款单
//更新订单状态
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);//更新退款单
refundsInfoService.updateRefund(bodyAsString);
/*** 记录退款记录* @param content*/
@Override
public void updateRefund(String content) {//将json字符串转换成MapHashMap resultMap = JSONObject.parseObject(content, HashMap.class);//根据退款单编号修改退款单QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));//设置要修改的字段RefundInfo refundInfo = new RefundInfo();refundInfo.setRefundId( resultMap.get("refund_id").toString());//微信支付退款单号//查询退款和申请退款中的返回参数if(resultMap.get("status") != null){refundInfo.setRefundStatus( resultMap.get("status").toString());//退款状态refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段}//退款回调中的回调参数if(resultMap.get("refund_status") != null){refundInfo.setRefundStatus(resultMap.get("refund_status").toString());//退款状态refundInfo.setContentNotify(content);//将全部响应结果存入数据库的content字段}//更新退款单baseMapper.update(refundInfo, queryWrapper);
}
/*** 根据订单号更新订单状态*/
@Override
public void updateStatusByOrderNo(String outTradeNo, OrderStatus orderStatus) {log.info("更新订单状态 ===> {}", orderStatus.getType());QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("order_no", outTradeNo);OrderInfo orderInfo = new OrderInfo();orderInfo.setOrderStatus(orderStatus.getType());baseMapper.update(orderInfo, queryWrapper);
}
1.3.3 调用退款API
我们这个地方退款就是退全款,如果想退部分金额也可以,多次退款也可以
/*** 用户退款* @param orderNo 商户订单号* @param reason 退款原因*/@Transactional(rollbackFor = Exception.class)@Overridepublic void refund(String orderNo, String reason) throws Exception {log.info("创建退款单记录");//根据订单编号创建退款单RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);log.info("调用退款API");//调用统一下单APIString url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());HttpPost httpPost = new HttpPost(url);// 请求body参数Map 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 amountMap = new HashMap();amountMap.put("refund", refundsInfo.getRefund());//退款金额amountMap.put("total", refundsInfo.getTotalFee());//原订单金额amountMap.put("currency", "CNY");//退款币种paramsMap.put("amount", amountMap);//将参数转换成json字符串
// String jsonParams = gson.toJson(paramsMap);String jsonParams = JSONObject.toJSONString(paramsMap);log.info("请求参数 ===> {}" + jsonParams);StringEntity entity = new StringEntity(jsonParams,"utf-8");entity.setContentType("application/json");//设置请求报文格式httpPost.setEntity(entity);//将请求报文放入请求对象httpPost.setHeader("Accept", "application/json");//设置响应报文格式//完成签名并执行请求,并完成验签CloseableHttpResponse response = httpClient.execute(httpPost);try {//解析响应结果String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);}//更新订单状态orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);//更新退款单refundsInfoService.updateRefund(bodyAsString);} finally {response.close();}}
1.3.4 效果图
二、查询退款API
查询退款API和查询订单API几乎是一个样子的
我们规定,当商户平台的某个退款订单在五分钟之内没有收到退款通知,我们商户平台就会主动调用查询退款API,查查是怎么个事
注意商户退款单号是一个路径参数
/*** 查询退款接口调用* @param refundNo* @return*/
@Override
public String queryRefund(String refundNo) throws Exception {log.info("查询退款接口调用 ===> {}", refundNo);String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);url = wxPayConfig.getDomain().concat(url);//创建远程Get 请求对象HttpGet httpGet = new HttpGet(url);httpGet.setHeader("Accept", "application/json");//完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 查询退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);}return bodyAsString;} finally {response.close();}
}
自己写个接口测试一下
/*** 查询退款* @param refundNo* @return* @throws Exception*/
@ApiOperation("查询退款:测试用")
@GetMapping("/query-refund/{refundNo}")
public R queryRefund(@PathVariable String refundNo) throws Exception {log.info("查询退款");String result = wxPayService.queryRefund(refundNo);return R.ok().setMessage("查询成功").data("result", result);
}
三、退款结果通知
与之前写的很类似,只不过一个是支付通知,一个是退款通知1.4 内网穿透与通知、查询用户订单-CSDN博客
3.1 Controller
/*** 退款结果通知* 退款状态改变后,微信会把相关退款结果发送给商户。*/
@ApiOperation("退款结果通知")
@PostMapping("/refunds/notify")
public String refundsNotify(HttpServletRequest request, HttpServletResponse response){log.info("退款通知执行");Map<String, String> map = new HashMap<>();//应答对象try {//处理通知参数String body = HttpUtils.readData(request);HashMap<String, Object> bodyMap = JSONObject.parseObject(body, HashMap.class);String requestId = (String)bodyMap.get("id");log.info("支付通知的id ===> {}", requestId);//签名的验证WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest= new WechatPay2ValidatorForRequest(verifier, requestId, body);if(!wechatPay2ValidatorForRequest.validate(request)){log.error("通知验签失败");//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "通知验签失败");return JSONObject.toJSONString(map);}log.info("通知验签成功");//处理退款单wxPayService.processRefund(bodyMap);//成功应答response.setStatus(200);map.put("code", "SUCCESS");map.put("message", "成功");return JSONObject.toJSONString(map);} catch (Exception e) {e.printStackTrace();//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "失败");return JSONObject.toJSONString(map);}
}
3.2 Service处理退款单
/*** 处理退款单*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processRefund(Map<String, Object> bodyMap) throws Exception {log.info("退款单");JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(bodyMap));//解密报文String plainText = decryptFromResource(jsonObject);//将明文转换成mapHashMap plainTextMap = JSONObject.parseObject(plainText,HashMap.class);String orderNo = (String)plainTextMap.get("out_trade_no");if(lock.tryLock()){try {String orderStatus = orderInfoService.getOrderStatus(orderNo);if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {return;}//更新订单状态orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);//更新退款单refundsInfoService.updateRefund(plainText);} finally {//要主动释放锁lock.unlock();}}
}
四、账单
账单的作用就是方便商户进行对账
.html
现申请账单的url,虽然后将url当做下载交易/资金账单的参数向微信支付平台发起请求下载账单
4.1 申请账单API
下图所示是交易账单,交易账单更多的是针对微信用户
下图所示是资金账单,更多的是侧重资金流水
我们为了方便,下载交易账单和资金账单都写在一块,请求的时候添加一个type区分是申请交易账单还是下载资金账单
@ApiOperation("获取账单url:测试用")
@GetMapping("/querybill/{billDate}/{type}")
public R queryTradeBill(@PathVariable String billDate,@PathVariable String type) throws Exception {log.info("获取账单url");String downloadUrl = wxPayService.queryBill(billDate, type);return R.ok().setMessage("获取账单url成功").data("downloadUrl", downloadUrl);
}
Service层处理
/*** 申请账单* @param billDate* @param type* @return* @throws Exception*/
@Override
public String queryBill(String billDate, String type) throws Exception {log.warn("申请账单接口调用 {}", billDate);String url = "";if("tradebill".equals(type)){url = WxApiType.TRADE_BILLS.getType();}else if("fundflowbill".equals(type)){url = WxApiType.FUND_FLOW_BILLS.getType();}else{throw new RuntimeException("不支持的账单类型");}url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);//创建远程Get 请求对象HttpGet httpGet = new HttpGet(url);httpGet.addHeader("Accept", "application/json");//使用wxPayClient发送请求得到响应CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 申请账单返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("申请账单异常, 响应码 = " + statusCode+ ", 申请账单返回结果 = " + bodyAsString);}//获取账单下载地址Map<String, String> resultMap = JSONObject.parseObject(bodyAsString, HashMap.class);return resultMap.get("download_url");} finally {response.close();}
}
我们复制上面的"downloadUrl": "=U62KXq-sD-MreORg6ZzSRIjztZAdN-LNcSlwOKIkhnizBe2jMYDnhWkB7xXfC_Gk"url是没有办法下载的,只能通过下载账单API进行下载
4.2 下载账单API
首先记得在配置类中增加一个wxPayNoSignClient,这个对象不需要验签,因为我们下载账单API需要跳过验签的流程
/*** 获取HttpClient,无需进行应答签名验证,跳过验签的流程*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){//获取商户私钥PrivateKey privateKey = getPrivateKey(privateKeyPath);//用于构造HttpClientWechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()//设置商户信息.withMerchant(mchId, mchSerialNo, privateKey)//无需进行签名验证、通过withValidator((response) -> true)实现.withValidator((response) -> true);// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新CloseableHttpClient httpClient = builder.build();log.info("== getWxPayNoSignClient END ==");return httpClient;
}
下载账单的接口
@GetMapping("/downloadbill/{billDate}/{type}")
public R downloadBill(@PathVariable String billDate,@PathVariable String type) throws Exception {log.info("下载账单");String result = wxPayService.downloadBill(billDate, type);return R.ok().data("result", result);
}
下载账单的具体实现,一定要记得跳过验签
@Resourceprivate CloseableHttpClient wxPayNoSignClient; //无需应答签名,这个地方要用这个对象/*** 下载账单* @param billDate* @param type* @return* @throws Exception*/@Overridepublic String downloadBill(String billDate, String type) throws Exception {log.warn("下载账单接口调用 {}, {}", billDate, type);//获取账单url地址String downloadUrl = this.queryBill(billDate, type);//创建远程Get 请求对象HttpGet httpGet = new HttpGet(downloadUrl);httpGet.addHeader("Accept", "application/json");//使用wxPayClient发送请求得到响应CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 下载账单返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("下载账单异常, 响应码 = " + statusCode+ ", 下载账单返回结果 = " + bodyAsString);}return bodyAsString;} finally {response.close();}}
在Swagger中进行测试
这个地方是前端将数据转换成文件的,在后端将数据转成文件然后传输给前端也可以