Springboot----项目整合微信支付(获取支付二维码)

这篇具有很好参考价值的文章主要介绍了Springboot----项目整合微信支付(获取支付二维码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

个人简介

📦个人主页:赵四司机
🏆学习方向:JAVA后端开发
📣种一棵树最好的时间是十年前,其次是现在!
🧡喜欢的话麻烦点点关注喔,你们的支持是我的最大动力。

前言

目前更新的是Springboot项目整合微信支付系列的文章,可以在我的主页中找到该系列其他文章,这一系列的文章将会系统介绍如何在项目中引入微信支付的下单、关单、处理回调通知等功能。由于前面创作经验不足,写的文章可能不是很好,后面我会多加努力学习怎么创作,也请各位大佬有什么建议的可以不吝赐教。因为我侧重的方面不是介绍项目开发,所以关于项目开发的具体代码可以查看文末的项目源代码(后面可能会出文章介绍该项目的开发)。喜欢的话希望大家多多点赞评论收藏,当然还可以加个关注喔,目前我的愿望是突破500粉,求各位大佬成全,我在线回。

一:问题引入

在很多项目中都会涉及到支付问题,我这里的项目是外卖项目,需要再用户点击菜品加入购物车进行结算时候使用支付功能,可以采用微信支付或者支付宝支付,由于微信支付比较普遍,在这里我选用的就是微信支付。首先要说明的是,要使用微信支付是有一定的要求的,你需要获取到你的商户证书,商户私钥,商户号等等,这些东西个人是很难获取的,只有商家才能申请,可以到B站尚硅谷下载他们的证书来使用,亲测可以直接用。微信支付总体来说开发并不难,就是繁琐,因为涉及到交易问题,处理逻辑一定要严谨,但是中间很多繁琐的工作微信已经给我们封装好了方法,直接查看接口文档调用即可,省去了中间一些验签的细节,大大提高了开发效率。这里附上微信支付开发文档入口地址。我使用的是微信的Native支付,该支付方式特点是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式,当然如果你的是小程序的话可以使用小程序支付。

二:流程图

Springboot----项目整合微信支付(获取支付二维码)

获取支付二维码并不难,难的地方在于如何对订单进行处理,当用户点击“去支付”按钮时候,这时候订单就已经生成了,需要将其存入数据库中,订单状态为未支付。这时候有一个问题,一般我们下单付款途径有两种,一种是通过购物车点击去支付直接进行付款,另一种是在购物车中点击去支付弹出付款界面之后点击取消,然后到“我的订单”界面进行支付,这种称为“锁单”。这两者有什么区别呢?区别无非就在于订单是否已经创建,当我们在购物车点击去支付在向微信服务器发送请求之前,这时候其实订单还没生成,前端传过来的参数并不是一个完整的订单信息,而是一些订单的相关信息,比如收货地址,订单备注等等,后台需要拿着这些参数生成一个订单。要注意的是,出于安全考虑,一般不会在前端将订单金额直接传到后端进行使用,因为这样有可能会在前端对金额进行修改。我这里采用的策略是前端将用户的购物车id传过来,然后统计其购物车金额。

说了那么多,要如何区分两种支付发起方式呢?其实很简单,前面说过两者区别就是订单有没有创建,在“我的订单”界面发起的支付请求是已经生成有订单信息了的,我们就可以通过判断前端穿过来的数据是否携带订单号来判断该支付请求是在哪发起的。假如订单号为空,那么该支付请求就是在购物车界面“去支付”处发起的,假如订单号不为空,那么该支付请求就是在“我的订单”处发起的。

这时候还要考虑另外一个问题,用户点击去支付按钮弹出支付二维码之后,用户可以点击空白处取消该弹窗,这时候假如用户多次这样反复操作,那么我们是否就需要针对用户的每一次点击都向微信支付服务器申请一个新的二维码呢?显然这是不需要的,只有当该支付二维码过期了或者用户重新选择商品才需要重新申请。为了解决这个问题,我采取的策略是将第一次获得的code_url存入redis,由于code_url的有效期为2小时,不妨就设置该code_url在redis的存活时间就为2小时。这样每次用户发起新请求时候先尝试在redis获取code_url,获取成功则直接返回,不需要再向微信支付后台服务器发送请求。这时候还有另外一个问题,这个问题我将在下面代码中讲解。

三:代码实现

1.Controller层

@PostMapping("/native")
@ApiOperation("调用统一下单API,生成支付二维码")
public R<Map> nativePay(@RequestBody Orders orders, HttpSession session) throws IOException {
    log.info("发起支付请求");

    //返回支付二维码和订单号
    Map body = wxPayService.nativePay(orders, session);
    if(body != null) {
        return R.success(body);
    } else {
        return R.error("获取支付信息失败!");
    }
}

2.OrdersServiceImpl

@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);

    return orders;
}

3.WxPayServiceImpl

@Override
public Map nativePay(Orders orders, HttpSession session) throws IOException {
    //获取用户id
    Long userId = (Long) session.getAttribute("user");
    //构建返回体Map
    Map < String, String > map = new HashMap <> ();
    //获取当前用户购物车数据
    LambdaQueryWrapper < ShoppingCart > SCLqw = new LambdaQueryWrapper <> ();
    SCLqw.eq(ShoppingCart:: getUserId, userId);
    List < ShoppingCart > shoppingCartList = shoppingCartService.list(SCLqw);
    //尝试获取订单号,若为空则表示这是通过“去支付”按钮发起的支付请求,否则为通过“我的订单”处发起请求,此时订单号已生成
    String number = orders.getNumber();
    //创建Order实体
    Orders order = new Orders();
    /*加入购物车判断的目的是防止获取到上一次支付的支付信息,假如购物车为空说明该笔支付重新生成了支付信息
    因为只有重新生成订单才会清空购物车*/
    if (shoppingCartList.size() == 0 && number == null) {
        //获取redis缓存
        String code_url = (String) redisTemplate.opsForValue().get("codeUrl");
        //获取订单id
        String order_id = (String) redisTemplate.opsForValue().get("orderId");
        //支付链接还未过期,直接返回
        if (code_url != null && order_id != null) {
            map.put("codeUrl", code_url);
            map.put("orderId", order_id);
            return map;
        }
        if (order_id == null && code_url != null) {
            //订单超时未支付
            map.put("message", "timeout");
            return map;
        }
    }
    if (number != null) {
        //表示该请求是在“我的订单”页面发起的支付请求,此时该订单已经被创建
        String key = "orderNo_" + number + "_codeUrl";
        String codeUrl = (String) redisTemplate.opsForValue().get(key);
        if (codeUrl != null) {
            map.put("codeUrl", codeUrl);
            map.put("orderId", number);
            return map;
        }
        order = orders;
    } else {
        order = ordersService.createOrder(orders, session);
    }

    //存储订单id,存活时间为5分钟
    redisTemplate.opsForValue().set("orderId", order.getNumber(), 5, TimeUnit.MINUTES);
    map.put("orderId", order.getNumber());
    //计算金额
    log.info("计算支付金额...");
    LambdaQueryWrapper < Orders > lqw = new LambdaQueryWrapper <> ();
    lqw.eq(Orders:: getNumber, order.getNumber());
    Orders order_getAmount = ordersService.getOne(lqw);
    BigDecimal amount = order_getAmount.getAmount();
    int PayAmount = amount.intValue();

    //调用统一下单API
    HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
    httpPost.addHeader("Accept", "application/json");
    httpPost.addHeader("Content-type", "application/json; charset=utf-8");

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectMapper objectMapper = new ObjectMapper();

    ObjectNode rootNode = objectMapper.createObjectNode();
    rootNode.put("mchid", wxPayConfig.getMchId())
        .put("appid", wxPayConfig.getAppid())
        .put("description", "外卖订单")
        .put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()))
        .put("out_trade_no", orders.getNumber());  //订单号
    rootNode.putObject("amount")
        .put("total", PayAmount);  //金额

    objectMapper.writeValue(bos, rootNode);

    httpPost.setEntity(new StringEntity(bos.toString(StandardCharsets.UTF_8), "UTF-8"));
    CloseableHttpResponse response = wxPayClient.execute(httpPost);

    String bodyAsString = EntityUtils.toString(response.getEntity());
    JSONObject body = JSON.parseObject(bodyAsString);
    String code_url = (String) body.get("code_url");
    map.put("codeUrl", code_url);
    
    //将Code_url存入redis缓存,有效期为2小时
    redisTemplate.opsForValue().set("codeUrl", code_url, 2, TimeUnit.HOURS);
    redisTemplate.opsForValue().set("orderNo_" + order.getNumber() + "_codeUrl", code_url, 2, TimeUnit.HOURS);
    log.info("Code_url===>{}", code_url);
    return map;
}

可以看到,在判断订单号是否为空的 if(shoppingCartList.size() = = 0 && number = = null)语句中加入了shoppingCartList.size() == 0条件,为什么要加入这个条件呢?试想下我们将code_url存入redis中会存在什么问题,redis中code_url是有存活时间的,假如用户知道这个漏洞,买了一个1分钱的商品下单之后没付款,然后退出页面挑选了一个999大洋的商品下单,这时候加入判定条件是if(number = = null),这时候该条件肯定成立,因为用户是在购物车界面发起的支付请求,这时候压根没有订单号,那么问题就来了,这时候继续执行if里面的代码

//获取redis缓存
String code_url = (String) redisTemplate.opsForValue().get("codeUrl");
//获取订单id
String order_id = (String) redisTemplate.opsForValue().get("orderId");
//支付链接还未过期,直接返回
if(code_url != null && order_id != null){
       map.put("codeUrl",code_url);
       map.put("orderId",order_id);
       return map;
}

这时候获取到的还是上一次1分钱商品的支付码呀!这样聪明的用户不就可以支付1分钱获得价值999大洋的商品了吗,显然这样资本家是不同意的。如何解决这个问题呢?首先肯定是不能将redis取消掉的,万一取消掉之后有闲着没事干的用户要采用某些技术频繁发送支付请求就是不支付呢?这样微信那边不就禁了你的账号。这时候就要思考,当用户在购物车界面发起支付请求还携带有哪些信息可以利用到呢?没错,就是购物车数据,我们通过购物车去结算当你支付完成之后你会发现购物车原本的商品会被清空,出于严谨考虑我这也会实现这一功能。购物车是什么时候被清空的呢,看程序,只有在订单创建完成之后购物车才会清空,而订单创建的前提又是订单号为空并且能够成功在redis中获取支付URL,当用户想利用这个漏洞时候前面两个条件肯定是符合的,那么我们就正好可以利用这两个条件来修复漏洞。实现过程很简单,也就仅仅加了一个判断用户购物车数据是否为空,当用户购物车为空并且能够成功获取支付URL时候将code_url返回即可。当用户购物车不为空,说明用户更换了商品,这时候就需要向微信支付服务端发送新的请求获取新的支付URL。

至此,微信支付的用户下单功能已完成,至于如何在前端将Code_url转换成二维码展示给用户,可以查看我这篇文章附上链接。文章来源地址https://www.toymoban.com/news/detail-446106.html

四:友情链接

  • 微信支付:开发者文档
  • 微信支付API v3的Apache HttpClient扩展
  • 项目源代码:这是一个链接

到了这里,关于Springboot----项目整合微信支付(获取支付二维码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue3-pc端生成微信二维码、扫码支付监听支付回调(WebSocket)功能实现

    项目场景:后台系统中采购订单列表需要支持微信扫描支付功能,支付成功需要返回到订单列表。 调用接口接收后端返回数据, npm install qrcode --save 安装插件,处理返回数据生成二维码。 1. 后端接口返回数据如下: 2. 前端代码如下: 3. 效果图如下: 生成二维码功能到这就完

    2024年02月12日
    浏览(49)
  • 一码多端,一个二维码适用微信小程序,支付宝小程序,h5页面

    最近公司研发自己的一个小程序,因为是线下树牌,涉及到扫码这个问题,但是扫码又分三个端,浏览器扫码,微信扫一扫,支付宝扫码,做这个需求也是遇到了很多坑,在此记录一下 1.扫码进入微信小程序 首先登录微信公众平台,链接 https://mp.weixin.qq.com/  原本此处会有一

    2024年02月08日
    浏览(72)
  • 小程序获取企业微信二维码,使用联系我插件配置企业微信二维码

    通过配置获取企业微信二维码总共分为五步: 第一步:登录企业微信管理后台,查询企业微信的企业ID(corpid)和Secret(corpsecret); 第二步:获取access_token; 第三步:通过员工ID配置生成config_id(即企业微信联系我plugid); 第四步:通过config_id获取企业已配置的「联系我」方式;

    2024年02月09日
    浏览(42)
  • 生成微信小程序发布上线后的二维码 、获取微信小程序二维码、微信小程序二维码如何生成?

    情景: 1、在微信小程序审核完成,发布到线上后,想通过扫描小程序二维码进入小程序 2、可分享二维码出去,通过二维码扫码进入小程序 方法: 1、进入微信小程序的后台配置。链接:微信公众平台。(如图一) 2、进入 “设置” -- “ 基本设置” -- “小程序码及线下物

    2024年02月12日
    浏览(51)
  • 微信小程序:生成二维码带参数并获取值

    通过后台接口可以获取小程序任意页面的小程序码,需要注意的是 接口只能生成已发布的小程序的二维码 小程序接口文档 1)scene 字段的值会作为 query 参数传递给小程序/小游戏。用户扫描该码进入小程序/小游戏后,开发者可以获取到二维码中的 scene 值,再做处理逻辑。

    2024年02月15日
    浏览(41)
  • 【Java】微信小程序二维码(后台,附获取accessToken)

    目录         调用方式         主要的请求参数         工具类         二维码转图片         获取accessToken         HTTPS 调用:         请求参数         代码         实现类 POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN 属性

    2024年02月04日
    浏览(59)
  • uniapp微信小程序 获取从二维码进入小程序所携带的参数

    在app.vue  onLaunch中调用获取场景值的方法即可 (建议在onShow中调用,避免扫码冷启动)

    2024年02月15日
    浏览(41)
  • 微信小程序二维码生成及access_token获取方法详解

    本文介绍了微信小程序如何生成二维码(wxacode.get接口)以及如何通过auth.getAccessToken接口获取全局唯一的后台接口调用凭据(access_token),为开发者提供了详细的API调用指南和代码示例。 Keywords (关键词):

    2024年02月07日
    浏览(46)
  • 微信公众号二维码扫码登录(SpringBoot Java实现)

    用户扫描公众号的二维码,实现登录当前平台。 若未关注公众号,则关注后触发登录;若已关注,则直接登录。  登录时通过union_id判断用户是否在系统注册,若未注册则跳转到注册页面或提示未注册。 注意,此功能使用的接口,需要 公众号类型为 服务号 才支持! 开发阶

    2024年02月12日
    浏览(47)
  • 西米支付:支付二维码的简单介绍

    二维码支付从1.0到3.0时代的历史进程 实际上二维码技术被推出来已经有十多年了,这段悠久绵长的英雄无用武之地的时代属于二维码1.0时代,得益于互联网电子商务的飞速发展首先将二维码应用于支付并发扬光大的是两个第三方支付公司,支付宝微信,这种新型的支付方式支

    2024年02月01日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包