一:问题引入

前面讲到用户支付完成之后微信支付服务器会发送回调通知给商户,商户要能够正常处理这个回调通知并返回正确的状态码给微信支付后台服务器,不然微信支付后台服务器就会在一段时间之内重复发送回调通知给商户。具体流程见下图:

在这里插入图片描述

那么这时候问题就来了,微信后台发送回调通知次数也是有限制的,而且,微信支付开发文档中这样说到:对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。也就是说我们不能单单通过微信支付的回调通知来被动地更新订单状态,假如接收微信回调通知失败但是这时候用户是已经付了款的,而商户这边却显示未付款状态,假如没有作进一步处理就会造成一些不必要的麻烦。这时候就需要商户主动向微信支付后台查询订单状态。

二:处理流程

在这里插入图片描述

一开始我采用的策略并不是延迟队列,而是采用定时器定时查询数据库来实现商户主动查单并实现订单过期自动取消功能,但是这一做法十分不友好,体现在下面几个方面:

  • 定时查询数据库会加大数据库负担
  • 假如数据量很大,频繁查询数据库消耗的时间较多
  • 会造成时间误差,定时时间过长误差大,定时时间过短查询又太频繁

假如我设定的定时时间是5分钟查询一次,那么假如定时器还有1秒就要去查询一次,但是有一批订单还有2秒才到期,这时候定时器就处理不到这批订单。等下次再进行处理时候这批订单已经过期差不多5分钟了,这显然是很大的一个缺陷。此外假如很长一段时间都没有用户下单,但是由于定时器并不知道什么时候有用户下单什么时候没有用户下单,它只是个一到点就开始定时查询的无感情机器,这样就会产生一些不必要的开销。

实现订单过期自动删除的策略有很多,其中一种就是我上面提到的数据库轮询方法,此外,还可以采用的策略有:JDK的延迟队列、时间轮算法、redis缓存、使用消息队列等等,我选用的策略是采用RabbitMQ的延迟队列来实现,至于延迟队列的实现细节我将在下一篇文章讲解,这里仅介绍订单处理部分。

处理策略为商户下单之后生成订单存入数据库并将该订单号存入延迟队列,此时订单状态为“未支付”,假如接收微信回调成功并且验证到用户已付款,这时候就更新数据库中该订单状态为“已付款”。当延迟队列到期进行消费时,根据延迟队列中的订单号先在数据库中进行查询,假如这时候数据库中该订单状态为“已支付”,这时候就不需要进行处理,假如订单状态为“未支付”,商户程序应主动向微信支付后台进行订单状态查询,如果订单状态为已支付,这时候就不需要进行处理,如果订单状态为未支付,这时候就将订单状态改为“已取消”。一开始我的做法为无论数据库中该订单状态是否已支付都向微信支付后台进行订单状态查询,然后再根据查询结果做进一步处理,显然这种做法存在缺陷,就是每一笔订单都主动向微信支付后台进行查询会消耗很大的网络带宽,而且假如已经成功接收到微信支付回调通知的订单并不需要进行再次查询确认。

三:代码实现

3.1:orderServiceImpl.java

/**
* 提交订单
* @param orders
* @param session
*/
@Override
@Transactional
public 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<ShoppingCart> SCLqw = new LambdaQueryWrapper<>();
    SCLqw.eq(ShoppingCart::getUserId,userId);
    List<ShoppingCart> shoppingCartList = shoppingCartService.list(SCLqw);
    //生成订单号
    long orderId = IdWorker.getId();
    //设置订单号
    orders.setNumber(String.valueOf(orderId));
    //设置订单状态(待付款)
    orders.setStatus(1);
    //设置下单用户id
    orders.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);
    //设置订单详细信息
    List<OrderDetail> orderDetailList = 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)
@Override
public 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<String,String> 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
*/
@Override
public 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 微信支付内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Springboot整合微信支付(订单过期取消及商户主动查单)的更多相关文章

  1. SpringBoot本地磁盘映射问题

    这篇文章主要介绍了SpringBoot本地磁盘映射问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  2. java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源)

    这篇文章主要介绍了java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源),文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下

  3. SpringBoot整合Javamail实现邮件发送的详细过程

    日常开发过程中,我们经常需要使用到邮件发送任务,比方说验证码的发送、日常信息的通知等,下面这篇文章主要给大家介绍了关于SpringBoot整合Javamail实现邮件发送的详细过程,需要的朋友可以参考下

  4. SpringBoot详细讲解视图整合引擎thymeleaf

    这篇文章主要分享了Spring Boot整合使用Thymeleaf,Thymeleaf是新一代的Java模板引擎,类似于Velocity、FreeMarker等传统引擎,关于其更多相关内容,需要的小伙伴可以参考一下

  5. Springboot集成mybatis实现多数据源配置详解流程

    在日常开发中,若遇到多个数据源的需求,怎么办呢?通过springboot集成mybatis实现多数据源配置,简单尝试一下,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  6. SpringBoot使用Minio进行文件存储的实现

    本文主要介绍了SpringBoot使用Minio进行文件存储的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  7. 解析SpringBoot中使用LoadTimeWeaving技术实现AOP功能

    这篇文章主要介绍了SpringBoot中使用LoadTimeWeaving技术实现AOP功能,AOP面向切面编程,通过为目标类织入切面的方式,实现对目标类功能的增强,本文给大家介绍的非常详细,需要的朋友可以参考下

  8. 详解springboot测试类注解

    这篇文章主要介绍了springboot测试类注解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. SpringBoot实现过滤器和拦截器的方法

    大家应该都晓得实现过滤器需要实现 javax.servlet.Filter 接口,而拦截器会在处理指定请求之前和之后进行相关操作,配置拦截器需要两步,本文通过实例代码给大家介绍SpringBoot 过滤器和拦截器的相关知识,感兴趣的朋友一起看看吧

  10. SpringBoot中使用@scheduled定时执行任务的坑

    本文主要介绍了SpringBoot中使用@scheduled定时执行任务的坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部