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

news2024/11/26 11:28:20

目录

  • 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.coloradmin.cn/o/1190787.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

tqdm学习

from tqdm import tqdmepochs 10 epoch_bar tqdm(range(epochs)) count 0 for _ in epoch_bar:count count1print("count {}".format(count))print(_)每次就是一个epoch

CSDN每日一题学习训练——Java版(克隆图、最接近的三数之和、求公式的值)

版本说明 当前版本号[20231109]。 版本修改说明20231109初版 目录 文章目录 版本说明目录克隆图题目解题思路代码思路参考代码 最接近的三数之和题目解题思路代码思路参考代码 求公式的值题目解题思路代码思路参考代码 克隆图 题目 给你无向 连通(https://baike.baidu.com…

电商项目之Java8函数式接口落地实践

文章目录 1 问题背景2 前言3 多处重复的重试机制代码4 优化后的代码5 进一步优化 1 问题背景 在电商场景中&#xff0c;会调用很多第三方的云服务&#xff0c;比如发送邮件、发起支付、发送验证码等等。由于网络存在抖动&#xff0c;有时候发起调用后会拿到500的状态码&#xf…

Visual Studio2022安装教程【图文详解】(大一小白)编译软件

工欲善其事&#xff0c;必先利其器。想要学好编程&#xff0c;首先要把手中的工具利用好&#xff0c;今天小编教一下大家如何下载安装并使用史上最强大的编译器--Visual Studio&#x1f357; 一.Visual Studio下载及安装 https://visualstudio.microsoft.com/ 打开文件 点击.ex…

Halcon的相机内参外参的标定

halcon标定相机内参只能使用方向标定板和圆点标定板。并且方向标定板可也可用性极高。 1.打开halcon的标定助手&#xff0c;选择标定板的描述文件&#xff0c;填写标定板的厚度&#xff0c;根据相机选择像元的尺寸和镜头的焦距。如果已有相机内参&#xff0c;只标定外参&#…

使用ESP8266构建家庭自动化系统

随着物联网技术的不断发展&#xff0c;家庭自动化系统变得越来越受欢迎。ESP8266是一款非常适合于构建家庭自动化系统的WiFi模块。它小巧、低成本&#xff0c;能够实现与各种传感器和执行器的连接&#xff0c;为家庭带来智能化、便利化的体验。在本篇文章中&#xff0c;我们将向…

kubernetes集群编排(9)

目录 helm 部署helm 封装chart包 上传chart到OCI仓库 部署wordpress博客系统 helm部署storageclass helm部署ingress-nginx helm部署metrics-server kubeapps 更新 helm 部署helm 官网&#xff1a; Helm | 快速入门指南 https://github.com/helm/helm/releases [rootk8s2 ~]# t…

2022年06月 Python(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 有如下Python程序,包含lambda函数,运行该程序后,输出的结果是?( ) g = lambda x,y:x*y print(g(2,3)

理解RNN以及模型搭建代码

RNN结构 这是一张不直观易懂的RNN结构示意图。但也是大家见得最多结构示意图。 RNN模型解释 RNN一文就讲解清楚的博客&#xff0c;看这里&#xff1a;https://zhuanlan.zhihu.com/p/408998328 RNN为什么梯度消失和梯度爆炸&#xff0c;看这里&#xff1a;https://zhuanlan.z…

隧道技术的三种应用场景(IPv6,多播,VPN)

目录 1.IPv6的隧道技术 2.多播路由选择 (1)洪泛 (2)隧道技术 (3)基于核心的发现技术 3.隧道技术实现&#xff08;VPN&#xff09;虚拟专用网 1.IPv6的隧道技术 IPv6与IPv4的过渡技术中包含了IPv6的隧道技术&#xff1a; http://t.csdnimg.cn/wuvXY 2.多播路由选择 转发…

Redis数据类型之List类型

文章目录 简介常用命令赋值语法LPUSHRPUSHLPUSHXRPUSHX 取值语法LLENLINDEXLRANGE 删除语法LPOPRPOPBLPOPBRPOPLTRIMLREM 修改语法LSETLINSERT 高级命令RPOPLPUSH 应用场景 简介 List类型是一个链表结构的集合&#xff0c;其主要功能有push&#xff0c;pop&#xff0c;获取元素…

2023中国互联网公司排行榜!

日前&#xff0c;中国互联网协会正式发布《中国互联网企业综合实力指数&#xff08;2023&#xff09;》报告&#xff0c;同时还发布了2023年中国互联网综合实力前百家企业榜单、2023年中国互联网成长型前二十家企业和数据安全服务前五家企业名单。 总体来看&#xff0c;我国互…

C/C++特殊求和 2021年6月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C幻数求和 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C幻数求和 2021年6月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 如果一个数能够被7整除或者十进制表示中含有数字7&…

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

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

尼得科电机的强大性能,将列车门和屏蔽门的开合变得从容而安全

城市脉动&#xff0c;人流涌动。 无论城市轨道交通还是远途铁路运输&#xff0c; 尼得科电机的强大性能&#xff0c;将列车门和屏蔽门的开合变得从容而安全。 尼得科的电机方案&#xff0c;有助于列车门稳准开闭&#xff0c;保障乘客安全无忧。高效驱动&#xff0c;让乘客的行程…

VUE获取当前日期的周日和周六

<template><div><div click"handleLast()">上一周</div><div click"handleNext()">下一周</div><el-calendarref"monChild"v-model"value":first-day-of-week"7":range"[sta…

iis 部署 netcore 和vue 共用端口

常规情况下&#xff0c;vue 和api是分开的两个站点进行部署&#xff0c;若是要对外只有一个端口的话&#xff0c;采用以下梁总方式即可。 1.需要配置路由转发和代理开启&#xff08;vue 使用hisoty模式&#xff09; 参考链接.netCore vue&#xff08;history模式&#xff0…

线性代数(六)| 二次型 标准型转换 正定二次型 正定矩阵

文章目录 1. 二次型化为标准型1.1 正交变换法1.2 配方法 2 . 正定二次型与正定矩阵 1. 二次型化为标准型 和第五章有什么样的联系 首先上一章我们说过对于对称矩阵&#xff0c;一定存在一个正交矩阵Q&#xff0c;使得$Q^{-1}AQB $ B为对角矩阵 那么这一章中&#xff0c;我们…

IBM Qiskit量子机器学习速成(一)

声明&#xff1a;本篇笔记基于IBM Qiskit量子机器学习教程的第一节&#xff0c;中文版译文详见&#xff1a;https://blog.csdn.net/qq_33943772/article/details/129860346?spm1001.2014.3001.5501 概述 首先导入关键的包 from qiskit import QuantumCircuit from qiskit.u…

暴力递归转动态规划(十四)

题目 arr是面值数组&#xff0c;其中的值都是正数且没有重复。再给定一个正数aim。 每个值都认为是一种面值&#xff0c;且认为张数是无限的。 返回组成aim的最少货币数 暴力递归 依然是面值张数的问题&#xff0c;暴力递归尝试的过程是从数组arr index 0位置出发&#xff0c…