03、SpringBoot + 微信支付 ---- 创建订单、保存二维码url、显示订单列表

目录

  • Native 下单
    • 1、创建课程订单保存到数据库
      • 1-1:需求:
      • 1-2:代码:
      • 1-3:测试结果:
    • 2、保存支付二维码的url
      • 2-1:需求:
      • 2-2:代码:
      • 2-3:测试:
      • 2-4:完整代码:
        • 后端:
          • WxPayController
          • WxPayService
          • WxPayServiceImpl
          • OrderInfoService
          • OrderInfoServiceImpl
    • 3、显示订单列表
      • 3-1:需求:
      • 3-2:代码:
        • 前端:
        • 后端:
      • 3-3:测试:
        • 查看swagger
        • 查看订单列表
      • 3-4:完整代码
        • 后端:
          • OrderInfoController
          • OrderInfoService
          • OrderInfoServiceImpl

Native 下单

1、创建课程订单保存到数据库

1-1:需求:

之前的下单,只是获取支付二维码,但是并没有将订单存到数据库

需求1:点击确认支付后,创建商品的订单存到数据库

需求2:每次确认支付之前,要判断这个人是否存在已下单未支付的订单,有的话就不用再创建订单了,把他的订单查询出来给他就行。

在这里插入图片描述

1-2:代码:

在这里插入图片描述

需求1:点击确认支付后,创建商品的订单存到数据库

在这里插入图片描述

需求2:每次确认支付之前,要判断这个人是否存在已下单未支付的订单,有的话就不用再创建订单了,把他的订单查询出来给他就行。

在这里插入图片描述

1-3:测试结果:

成功在数据库添加订单,并且多次点击确认下单,并不会重复添加订单到数据库

在这里插入图片描述

2、保存支付二维码的url

2-1:需求:

上面创建订单的时候,是没有存二维码的url到数据库的,这里需要在创建订单的时候,把url存进去。
Native调起支付
在这里插入图片描述

2-2:代码:

解释:

因为获取支付二维码url的代码在创建订单之后,所以第一次创建订单是没有支付二维码的url的。

所以在往下的代码中,添加了保存二维码的代码。
在这里插入图片描述

在这里插入图片描述

2-3:测试:

保存二维码成功,并且在重复访问的时候,因为存在二维码,所以不会再去调用微信的下单接口。

因为二维码有效期为2小时,所以后面还需要优化,如果二维码过期,需要再次更新数据库中的二维码。

在这里插入图片描述

2-4:完整代码:

包含创建订单和保存支付二维码的代码

后端:
WxPayController
@CrossOrigin //跨域
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API") //swagger 注解
@Slf4j
public class WxPayController
{
    @Resource
    private WxPayService wxPayService;

    //调用统一下单API,生成支付二维码的链接和订单号
    //swagger注解
    @ApiOperation("调用统一下单API,生成支付二维码")
    @PostMapping("/native/{productId}")
    public R nativePay(@PathVariable Long productId) throws Exception
    {
        log.info("发起支付请求");
        //返回支付二维码的链接和订单号
        Map<String,Object> map = wxPayService.nativePay(productId);
        return R.ok().setData(map);
    }
}
WxPayService
public interface WxPayService
{
    //调用统一下单API,生成支付二维码的链接和订单号
    Map<String, Object> nativePay(Long productId) throws  Exception;
}
WxPayServiceImpl

//创建订单,调用 Native 支付接口
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService
{
    @Resource
    private WxPayConfig wxPayConfig;
    /* 原本应该注 入WxPayConfig 这个类,然后调用 getWxPayClient() 方法获取 HttpClient请求对象
     * 但是因为 getWxPayClient() 方法加了@Bean注解,交给了spring容器管理,所以项目启动的时候就会执行这个方法,
     * 就会存在返回值为 CloseableHttpClient 类型的 HttpClient请求对象
     * 所以这里可以直接注入这个 CloseableHttpClient 对象
     */
    @Resource
    private CloseableHttpClient wxPayClient;
    @Resource
    private OrderInfoService orderInfoService;

    /**
     * 创建订单,调用 Native 支付接口
     *
     * @param productId 商品id
     * @return code_url 和 订单号
     * @throws Exception
     */
    //调用统一下单API,生成支付二维码的链接和订单号
    @Override
    public Map<String, Object> nativePay(Long productId) throws Exception
    {
        //生成订单
        OrderInfo orderInfo = orderInfoService.createOrderInfoByProduct(productId);

        //获取二维码url-----如果是第一次生成订单,那么这个订单是没有二维码url的
        String codeUrl = orderInfo.getCodeUrl();
        //判断--如果订单存在,并且二维码的url也存在,那么就不需要再去调用微信的下单接口来获取支付二维码了
        if (orderInfo != null && !StringUtils.isEmpty(codeUrl))
        {
            //创建一个包含url和订单号的返回值
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        }



        /*
         * 官方提供的 Native下单 接口
         * 支持商户:【普通商户】
         * 请求方式:【POST】/v3/pay/transactions/native
         * 请求域名:【主域名】https://api.mch.weixin.qq.com
         * "https://api.mch.weixin.qq.com/v3/pay/transactions/native" 改成
         * wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType())
         */

        log.info("调用统一下单API.....");

        //调用统一下单API---拷贝官网的实例代码进行修改---统一下单的接口地址
        //封装统一下单API 的url
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));

        // 请求body参数---------调用接口需要的参数
        Gson gson = new Gson();
        //数据类型不固定,所以就不写泛型了
        Map paramsMap = new HashMap();
        //设置参数 --- 根据官网要求设置对应的参数
        paramsMap.put("appid", wxPayConfig.getAppid()); //公众号ID
        paramsMap.put("mchid", wxPayConfig.getMchId()); //直连商户号
        paramsMap.put("description", orderInfo.getTitle()); // 商品描述
        paramsMap.put("out_trade_no", orderInfo.getOrderNo()); //商户订单号
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType())); //通知地址
        //订单金额有两个参数--嵌套数据
        Map amountMap = new HashMap();
        amountMap.put("total", orderInfo.getTotalFee()); //总金额
        amountMap.put("currency", "CNY"); //货币类型
        paramsMap.put("amount", amountMap); // 订单金额

        //将参数转成字符串
        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
        {
            //字符串形式的响应体
            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");
            }
            //响应结果---如果下单成功,获取响应结果,   gson.fromJson()用于将 JSON 字符串转换为 Java 对象
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            //二维码---从返回的结果中获取二维码的url, 从官网看出 code_url 是 二维码的key
            codeUrl = resultMap.get("code_url");

            //保存二维码
            String orderNo = orderInfo.getOrderNo();
            orderInfoService.saveCodeUrl(orderNo,codeUrl);

            //创建一个包含url和订单号的返回值
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        } finally
        {
            response.close();
        }
    }
}
OrderInfoService

public interface OrderInfoService extends IService<OrderInfo> {


    /**
     * 根据商品id创建商品订单
     * @param productId 商品id
     * @return 订单对象
     */
    OrderInfo createOrderInfoByProduct(Long productId);


    /**
     * 将支付二维码的url存到订单中
     * @param orderNo 订单编号
     * @param codeUrl 支付二维码的地址url
     */
    void saveCodeUrl(String orderNo, String codeUrl);


}
OrderInfoServiceImpl

@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService
{
    @Resource
    private ProductMapper productMapper;
    @Resource
    private OrderInfoMapper orderInfoMapper;

    //创建商品订单
    @Override
    public OrderInfo createOrderInfoByProduct(Long productId)
    {
        //用户点击确认支付,要先查找该用户是否存在已下单未支付的订单
        OrderInfo orderInfo = this.getNoPayOrderByProductId(productId);
        if (orderInfo != null)
        {
            //表示该用户已经下单了,还没有支付,那就直接把他的订单返回回去就行了,否则就创建新订单
            return orderInfo;
        }
        //根据商品的id获取到该商品对象数据
        Product product = productMapper.selectById(productId);

        orderInfo = new OrderInfo();
        orderInfo.setTitle(product.getTitle()); //订单标题
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //生成订单号
        orderInfo.setProductId(productId); //商品id
        orderInfo.setTotalFee(1); //单位是:分
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType()); //支付状态
        //把商品订单存到数据库
        orderInfoMapper.insert(orderInfo);
        return orderInfo;
    }

    /**
     * 根据商品id查询已下单未支付的订单,防止重复创建订单
     * @param productId 商品id
     * @return 订单对象
     */
    private OrderInfo getNoPayOrderByProductId(Long productId)
    {
        //QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类
        QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();

        //相当于封装查询对象,这是查询条件
        //就是查询该表中,是否有 列名 product_id 对应的值等于这个 productId ,有就查询出来
        queryWrapper.eq("product_id", productId);
        queryWrapper.eq("order_status", OrderStatus.NOTPAY.getType());

        //把 queryWrapper 作为查询条件对象
        //selectOne 就是查询出一条,如果查询出多条,则会报错
        OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);

        return orderInfo;
    }


    /**
     * 将支付二维码的url存到订单中
     * @param orderNo 订单编号
     * @param codeUrl 支付二维码的地址url
     */
    @Override
    public void saveCodeUrl(String orderNo, String codeUrl)
    {
        //QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类
        QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
        //组装查询条件
        queryWrapper.eq("order_no",orderNo);

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

        //执行sql
        orderInfoMapper.update(orderInfo,queryWrapper);

    }

}

3、显示订单列表

3-1:需求:

在我的订单页面按时间倒序显示订单列表
目前是有订单,但是没展示出来。
在这里插入图片描述

3-2:代码:

前端:

调用后端接口的是api模块

<script> 脚本模块
<template> 模板,是用来定义组件的模板部分,用于描述组件的结构和布局

在这里插入图片描述

将后端返回的list商品订单列表赋值给 orders.vue 这个类后,就需要对这个数据进行渲染。
在这里插入图片描述

后端:

创建一个订单的controller
在这里插入图片描述

3-3:测试:

查看swagger

在这里插入图片描述

查看订单列表

成功显示
在这里插入图片描述

3-4:完整代码

后端:
OrderInfoController
@CrossOrigin //开放前端的跨域访问
@RestController
@RequestMapping(value = "/api/order-info")
@Api(tags = "商品订单管理")
public class OrderInfoController
{
    //依赖注入
    @Resource
    private OrderInfoService orderInfoService;

    @ApiOperation("显示商品订单列表")
    @GetMapping("/list")
    public R getOrderInfoList()
    {
        List<OrderInfo> list =
                orderInfoService.getOrderInfoListByCreateTimeDesc();
        return R.ok().data("list",list);
    }
}
OrderInfoService
    /**
     * 获取商品订单列表,并按时间倒序显示
     * @return 商品订单列表,倒序显示
     */
    List<OrderInfo> getOrderInfoListByCreateTimeDesc();
OrderInfoServiceImpl
    /**
     * 获取商品订单列表,并按时间倒序显示
     * @return 商品订单列表,倒序显示
     */
    @Override
    public List<OrderInfo> getOrderInfoListByCreateTimeDesc()
    {
        //QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类
        QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
        //组装查询条件
        queryWrapper.orderByDesc("create_time");

        //查询
        List<OrderInfo> list = orderInfoMapper.selectList(queryWrapper);

        return list;
    }

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

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

相关文章

[python 刷题] 1248 Count Number of Nice Subarrays

[python 刷题] 1248 Count Number of Nice Subarrays 题目如下&#xff1a; Given an array of integers nums and an integer k. A continuous subarray is called nice if there are k odd numbers on it. Return the number of nice sub-arrays. 这道题和 1343 Number of S…

Python实验五 异常处理

实验 1&#xff1a;为下列代码添加异常处理。 xint(input(请输入一个整数)) print(100/x) # 实验 1&#xff1a;为下列代码添加异常处理。 try:xint(input(请输入一个整数&#xff1a;))print(100/x) except ValueError:print(请输入一个整数) except ZeroDivisionError:print…

Spring Boot中解决跨域问题(CORS)

1. 跨域介绍 首先解释什么是跨域&#xff0c;跨域就是前端和后端的端口号不同&#xff1b;会产生跨域问题&#xff0c;这里浏览器的保护机制&#xff08;同源策略&#xff09;。 同源策略&#xff1a;前端和后端的协议、域名、端口号三者都相同叫做同源。 我们看一下不同源&am…

项目部署文档

申请SSL证书 先申请,用免费的 下载证书 先将下载下来的保存起来 服务器安装JDK: 创建develop目录 mkdir /usr/local/develop/ 把JDK压缩包上传到/usr/local/develop/目录 解压安装包 并且将安装到指定目录 tar -zxvf /usr/local/develop/jdk-8u191-linux-x64.tar.gz -C /us…

嵌入式中如何将BootLoader与APP合并成一个固件

1、前言 嵌入式固件一般分为BootLoader和App&#xff0c;BootLoader用于启动校验、App升级、App版本回滚等功能&#xff0c;BootLoader在cpu上电第一阶段中运行&#xff0c;之后跳转至App地址执行应用程序。 因此&#xff0c;在发布固件的时候&#xff0c;会存在BootLoader固件…

IOS手机耗电量测试

1. 耗电量原始测试方法 1.1 方法原理&#xff1a; 根据iPhone手机右上角的电池百分比变化来计算耗电量。 1.2实际操作&#xff1a; 在iOS通用设置中打开电池百分比数值显示&#xff0c;然后操作30分钟&#xff0c;60分钟&#xff0c;90分钟&#xff0c;看开始时和结束时电池…

【WSL/WSL 2-Redis】解决Windows无法安装WSL Ubuntu子系统与Redis安装

前言 在现代计算环境中&#xff0c;开发人员和技术爱好者通常需要在不同的操作系统之间切换&#xff0c;以便利用各种工具和应用程序。在这方面&#xff0c;Windows用户可能发现WSL&#xff08;Windows Subsystem for Linux&#xff09;是一个强大的工具&#xff0c;它允许他们…

第六章 块为结构建模 P1|系统建模语言SysML实用指南学习

仅供个人学习记录 概述 块是SysML结构中的模块单元&#xff0c;用于定义一类系统、部件、部件互连&#xff0c;或者是流经系统的项&#xff0c;也用于定义外部实体、概念实体或其他逻辑抽象 块定义图用于定义块以及块之间的相互关系&#xff0c;如层级关系&#xff0c;也用于…

vue+elementUI 设置el-descriptions固定长度并对齐

问题描述 对于elementUI组件&#xff0c;el-descriptions 在以类似列表的形式排列的时候&#xff0c;上下无法对齐的问题。 问题解决 在el-descriptions 标签中&#xff0c;添加属性&#xff1a; :contentStyle"content_style" 控制其内容栏长度 <el-descripti…

【快速解决】Android Studio ERROR: Read timed out

目录 前言 回顾我查到过的解决方案&#xff08;这里是我自己解决时候的经历&#xff0c;赶时间的可以直接跳过看文章最后&#xff0c;快速进行解决&#xff09; 快速解决方案如下 总结 前言 当我们新建一个安卓项目出现Read timed out时候不要慌&#xff0c;这篇文章会打开…

PHP进销存ERP系统源码

PHP进销存ERP系统源码 系统介绍&#xff1a; 扫描入库库存预警仓库管理商品管理供应商管理。 1、电脑端手机端&#xff0c;手机实时共享&#xff0c;手机端一目了然。 2、多商户Saas营销版 无限开商户&#xff0c;用户前端自行注册&#xff0c;后台管理员审核开通 3、管理…

[LeetCode]-链表中倒数第k个结点-CM11 链表分割-LCR 027. 回文链表

目录 链表中倒数第k个结点 题目 思路 代码 CM11 链表分割 题目 思路 代码 LCR 027.回文链表 题目 思路 代码 链表中倒数第k个结点 链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId…

YOLO目标检测数据集大全【含voc(xml)、coco(json)和yolo(txt)三种格式标签+划分脚本+训练教程】(持续更新建议收藏)

一、作者介绍&#xff1a;资深图像算法工程师&#xff0c;YOLO算法专业玩家&#xff1b;擅长目标检测、语义分割、OCR等。 二、数据集介绍&#xff1a; 真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;分享的绝大部分数据集已应用于各种实际落地项目。所有数据…

Technology strategy Pattern 学习笔记4 - Creating the Strategy-Corporate Context

Creating the Strategy-Corporate Context 1 •. Stakeholder Alignment 1.1 要成功&#xff0c;要尽可能获得powerful leader的支持 1.2 也需要获得最高执行层的支持 1.3 Determining&#xff08;确定&#xff09; Stakeholders 需要建立360度组织图&#xff0c;确认三类人…

GEE数据集——原住民土地(原住民土地地图)数据集

原住民土地&#xff08;原住民土地地图&#xff09; 土地承认是人们在日常生活中融入原住民存在和土地权利意识的一种方式。这通常在仪式、讲座或教育指南开始时进行。它可以是一种明确但有限的方式来认识殖民主义和第一民族的历史以及定居者殖民社会变革的需要。在这种情况下…

美团面试:Redis 除了缓存还能做什么?可以做消息队列吗?

这是一道面试中常见的 Redis 基础面试题,主要考察求职者对于 Redis 应用场景的了解。 即使不准备面试也建议看看,实际开发中也能够用到。 内容概览: Redis 除了做缓存,还能做什么? 分布式锁:通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Re…

【Mybatis小白从0到90%精讲】03:编写Mapper,第一个入门程序

文章目录 前言一、创建mysql user表二、注解方式三、XML方式四、编写main方法调用前言 映射器Mapper是 MyBatis 中最重要的文件,简单的讲主要用来映射SQL语句。 映射器有两种实现方式:注解方式、XML文件方式(推荐)。 接下来演示通过两种方式,开发Mybatis第一个入门程序,…

Python基础入门例程49-NP49 字符列表的长度

最近的博文&#xff1a; Python基础入门例程48-NP48 验证登录名与密码&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程47-NP47 牛牛的绩点&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程46-NP46 菜品的价格&#xff08;条件语句&#xff09;…

在Google Kubernetes集群创建分布式Jenkins(一)

因为项目需要&#xff0c;在GKE的集群上需要创建一个CICD的环境&#xff0c;记录一下安装部署一个分布式Jenkins集群的过程。 分布式Jenkins由一个主服务器和多个Agent组成&#xff0c;Agent可以执行主服务器分派的任务。如下图所示&#xff1a; 如上图&#xff0c;Jenkins Ag…

48基于matlab的经验傅里叶分解,适用于非线性及非平稳时间序列分析,将信号进行精确分解。程序已调通,可直接运行。

基于matlab的经验傅里叶分解&#xff0c;适用于非线性及非平稳时间序列分析&#xff0c;将信号进行精确分解。程序已调通&#xff0c;可直接运行。