最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

1.5 微信Native支付

维修 admin 21浏览 0评论

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中进行测试

这个地方是前端将数据转换成文件的,在后端将数据转成文件然后传输给前端也可以

与本文相关的文章

发布评论

评论列表 (0)

  1. 暂无评论