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
{@Resourceprivate OrderInfoService orderInfoService;@Resourceprivate 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 未支付的订单集合*/@Overridepublic 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*/@Overridepublic 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解密出来的数据是一样的**/@Overridepublic 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){ //处理成功,无返回BodySystem.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 = orderNoqueryWrapper.eq("order_no",orderNo);//要修改的字段,存到这个 orderInfo 对象里面OrderInfo orderInfo = new OrderInfo();orderInfo.setOrderStatus(orderStatus.getType());//调用sqlorderInfoMapper.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_totalMap<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.comurl = 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){ // 处理成功,无返回Bodylog.info("成功");} else{log.info("关闭订单API失败, 响应码 = " + statusCode + "");throw new IOException("请求失败 request failed");}} finally{response.close();}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

苍穹外卖-day06

苍穹外卖-day06 课程内容 HttpClient微信小程序开发微信登录导入商品浏览功能代码 功能实现&#xff1a;微信登录、商品浏览 微信登录效果图&#xff1a; 商品浏览效果图&#xff1a; 1. HttpClient 1.1 介绍 HttpClient 是Apache Jakarta Common 下的子项目&#xff0c;…

单例模式 rust和java的实现

文章目录 单例模式介绍应用实例&#xff1a;优点使用场景 架构图JAVA 实现单例模式的几种实现方式 rust实现 rust代码仓库 单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建…

rabbitMQ rascal/amqplib报错 Error: Unexpected close 排查

以下是一些可能导致此 RabbitMQ 客户端或任何其他 RabbitMQ 客户端中的套接字读取或写入失败的常见场景 1.错过&#xff08;客户端&#xff09;心跳 第一个常见原因是RabbitMQ 检测到心跳丢失。发生这种情况时&#xff0c;RabbitMQ 将添加一个有关它的日志条目&#xff0c;然…

SQL note1:Basic Queries + Joins Subqueries

目录 一、Basic Queries 1、数据库术语 2、查表 3、过滤掉我们不感兴趣的行 4、布尔运算 5、过滤空值&#xff08;NULL&#xff09; 6、分组和聚合 1&#xff09;汇总数据的列 2&#xff09;汇总数据组 7、分组聚合的警告 1&#xff09;SELECT age, AVG(num_dogs) FR…

基于ssm的大学生社团管理系统

基于ssm的大学生社团管理系统 摘要 基于SSM的大学生社团管理系统是一个全面、高效的社团管理平台&#xff0c;旨在帮助大学生和社团管理员更方便、更快捷地进行社团活动的组织和管理。该系统基于Spring、SpringMVC和MyBatis&#xff08;简称SSM&#xff09;开发&#xff0c;这三…

Ubuntu中安装rabbitMQ

一、安装 RabbitMQ ①&#xff1a;更新源 sudo apt-get update②&#xff1a;安装Rrlang语言 由于RabbitMq需要erlang语言的支持&#xff0c;在安装RabbitMq之前需要安装erlang sudo apt-get install erlang-nox③&#xff1a;安装rabbitMQ sudo apt-get install rabbitmq-s…

【算法与数据结构】216、LeetCode组合总和 III

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题可以直接利用77题的代码【算法与数据结构】77、LeetCode组合&#xff0c;稍作修改即可使用。   …

APISpace IP归属地查询接口案例代码

1.IP归属地查询API 1.1 API接口简介 IP归属地查询API&#xff1a;根据IP地址查询归属地信息&#xff0c;包含国家、省、市、区县和运营商等信息。APISpace 提供了IPv4 和 IPv6 的IP归属地查询接口&#xff0c;并且包含了各种归属地精度查询的接口。 1.2 IPv4 IPv4归属地查询…

亚马逊云科技海外服务器初体验

目录 前言亚马逊云科技海外服务器概述注册使用流程实例创建性能表现用户体验服务支持初体验总结 前言 随着云原生技术的飞速发展&#xff0c;越来越多的企业和开发者选择云服务器来作为自己的使用工具&#xff0c;云原生技术的发展也促进了云服务厂商的产品发展&#xff0c;所…

树莓派4B的测试记录(CPU、FFMPEG)

本文是用来记录树莓派 4B 的一些测试记录。 温度 下面记录中的风扇和大风扇是这样的&#xff1a; 为什么要用大风扇呢&#xff1f;因为小风扇在外壳上&#xff0c;气流通过外壳的珊格会有啸叫&#xff0c;声音不大但是很烦人&#xff0c;大风扇没这个问题&#xff0c;并且同样…

python编程复习系列——week1(Input Output)

Input & Output 前言0、我们的第一个Python程序一、变量和数据类型1.变量是用来存储值的保留存储位置2.变量以特定的数据类型存储值。常见数据类型&#xff1a;3.字符串添加&#xff08;连接&#xff09;4.字符串乘法&#xff08;带数字&#xff09;&#xff01;5.从用户处…

vue3 - swiper插件 实现PC端的 视频滑动功能(仿抖音短视频)

swiper官网 ​​​​​​swiper属性/组件查询 vue中使用swiper 步骤&#xff1a; ① npm install swiper 安装 ② 基础模板&#xff1a; <div><swiper class"swiper-box" :direction"vertical":grabCursor"true" :mousewheel"tr…

【面试经典150 | 】颠倒二进制位

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;逐位颠倒方法二&#xff1a;分治 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于…

线程基础知识

目录 进程 线程 CPU 核心数和线程数的关系 上下文切换(Context switch) Thread 和 Runnable 的区别 Callable、Future 和 FutureTask 面试题:新启线程有几种方式? 中止 中断 深入理解 run()和 start() 进程 我们常听说的是应用程序&#xff0c;也就是 app&#xff…

使命担当 守护安全 | 中睿天下获全国海关信息中心感谢信

近日&#xff0c;全国海关信息中心向中睿天下发来感谢信&#xff0c;对中睿天下在2023年网络攻防演练专项活动中的大力支持和优异表现给予了高度赞扬。 中睿天下对此次任务高度重视&#xff0c;紧密围绕全国海关信息中心的行动要求&#xff0c;发挥自身优势有效整合资源&#x…

Vue3中使用Pinia

前言&#xff1a; 在 Vue 3 中&#xff0c;Pinia 是一个用于管理全局状态的库。它可以让我们更容易地维护和共享应用的状态。下面是如何在 Vue 3 中使用 Pinia 的步骤。 正文&#xff1a; 首先&#xff0c;我们需要安装 Pinia。可以使用 npm 或者 yarn 来安装。例如&#xff0…

【Unity ShaderGraph】| 如何快速制作一个炫酷的 全息投影效果

前言 【Unity ShaderGraph】| 如何快速制作一个炫酷的 全息投影效果一、效果展示二、 全息投影效果 前言 本文将使用ShaderGraph制作一个 炫酷的 全息投影效果 &#xff0c;可以直接拿到项目中使用。对ShaderGraph还不了解的小伙伴可以参考这篇文章&#xff1a;【Unity Shader…

Docker学习——④

文章目录 1、Docker Image&#xff08;镜像&#xff09;2、镜像命令详解2.1 docker rmi2.2 docker save2.3 docker load2.4 docker image inspect2.5 docker history2.6 docker image prune 3、镜像综合实战3.1 离线镜像迁移3.2 镜像存储的压缩与共享 1、Docker Image&#xff…

创建多层级行索引,创建多层级行索引的DataFrameMultiIndex.from_product()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 创建多层级行索引, 创建多层级行索引的DataFrame MultiIndex.from_product() [太阳]选择题 使用pd.MultiIndex.from_product()&#xff0c;下列输出正确的是&#xff1a; import pandas as pd…

C++打怪升级(十)- STL之vector

~~~~ 前言1. vector 是什么2. 见见vector的常用接口函数吧构造函数无参构造函数使用n个val构造拷贝构造使用迭代器范围构造初始化形参列表构造 析构函数赋值运算符重载函数元素访问[]运算符重载函数访问at函数访问front函数back函数 迭代器相关正向迭代器反向迭代器 容量相关si…