Springboot整合微信支付(订单过期取消及商户主动查单)

目录
  • 一:问题引入
  • 二:处理流程
  • 三:代码实现

一:问题引入

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

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

二:处理流程

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

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

假如我设定的定时时间是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 微信支付内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • UniApp + SpringBoot 实现微信支付和退款功能

    目录 开发准备 微信支付开发 后端部分 前端部分 开发准备 一台用于支付的测试机,必须得是一个安卓机因为需要打支付基座才能使用. 用于编写的后端框架接口的 IDE (IDEA 或者 Eclipse 都可以) HBuilder X 用来编辑 UniApp 项目的编辑器和编译器 基本的 SpringBoot 的脚手架,可以去 https://start.spring.io/或者 IDEA 自带的快速生成脚手架插件. Jdk 11 微信支付开发 我这里省略了申请等步骤.如果没有申请过企业支付的可以去官

  • springboot对接微信支付的完整流程(附前后端代码)

    展示图: 对接的完整流程如下 首先是配置 gzh.appid=公众号appid wxPay.mchId=商户号 wxPay.key=支付密钥 wxPay.notifyUrl=域名回调地址 常量: /**微信支付统一下单接口*/ public static final String unifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; public static String SUCCESSxml = &q

  • 一篇文章带你入门Springboot整合微信登录与微信支付(附源码)

    0. 前期准备 在使用微信支付前,默认小伙伴已经具备以下技能: 熟练使用springboot(SSM) + Mybatis(plus)/JPA + HttpClient + mysql5.x 了解JWT 权限校验 阅读过微信开放平台微信支付与微信登录相关文档,可以简单看懂时序图 有微信开放平台开发者资质认证账户,具备开通微信支付(如果不具备的小伙伴可以找身边有的人借一下) 1. 微信扫码登录 1.1 微信授权一键登录功能介绍 简介:登录方式优缺点和微信授权一键登录功能介绍 # 1.手机号或者邮箱

  • SpringBoot实现整合微信支付方法详解

    目录 1.准备工作 1.1 数据库表 1.2 实体类 1.3 导入依赖 1.4 配置文件 1.5 创建读取微信支付相关信息的工具类 1.6 其他工具类 2.生成订单 2.1 远程调用用户模块和课程模块 2.2 远程调用方法的实现 2.3 根据课程id和用户id生成订单 3.查询订单信息 3.1 controller层 3.2 service层 4.生成微信支付的二维码 4.1 controller层 4.2 service层 5.查询订单支付状态 5.1 controller层 5.2 serv

  • springboot整合微信支付sdk过程解析

    前言 之前做的几个微信小程序项目,大部分客户都有要在微信小程序前端提现的需求.提现功能的实现,自然使用企业付款接口,不过这个功能开通比较麻烦,要满足3个条件; 之前实现过几个微信支付的接口,不过都是自己码的代码,从网上找找拼凑,觉得看起来不舒服~_~ 于是乎找到了微信官方提供的支付sdk.这里用的是java版本,springboot整合java 下载sdk,引入项目 这里可以直接下载官方提供的sdk,然后将几个java类拷贝到你的项目,也可以直接引入maven依赖,这里是直接将Java类拷贝到我

  • Springboot整合微信支付(订单过期取消及商户主动查单)

    目录 一:问题引入 二:处理流程 三:代码实现 一:问题引入 前面讲到用户支付完成之后微信支付服务器会发送回调通知给商户,商户要能够正常处理这个回调通知并返回正确的状态码给微信支付后台服务器,不然微信支付后台服务器就会在一段时间之内重复发送回调通知给商户.具体流程见下图: 那么这时候问题就来了,微信后台发送回调通知次数也是有限制的,而且,微信支付开发文档中这样说到:对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成

  • thinkphp整合微信支付代码分享

    本文实例为大家分享了thinkphp整合微信支付代码,供大家参考,具体内容如下 下载:支付SDK 将微信支付SDK放在第三方类库Vendor下面,请切记把WxPay.Config.php里面的商户信息修改为您的公众号信息,以避免造成资金的流失. php端代码 public function pay(){ //商户基本信息,可以写死在WxPay.Config.php里面,其他详细参考WxPayConfig.php vendor('Pay.JSAPI'); $tools = new \JsApiPa

  • Thinkphp整合微信支付功能

    先上效果图:我要告诉你我这一篇文章写的是微信支付之中的(普通商户而非服务商商户的统一下单JSPI)微信支付: 其实自己整合SDK失败了,用了一个博客博主整合的代码,在这里写一下笔记: 前面准备: 1.微信公众号: 独特的appid.appscrect.接口权限之中设置可以获取用户ID信息权限的域名(每个用户对于不同公众都会有一个特有ID,通过这个ID获取用户微信账号基本信息.详情看微信开发者文档).在微信支付按钮出设置微信支付授权目录(写到发起请求的控制器那一层).设置开发者微信账号为测试白名单

  • Springboot整合支付宝支付功能

    1.需要的Maven依赖 // 支付宝 <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.8.73.ALL</version> </dependency> 2.controller层 /** * 统一下单接口 * @param orderNo 订单号 * @para

  • ThinkPHP框架整合微信支付之JSAPI模式图文详解

    本文实例讲述了ThinkPHP框架整合微信支付之JSAPI模式.分享给大家供大家参考,具体如下: 目前微信是很火的,微信支付目前很少在网上能看到一系列详细的demo,因此,花一点时间来做一下关于微信支付系列教程, 本次教程是JSAPI模式支付,其他的还会继续写下去 首先,下载微信支付的demo,我们根据微信上的demo来整合到TP中. 介绍下我们这里需要用到的几个文件: 在demo文件夹中: js_api_call.php:提供了微信jsapi的主要功能 log_.php:提供打印日志功能 no

  • ThinkPHP框架整合微信支付之刷卡模式图文详解

    本文实例讲述了ThinkPHP框架整合微信支付之刷卡模式.分享给大家供大家参考,具体如下: 大家好,这篇文章是继微信支付之Native 扫码支付 模式二之后的微信支付系列教程第四篇:微信刷卡支付 本教程跟前三篇教程不一样,所需要的类库也不一样,所以做刷卡支付的时候,与之前的方法没多少关系,大家注意一下. 下面开始介绍详细步骤! step1:下载微信刷卡支付demo,如下图: WxPayMicropayHelper:这个文件夹下就是刷卡支付微信提供的类库 demo:这个文件夹下是关于刷卡支付的详细

随机推荐