Springboot整合微信支付(订单过期取消及商户主动查单)
目录
- 一:问题引入
- 二:处理流程
- 三:代码实现
一:问题引入 前面讲到用户支付完成之后微信支付服务器会发送回调通知给商户,商户要能够正常处理这个回调通知并返回正确的状态码给微信支付后台服务器,不然微信支付后台服务器就会在一段时间之内重复发送回调通知给商户。具体流程见下图:
文章图片
那么这时候问题就来了,微信后台发送回调通知次数也是有限制的,而且,微信支付开发文档中这样说到:对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。也就是说我们不能单单通过微信支付的回调通知来被动地更新订单状态,假如接收微信回调通知失败但是这时候用户是已经付了款的,而商户这边却显示未付款状态,假如没有作进一步处理就会造成一些不必要的麻烦。这时候就需要商户主动向微信支付后台查询订单状态。
二:处理流程
文章图片
一开始我采用的策略并不是延迟队列,而是采用定时器定时查询数据库来实现商户主动查单并实现订单过期自动取消功能,但是这一做法十分不友好,体现在下面几个方面:
- 定时查询数据库会加大数据库负担
- 假如数据量很大,频繁查询数据库消耗的时间较多
- 会造成时间误差,定时时间过长误差大,定时时间过短查询又太频繁
实现订单过期自动删除的策略有很多,其中一种就是我上面提到的数据库轮询方法,此外,还可以采用的策略有:JDK的延迟队列、时间轮算法、redis缓存、使用消息队列等等,我选用的策略是采用RabbitMQ的延迟队列来实现,至于延迟队列的实现细节我将在下一篇文章讲解,这里仅介绍订单处理部分。
处理策略为商户下单之后生成订单存入数据库并将该订单号存入延迟队列,此时订单状态为“未支付”,假如接收微信回调成功并且验证到用户已付款,这时候就更新数据库中该订单状态为“已付款”。当延迟队列到期进行消费时,根据延迟队列中的订单号先在数据库中进行查询,假如这时候数据库中该订单状态为“已支付”,这时候就不需要进行处理,假如订单状态为“未支付”,商户程序应主动向微信支付后台进行订单状态查询,如果订单状态为已支付,这时候就不需要进行处理,如果订单状态为未支付,这时候就将订单状态改为“已取消”。一开始我的做法为无论数据库中该订单状态是否已支付都向微信支付后台进行订单状态查询,然后再根据查询结果做进一步处理,显然这种做法存在缺陷,就是每一笔订单都主动向微信支付后台进行查询会消耗很大的网络带宽,而且假如已经成功接收到微信支付回调通知的订单并不需要进行再次查询确认。
三:代码实现 3.1:orderServiceImpl.java
/*** 提交订单* @param orders* @param session*/@Override@Transactionalpublic Orders createOrder(Orders orders, HttpSession session) {//获取当前用户信息Long userId = (Long) session.getAttribute("user"); //查询地址数据Long addressBookId = orders.getAddressBookId(); AddressBook addressBook = addressBookService.getById(addressBookId); if(addressBook == null) {throw new CustomException("用户地址信息有误,不能下单"); }//获取当前用户购物车数据LambdaQueryWrapper SCLqw = new LambdaQueryWrapper<>(); SCLqw.eq(ShoppingCart::getUserId,userId); List shoppingCartList = shoppingCartService.list(SCLqw); //生成订单号long orderId = IdWorker.getId(); //设置订单号orders.setNumber(String.valueOf(orderId)); //设置订单状态(待付款)orders.setStatus(1); //设置下单用户idorders.setUserId(userId); //设置下单时间orders.setOrderTime(LocalDateTime.now()); //设置付款时间orders.setCheckoutTime(LocalDateTime.now()); //设置实收金额AtomicInteger amount = new AtomicInteger(0); for(ShoppingCart shoppingCart : shoppingCartList) {amount.addAndGet(shoppingCart.getAmount().multiply(new BigDecimal(100)).multiply(new BigDecimal(shoppingCart.getNumber())).intValue()); }orders.setAmount(BigDecimal.valueOf(amount.get())); //设置用户信息User user = userService.getById(userId); orders.setPhone(addressBook.getPhone()); orders.setAddress(addressBook.getDetail()); orders.setUserName(user.getPhone()); orders.setConsignee(addressBook.getConsignee()); //保存单条订单信息this.save(orders); //设置订单详细信息ListorderDetailList = new ArrayList<>(); for(ShoppingCart shoppingCart : shoppingCartList) {OrderDetail orderDetail = new OrderDetail(); orderDetail.setName(shoppingCart.getName()); orderDetail.setImage(shoppingCart.getImage()); orderDetail.setOrderId(orderId); orderDetail.setDishId(shoppingCart.getDishId()); orderDetail.setSetmealId(shoppingCart.getSetmealId()); orderDetail.setDishFlavor(shoppingCart.getDishFlavor()); orderDetail.setNumber(shoppingCart.getNumber()); AtomicInteger detailAmount = new AtomicInteger(0); detailAmount.addAndGet(shoppingCart.getAmount().multiply(new BigDecimal(shoppingCart.getNumber())).intValue()); orderDetail.setAmount(BigDecimal.valueOf(detailAmount.get())); orderDetailList.add(orderDetail); }//批量保存订单详细数据orderDetailService.saveBatch(orderDetailList); //清空购物车数据shoppingCartService.remove(SCLqw); //设置延迟队列,过期时间为5分钟log.info("订单编号:{}进入延迟队列...",orders.getNumber()); delayProducer.publish(orders.getNumber(),String.valueOf(orders.getId()),DelayMessageConfig.DELAY_EXCHANGE_NAME,DelayMessageConfig.ROUTING_KEY_ORDER,1000*60*5); return orders; }
3.2:RabbitmqDelayConsumer.java
/*** 监听订单延迟队列* @param orderNo* @throws Exception*/@RabbitListener(queues = {"plugin.delay.order.queue"})public void orderDelayQueue(String orderNo, Message message, Channel channel) throws Exception {log.info("订单延迟队列开始消费..."); try {//处理订单wxPayService.checkOrderStatus(orderNo); //告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); log.info("消息接收成功"); } catch (Exception e) {e.printStackTrace(); //消息重新入队channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,true); log.info("消息接收失败,重新入队"); }}
3.3:WxPayServiceImpl.java
/*** 商户主动查询订单状态* 当核实到订单超时未支付则取消订单* 当核实到订单已支付则更新订单状态*/@Transactional(rollbackFor = Exception.class)@Overridepublic void checkOrderStatus(String orderNo) throws Exception {log.info("根据订单号核实订单状态==>{}",orderNo); log.info("在数据库中查询订单状态...."); Integer status = ordersService.getOrderStatus(orderNo); if(status != 1) {//订单不是”未支付“状态log.info("订单不是”未支付“状态,无需进行进一步处理"); return; }String result = this.queryOrder(orderNo); Gson gson = new Gson(); Map map = gson.fromJson(result, HashMap.class); //获取订单状态String tradeState = map.get("trade_state"); //判断订单状态if(WxTradeState.NOTPAY.getType().equals(tradeState)) {log.info("核实到订单超时未支付==>{}",orderNo); //关闭订单log.info("订单已自动取消"); this.closeOrder(orderNo); //更新本地订单状态ordersService.updateStatusByOrderNo(orderNo,"5"); }else if(WxTradeState.SUCCESS.getType().equals(tradeState)) {log.info("核实到订单已支付==>{}",orderNo); Integer orderStatus = ordersService.getOrderStatus(orderNo); if(orderStatus != null && orderStatus != 2) {//更新本地订单状态ordersService.updateStatusByOrderNo(orderNo,"2"); //保存订单记录paymentInfoService.saveInfo(result); }}else if(WxTradeState.CLOSED.getType().equals(tradeState)) {log.info("核实到订单已取消==>{}",orderNo); Integer orderStatus = ordersService.getOrderStatus(orderNo); if(orderStatus != null && orderStatus != 5) {//更新本地订单状态ordersService.updateStatusByOrderNo(orderNo,"5"); //保存订单记录paymentInfoService.saveInfo(result); }}}/*** 查询订单* @param orderNo* @return*/@Overridepublic String queryOrder(String orderNo) throws Exception {log.info("查单接口调用===>{}",orderNo); //构建请求链接String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(),orderNo); url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId()); URIBuilder uriBuilder = new URIBuilder(url); HttpGet httpGet = new HttpGet(uriBuilder.build()); httpGet.addHeader("Accept","application/json"); CloseableHttpResponse response = wxPayClient.execute(httpGet); return EntityUtils.toString(response.getEntity()); }
【Springboot整合微信支付(订单过期取消及商户主动查单)】到此这篇关于Springboot整合微信支付(订单过期取消及商户主动查单)的文章就介绍到这了,更多相关Springboot 微信支付内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- SpringBoot整合Redis实现常用功能超详细过程
- SpringBoot整合Redis实现常用功能
- 现实与电子游戏(整合)
- 全干工程师|推荐一个好用的微信、支付宝等Rust三方服务框架
- SpringBoot|SpringBoot读取配置文件到实体类和静态变量
- 微信小程序使用navigator实现页面跳转功能
- SpringBoot|SpringBoot 注解简介(持续更新)
- 微信|我的朋友圈被“折叠”了,微信说是为了我好
- 微信小程序音频录制波纹循环动画
- github|微信小程序开源到github并更新的步骤