07、SpringBoot+微信支付 -->处理超时订单(定时查询、核实微信支付平台的订单、调用微信支付平台查单接口、更新本地订单状态、记录支付日志)

目录

  • Native 支付
    • 处理超时订单
      • 定时的讲解
      • 需求分析
      • 代码
        • 定时任务:WxPayTask
        • 定时查询的方法:
        • 核实订单状态等操作 :WxPayServiceImpl
        • 查单接口方法:queryOrder
        • 更新本地订单状态:updateStatusByOrderNo
        • 记录支付日志:createPaymentInfo
        • 关闭订单接口:closeOrder
      • 测试:
        • 创建测试环境:
        • 期望结果:
        • 实际结果:成功
      • 完整代码:
        • WxPayTask
        • getNoPayOrderByDuration 方法
        • checkOrderStatus 方法
        • queryOrder 方法
        • updateStatusByOrderNo 方法
        • PaymentInfoServiceImpl 方法
        • closeOrder 方法

Native 支付

处理超时订单

处理超时订单(定时查询、核实微信支付平台的订单、调用微信支付平台查单接口、更新本地订单状态、记录支付日志)

定时的讲解

【* * * * * *  】 每一秒执行一次

在这里插入图片描述

【0/3 * * * * *  】 从第0秒开始,每隔3秒执行一次

在这里插入图片描述

【1-3 * * * * *  】 从第1秒开始执行,到第3秒结束执行

在这里插入图片描述

【1,2,3 * * * * *  】 在指定的第1,2,3秒执行

在这里插入图片描述
在这里插入图片描述

需求分析

每隔30秒执行一次定时查询方法,先在【本地数据库】查询创建超过5分钟,并且未支付的订单。

然后再根据商品订单号调用【微信支付端】的【查单接口】进行查询,核实订单状态

如果订单在微信支付端那边已支付,则更新商户端(就是本地数据库)订单状态为已支付(本地数据库修改商户端的订单状态)

如果订单在微信支付端那边未支付,则调用微信支付平台的关单接口关闭订单,并更新商户端订单状态(本地数据库修改商户端的订单状态)

作用:把那些超时未支付的订单给关了

注意:调用微信支付端的【商户订单号查询订单】的接口,查询出来的数据是明文的,不要老是想着查出来的都是密文。

而且该接口查询出来的数据,和微信支付平台自动发给商户端的支付通知里面携带的通知参数的 resource 属性里面的 ciphertext 这个密文数据解密出来后的数据是一样的。

在这里插入图片描述

代码

定时任务:WxPayTask

这里的分钟是1,只是为了方便测试

在这里插入图片描述

定时查询的方法:

在这里插入图片描述

核实订单状态等操作 :WxPayServiceImpl

根据订单号查询微信支付查单接口,核实订单状态

queryOrder 查单接口方法,查出来的数据是明文的。

在这里插入图片描述

在这里插入图片描述

查单接口方法:queryOrder

这个是调用微信支付端的【商户订单号查询订单】的接口,查询出来的数据是明文的,不要老是想着查出来的都是密文。

商户订单号查询订单

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

更新本地订单状态:updateStatusByOrderNo

在这里插入图片描述

记录支付日志:createPaymentInfo

因为queryOrder 查单接口方法,查出来的数据是明文的。跟支付通知的 resource 里面的 **ciphertext(密文)**进行解密后的数据是一样的,所以也可以作为参数传给这个方法。

支付通知

在这里插入图片描述

关闭订单接口:closeOrder

关闭订单

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试:

创建测试环境:

先创建一个测试环境:

Java课程:点击了【确认支付】,弹出了支付二维码后就直接关掉了,没有进行支付,所以只添加了一条未支付的订单。

大数据课程:扫码支付了,但是我把ngrok内网穿透关掉了,那么隧道就失效了,微信支付平台发送的支付通知,商户端这边也就接收不到了,所以虽然微信支付平台那边,这个订单已经支付了,但商户端本地这边,因没有接收到支付通知,所以这个订单也是未支付的状态。

内网穿透地址注释掉用于演示商户端接收不到微信支付平台发来的支付通知,从而无法修改订单支付状态的情况。

在这里插入图片描述
在这里插入图片描述

演示环境创建好了,现在启动处理超时订单的方法。

查出创建订单超过1分钟且未支付的订单,然后到微信支付平台调用查询订单的接口,核实这个超时的订单是否真的没支付。

如果在微信支付平台那边已经支付了,那么获取该接口返回的结果里面的支付状态,修改到本地数据库的订单状态里面。

如果没有支付,直接调用微信支付端那边的关闭订单的接口,然后修改本地数据库的那条订单的支付状态为超时未支付。

期望结果:

期望结果应该是:

**Java课程:**是超时1分钟且没有支付的,所以调用定时任务后,本地数据库的该订单的支付状态应该是【超时已关闭】

**大数据库课程:**是超时1分钟,但是已经支付了,只是没收到支付通知,所以调用定时任务后,本地数据库的该订单的支付状态应该【支付成功】,且为该订单生成一条【支付日志记录】

实际结果:成功

实际上:跟预想的一样,成功。

一开始查询未支付且超时的订单:

在这里插入图片描述

Java课程的:
在这里插入图片描述

大数据课程的:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

成功。

完整代码:

WxPayTask
@Slf4j
@Component //组件,项目启动的时候就会加载这个组件类
public class WxPayTask
{
    @Resource
    private OrderInfoService orderInfoService;
    @Resource
    private WxPayService wxPayService;

    //从0秒开始,每隔30秒执行一次这个方法,查询创建超过5分钟,并且未支付的订单
    @Scheduled(cron = "0/30 * * * * *")
    public void orderConfirm() throws Exception
    {
        //从0秒开始,每隔30秒执行一次这个方法,查询创建超过5分钟,并且未支付的订单
        List<OrderInfo> orderInfoList =  orderInfoService.getNoPayOrderByDuration(1);

        for (OrderInfo orderInfo : orderInfoList)
        {
            String orderNo = orderInfo.getOrderNo();
            log.warn("超时订单 ===> {}" , orderNo);

            //核实订单状态:调用微信支付查单接口
            wxPayService.checkOrderStatus(orderNo);
        }
    }
}
getNoPayOrderByDuration 方法
    /**
     * 从0秒开始,每隔30秒执行一次这个方法,查询创建超过5分钟,并且未支付的订单
     * @param minutes 5 分钟
     * @return 未支付的订单集合
     */
    @Override
    public List<OrderInfo> getNoPayOrderByDuration(int minutes)
    {
        // 使用Java的 Instant 类和 Duration 类来计算一个时间点。
        // 获取当前时间,并减去指定的分钟数。
        Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));

        //QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类
        QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
        //构建查询条件
        //条件1:订单的状态为未支付
        queryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());
        //条件2:该订单创建时间超过5分钟--le 是“less than or equal to”的缩写:小于或等于
        queryWrapper.le("create_time",instant);

        List<OrderInfo> orderInfoList = orderInfoMapper.selectList(queryWrapper);

        return orderInfoList;
    }
}
checkOrderStatus 方法
    /**
     * 根据订单号查询微信支付查单接口,核实订单状态
     * 如果订单已支付,则更新商户端订单状态
     * 如果订单未支付,则调用微信支付平台的关单接口关闭订单,并更新商户端订单状态
     * @param orderNo 订单id
     */
    @Override
    public void checkOrderStatus(String orderNo) throws Exception
    {
        log.warn("根据订单号核实订单状态 ===> {}", orderNo);

        //调用微信支付的查单接口---这个方法查出来的是明文
        String result = this.queryOrder(orderNo);

        System.err.println("订单号:"+orderNo+",调用微信支付查单接口:" + result);

        Gson gson = new Gson();
        //将结果转成map类型
        Map resultMap = gson.fromJson(result, HashMap.class);

        //获取微信支付端的订单的状态
        Object tradeState = resultMap.get("trade_state");

        //判断订单状态
        if (WxTradeState.SUCCESS.getType().equals(tradeState))
        {
            log.warn("核定该订单已经支付 ===> {}", orderNo);

            //如果确认该订单已支付,则更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.SUCCESS);

            //记录支付日志
            paymentInfoService.createPaymentInfo(result);
        }

        if (WxTradeState.NOTPAY.getType().equals(tradeState))
        {
            log.warn("核实订单未支付 ===> {}", orderNo);

            //如果订单未支付,则调用关单接口
            this.closeOrder(orderNo);

            //更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CLOSED);

        }
    }

queryOrder 方法
    /**
     * 根据商品订单号查询订单
     * @param orderNo 商品订单号
     * @return 订单
     *
     * 注意:这个查单接口查出来的数据是明文不是密文,不要想成是密文
     * 而且查出来的数据 跟支付通知里面的通知参数的密文ciphertext解密出来的数据是一样的
     *
     */
    @Override
    public String queryOrder(String orderNo) throws Exception
    {
        log.info("查单接口调用 ====> {}", orderNo);

        //组装url---主机地址 + 访问接口地址
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
        //还要拼接上商户号
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());

        //创建远程请求对象
        HttpGet httpGet = new HttpGet(url);

        //get请求只需要设置请求头就可以了--作用:希望接收json类型的响应
        httpGet.setHeader("Accept","application/json");

        // 完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);


        try
        {
            //字符串形式的响应体
            String bodyAsString = EntityUtils.toString(response.getEntity());

            //响应状态码
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200)
            { //处理成功
                System.out.println("成功, 返回结果  = " + bodyAsString);
            } else if (statusCode == 204)
            { //处理成功,无返回Body
                System.out.println("成功");
            } else
            {
                System.out.println("下单失败, 响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
                throw new IOException("请求失败 request failed");
            }

            return bodyAsString;
        } finally
        {
            response.close();
        }
    }

updateStatusByOrderNo 方法
/**
 * 根据订单号更新订单状态
 * @param orderNo 商品订单号
 * @param orderStatus 订单状态
 */
@Override
public void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus)
{
    log.info("修改订单状态为: ===> {}" , orderStatus);

    //QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
    //查询条件---相当于 update t_order_info set xxx = xxx where order_no = orderNo
    queryWrapper.eq("order_no",orderNo);

    //要修改的字段,存到这个 orderInfo 对象里面
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderStatus(orderStatus.getType());

    //调用sql
    orderInfoMapper.update(orderInfo,queryWrapper);

}
PaymentInfoServiceImpl 方法
@Resource
private PaymentInfoMapper paymentInfoMapper;

/**
 * 记录支付日志
 * @param plainText 解密后的参数明文
 */
@Override
public void createPaymentInfo(String plainText)
{
    log.info("记录支付日志");

    Gson gson = new Gson();
    // 将字符串的plainText转成 hashMap 对象
    Map plainTextMap = gson.fromJson(plainText, HashMap.class);

    // 创建一个记录支付日志的对象
    PaymentInfo paymentInfo = new PaymentInfo();

    // 从 plainTextMap 对象中获取要存到记录支付日志(PaymentInfo)的 属性字段
    // 商品订单编号
    String orderNo = (String) plainTextMap.get("out_trade_no");
    // 支付系统交易编号
    String transactionId = (String)plainTextMap.get("transaction_id");
    // 交易类型-- NATIVE:扫码支付
    String tradeType = (String)plainTextMap.get("trade_type");
    // 交易状态--SUCCESS:支付成功
    String tradeState = (String)plainTextMap.get("trade_state");
    // 用户支付金额,单位为分    amount.payer_total
    Map<String, Object> amount = (Map)plainTextMap.get("amount");
    // 官网指定返回的金额是int类型,但是直接把object转成int会报错
    // 弄个中转站:隐式类型转换(小转大)将int类型转换成Double,
    // 然后再用intValue 把double类型的值转成Integer整形
    int payerTotal = ((Double) amount.get("payer_total")).intValue();

    paymentInfo.setOrderNo(orderNo); //商品订单编号
    paymentInfo.setTransactionId(transactionId);//支付系统交易编号
    paymentInfo.setTradeType(tradeType);//交易类型
    paymentInfo.setTradeState(tradeState);//交易状态
    paymentInfo.setPayerTotal(payerTotal);//用户支付金额,单位为分
    paymentInfo.setPaymentType(PayType.WXPAY.getType()); //支付类型
    // 通知参数 -- 因为可能会有各种各样的参数,所以直接把整个参数存到一个字段里面,
    // 方便后续遇到问题可以查看
    paymentInfo.setContent(plainText);

    //插入一个订单支付日志记录
    paymentInfoMapper.insert(paymentInfo);
}
closeOrder 方法
/**
 * 关单接口
 * @param orderNo 订单编号
 */
private void closeOrder(String orderNo) throws IOException
{
    log.info("关单接口的调用,订单编号 ===> {}", orderNo);

    // 构建url地址:
    // 关单接口的url地址--/v3/pay/transactions/out-trade-no/{out_trade_no}/close,
    // {out_trade_no}这个是个占位符,用format方法,把orderNo传进去
    String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);

    //微信端的主机地址:  【主域名】https://api.mch.weixin.qq.com
    url = wxPayConfig.getDomain().concat(url);

    // 创建远程请求对象
    HttpPost httpPost = new HttpPost(url);

    // 组装json请求体
    Gson gson = new Gson();
    HashMap<String, String> paramsMap = new HashMap<>();
    paramsMap.put("mchid", wxPayConfig.getMchId()); // 直连商户号
    String jsonParams = gson.toJson(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 = wxPayClient.execute(httpPost);
    try
    {
        // 响应状态码
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200)
        { // 处理成功
            log.info("成功");
        } else if (statusCode == 204)
        { // 处理成功,无返回Body
            log.info("成功");
        } else
        {
            log.info("关闭订单API失败, 响应码 = " + statusCode + "");
            throw new IOException("请求失败 request failed");
        }
    } finally
    {
        response.close();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/125346.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TCP网络编程

一)TCP Socket介绍: 1)TCP和UDP有着很大的不同&#xff0c;TCP想要进行网络通信的话首先需要通信双方建立连接以后然后才可以进行通信&#xff0c;TCP进行网络编程的方式和文件中的读写字节流类似&#xff0c;是以字节为单位的流进行传输 2)针对于TCP的套接字来说&#xff0c;J…

ubuntu 火焰图脚本

环境ubuntu1804 x86_64 #!/bin/bash if [ "$2_" "_" ];thenecho "usage ./fire.sh oncpu/offcpu pid"exit fiif [ "$1_" "oncpu_" ];thensudo perf record -F 99 -p $2 -g -- sleep 10syncsudo perf script > out.pe…

144. 二叉树的前序遍历

描述 : 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 题目 : LeetCode 二叉树的前序遍历 : 144. 二叉树的前序遍历 分析 : 我们先选一个最小的子树: 先判断5节点不是null之后把5添加到集合里 , 再把5的左节点递归 , 判断7节点不是null把7添加到集合中 …

Git简介和安装

一&#xff0c;Git简介 Git 是一个分布式版本控制工具&#xff0c;通常用来对软件开发过程中的源代码文件进行管理。通过Git 仓库来存储和管理这些文件&#xff0c;Git 仓库分为两种&#xff1a; 本地仓库&#xff1a;开发人员自己电脑上的 Git 仓库 远程仓库&#xff1a;远程…

云MES优势有哪些?

云MES可以说是近几年来的行业焦点&#xff0c;云MES使用公共云提供商(如华为云、腾讯云、阿里云等)的全球数据中心中的广泛硬件和基础架构&#xff0c;在公共云中的Internet上运行&#xff0c;线下线上互联互通&#xff0c;有效帮助企业实现生产数字化智能化。 近几年来云服务…

Linux 多线程编程详解

目录 为什么要使用多线程 线程概念 线程的标识 pthread_t 线程的创建 向线程传入参数 线程的退出与回收 线程主动退出 线程被动退出 线程资源回收(阻塞方式) 线程资源回收(非阻塞方式) 为什么要使用多线程 在编写代码时&#xff0c;是否会遇到以下的场景会感觉到难以…

显著提升!| (WOA)融合模拟退火和自适应变异的混沌鲸鱼优化算法应用于函数寻优

鲸鱼优化算法(whale optimization algorithm,WOA)是由Mirjalili和Lewis[1]于2016年提出的一种新型群体智能优化搜索方法,它源于对自然界中座头鲸群体狩猎行为的模拟&#xff0c;与其它群体智能优化算法相比&#xff0c;WOA算法结构新颖, 控制参数少&#xff0c;在许多数值优化和…

在IDEA中配置Web开发环境

一、idea配置Web开发环境 第一步&#xff1a;下载并安装Tomcat服务器&#xff08;建议放根目录&#xff0c;完整路径中不要出现中文&#xff09; 第二步&#xff1a;打开IDEA&#xff0c;新建java项目 第三步&#xff1a;为项目添加Web应用 在项目上右键➡️选择“Add Framew…

在线存储系统源码 网盘网站源码 云盘系统源码

Cloudreve云盘系统源码-支持本地储存和对象储存,界面美观 云盘系统安装教程 测试环境:PHP7.1 MYSQL5.6 Apache 上传源码到根目录 安装程序: 浏览器数据 http://localhost/CloudreveInstallerlocalhost更换成你的网址 安装完毕 记住系统默认的账号密码 温馨提示:如果默认…

已解决:云原生领域的超时挂载Bug — Kubernetes深度剖析

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

线性代数(三) | 向量组的秩 线性相关无关 几何直观理解 题解应用

文章目录 1 维数&#xff1f;向量组的秩究竟是什么&#xff1f;1.1 线是一维的1.2 面是二维的1.3 体是三维的 2 线性相关、线性无关、线性表示究竟是什么&#xff1f;2.1 基于以上几何直观的解题角度2.2 基于方程组的解题角度 1 维数&#xff1f;向量组的秩究竟是什么&#xff…

10道高频Vuex面试题快问快答

※其他的快问快答&#xff0c;看这里&#xff01; 10道高频Qiankun微前端面试题快问快答 10道高频webpack面试题快问快答 20道高频CSS面试题快问快答 20道高频JavaScript面试题快问快答 30道高频Vue面试题快问快答 面试中的快问快答 快问快答的情景在面试中非常常见。 在面试过…

[100天算法】-面试题 17.11.单词距离(day 68)

题目描述 有个内含单词的超大文本文件&#xff0c;给定任意两个单词&#xff0c;找出在这个文件中这两个单词的最短距离(相隔单词数)。如果寻找过程在这个文件中会重复多次&#xff0c;而每次寻找的单词不同&#xff0c;你能对此优化吗?示例&#xff1a;输入&#xff1a;word…

天津WEB前端培训哪家好?Web机构推荐!

05年以后&#xff0c;互联网已经进入了web2.0时代&#xff0c;同时也标志着网站的前端由此发生了翻天覆地的变化&#xff0c;现在市场上对WEB前端开发工程师岗位有着很大的需求&#xff0c;学习web前端开发的方式有很多种&#xff0c;对于初学者来说&#xff0c;选择自学还是培…

大数据毕业设计选题推荐-河长制大数据监测平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

2023.11-9 hive数据仓库,概念,架构

目录 一.HDFS、HBase、Hive的区别 二.大数据相关软件 三. Hive 的优缺点 1&#xff09;优点 2&#xff09;缺点 四. Hive 和数据库比较 1&#xff09;查询语言 2&#xff09;数据更新 3&#xff09;执行延迟 4&#xff09;数据规模 五.hive架构流程 六.MetaStore元…

AI:73-结合语法知识的神经机器翻译研究

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

企业微信开发教程一:添加企微应用流程图解以及常见问题图文说明

最近在前辈的基础上新添加了一个企微应用&#xff0c;过程中遇到了一些卡点&#xff0c;这里一一通过图片标注与注释的方式记录一下&#xff0c;希望能给后来人提供一些清晰明了的帮助&#xff0c;话不多说&#xff0c;大家直接看图吧。 &#xff08;文中包括一些本项目独有的配…

[Matlab]基于LSTM+NSGA2的风光火力发电策略优化

最近比较忙&#xff0c;好久没分享案例啦&#xff0c;今天简单分享一个滚动时域的多目标优化 一 模型介绍 1 风电 2 光伏 3 火电 4 储能 5 用电需求 等五个对象。 其中风电和光伏还有用电需求&#xff0c;用历史数据LSTM网络&#xff0c;训练一个预测模型&#xff1b;火电根据策…

使用sizeof()和strlen()去计算【数组】和【指针】的大小

文章目录 一、知识回顾1、回顾sizeof()、strlen的作用&#xff1a;2、数组和指针3、数组名 二、sizeof()、strlen()的使用区别1、注意区别&#xff1a;2、一维数组与一级指针3、二维数组与二级指针 三、总结回顾 一、知识回顾 1、回顾sizeof()、strlen的作用&#xff1a; siz…
最新文章