从零玩转系列之微信支付实战PC端接口搭建

news2024/11/20 8:24:50

一、前言

halo各位大佬很久没更新了最近在搞微信支付,因商户号审核了我半个月和小程序认证也找了资料并且将商户号和小程序进行关联,至此微信支付Native支付完成.此篇文章过长我将分几个阶段的文章发布(项目源码都有,小程序和PC端)

在此之前已经更新了微信支付开篇、微信支付安全、微信实战基础框架搭建、本次更新为微信支付实战PC端接口搭建,实战篇分为几个章节因为代码量确实有点多哈.

  • 第一章从零玩转系列之微信支付开篇
  • 第二章从零玩转系列之微信支付安全
  • 第三章从零玩转系列之微信支付实战基础框架搭建
  • 第四章从零玩转系列之微信支付实战PC端接口搭建

开源仓库,对您有帮助的话请给我一个star 谢谢

阿志同学/从零玩转微信支付

banner

本次项目使用技术栈

后端: SpringBoot3.1.x、Mysql8.0、MybatisPlus

前端: Vue3、Vite、ElementPlus

小程序: Uniapp、Uview

问题微信添加: BN_Tang

备注: 微信支付

二、Native模式

在com.yby6.service包下创建接口 WxPayService

package com.yby6.service;

import cn.hutool.json.JSONUtil;
import com.yby6.config.WxPayConfig;
import com.yby6.enums.WxApiType;
import com.yby6.utils.OrderNoUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Service
@Slf4j
@RequiredArgsConstructor
public class WxPayService {
    private final WxPayConfig wxPayConfig;
    /**
     * 会进行验证签名
     * 发送请求
     */
    private final CloseableHttpClient wxPayClient;

    /**
     * 统一调用下单API,生成支付二维码
     *
     * @param productId 商户ID
     */
    @SneakyThrows
    public Map<String, Object> nativePay(Long productId) {
        log.info("调用统一下单API");

        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));

        // 请求body参数
        Map<String, Object> paramsMap = builderRequestParams("测试页面统一下单", 1);

        //将参数转换成json字符串
        String jsonParams = JSONUtil.toJsonStr(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
            // 获取响应参数
            Map resultMap = buildBodyParams(response, Map.class);
            // 二维码
            String codeUrl = (String) resultMap.get("code_url");
            // 保存二维码
            return new HashMap<>() {{
                put("codeUrl", codeUrl);
            }};
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 构建器请求参数
     *
     * @param description 描述
     * @param total       总
     * @return {@link Map}<{@link String}, {@link Object}>
     */
    private Map<String, Object> builderRequestParams(String description, int total) {
        Map<String, Object> paramsMap = new HashMap<>(14);
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", description);
        paramsMap.put("out_trade_no", OrderNoUtils.getOrderNo());
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain()); // 微信回调通知 支付通知 和退款通知都调用这个接口

        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", total); // 分
        amountMap.put("currency", "CNY");
        // 设置金额
        paramsMap.put("amount", amountMap);
        return paramsMap;
    }


    /**
     * 解析响应参数
     */
    private <T> T buildBodyParams(CloseableHttpResponse response, Class<T> tClass) throws IOException {
        T bodyAsString = null;

        if (null != response.getEntity()) {
            String json = EntityUtils.toString(response.getEntity());//响应体
            bodyAsString = JSONUtil.toBean(json, tClass);
        }

        int statusCode = response.getStatusLine().getStatusCode();//响应状态码
        if (statusCode == 200) { //处理成功
            log.info("成功, 返回结果 = " + bodyAsString);
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功");
        } else if (statusCode == 404) { //处理成功,无返回Body
            log.info("没找到订单...");
        } else {
            log.info("响应:{}, {}", response.getEntity(), response.getStatusLine());
            log.info("失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
            throw new IOException("request failed");
        }
        return bodyAsString;
    }

}

在com.yby6.controller包下创建 WechatNativeController

package com.yby6.controller;

import com.yby6.config.WxPayConfig;
import com.yby6.reponse.R;
import com.yby6.service.WxPayService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 *
 * @author Yang Shuai
 * Create By 2023/6/8
 */
@Slf4j
@RestController
@RequestMapping("/api/wx-pay/native")
@RequiredArgsConstructor
public class WechatNativeController {
    private final WxPayService wxPayService;

    /**
     * 调用统一下单API,生成支付二维码
     *
     * @param productId 产品id
     * @return {@link R}
     */
    @PostMapping("/native/{productId}")
    public R<Map<String, Object>> nativePay(@PathVariable Long productId) {
        log.info("发起支付请求 v3, 返回支付二维码连接和订单号");
        return R.ok(wxPayService.nativePay(productId));
    }

}

启动程序 请求下单接口 /api/wx-pay/native/native/1

image-20230611141531298

复制返回的微信二维码地址

进入 https://cli.im/url 生成扫描二维码 使用微信扫描

image-20230611142227976

结果可以正常扫码并且支付

image-20230611142342119

🌈 接入商品和订单存储数据库 (重点)

修改 WxPayService 引入订单服务

/**
 * 订单服务
 */
private final OrderInfoService orderInfoService;

修改 OrderInfoService 新增 createOrderByProductId 方法

package com.yby6.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yby6.domain.OrderInfo;
import com.yby6.domain.Product;
import com.yby6.enums.OrderStatus;
import com.yby6.mapper.OrderInfoMapper;
import com.yby6.utils.OrderNoUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfo> {

    private final ProductService productService;

    /**
     * 创建订单产品id
     *
     * @param productId 产品id
     * @param nickName  支付者名称 (用于小程序)
     * @return {@link OrderInfo}
     */
    public OrderInfo createOrderByProductId(Long productId, String nickName) {
        // 查找已存在但未支付的订单
        OrderInfo orderInfo = this.lambdaQuery().eq(OrderInfo::getProductId, productId).eq(OrderInfo::getOrderStatus, OrderStatus.NOTPAY.getType()).one();
        if (orderInfo != null) {
            return orderInfo;
        }

        // 根据商品ID 获取商品信息
        final Product product = productService.lambdaQuery().eq(Product::getId, productId).one();
        // 创建订单信息
        orderInfo = new OrderInfo();

        String productTitle = product.getTitle();
        if (nickName != null) {
            productTitle = productTitle.concat("-" + nickName);
        }

        orderInfo.setTitle(productTitle);
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo());
        orderInfo.setProductId(productId);
        orderInfo.setTotalFee(product.getPrice()); // 分
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
        // 保存订单信息
        save(orderInfo);
        return orderInfo;
    }
}

修改 nativePay 方法并且优化部分代码提公共

/**
     * 统一调用下单API,生成支付二维码
     *
     * @param productId 商户ID
     */
    @SneakyThrows
    public Map<String, Object> nativePay(Long productId) {
        log.info("生成订单");
        // 生成订单
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, null);
        String codeUrl = orderInfo.getCodeUrl();

        // 下下面这段代码限制了商品只能购买一次在有效期期间不能继续创建订单
        if (StrUtil.isNotEmpty(codeUrl)) {
            log.info("订单已存在,二维码已保存");
            // 返回二维码
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        }

        log.info("调用统一下单API");
        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        // 请求body参数
        builderRequestParams(httpPost, orderInfo.getTitle(), orderInfo.getTotalFee(), orderInfo.getOrderNo());
        // 完成签名并执行请求
        try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
            // 获取响应参数
            Map resultMap = buildBodyParams(response, Map.class);
            // 二维码
            String url = (String) resultMap.get("code_url");
            // 保存二维码
            return saveCodeUrl(orderInfo, url);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 保存二维码
     *
     * @param orderInfo 订单信息
     * @param codeUrl   保存二维码
     */
    private Map<String, Object> saveCodeUrl(OrderInfo orderInfo, String codeUrl) {
        //保存二维码
        orderInfoService.lambdaUpdate().eq(OrderInfo::getOrderNo, orderInfo.getOrderNo()).set(OrderInfo::getCodeUrl, codeUrl).update();
        //返回二维码
        Map<String, Object> map = new HashMap<>();
        map.put("codeUrl", codeUrl);
        map.put("orderNo", orderInfo.getOrderNo());
        return map;
    }

    /**
     * 构建器请求参数
     *
     * @param httpPost    构建请求
     * @param description 描述
     * @param total       总
     * @param orderNo 交易订单号
     * @return {@link Map}<{@link String}, {@link Object}>
     */
    private void builderRequestParams(HttpPost httpPost, String description, int total, String orderNo) {
        Map<String, Object> paramsMap = new HashMap<>(8);
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", description);
        paramsMap.put("out_trade_no", orderNo);
        // 微信回调通知 支付通知 和退款通知都调用这个接口
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain());

        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", total); // 分
        amountMap.put("currency", "CNY");
        // 设置金额
        paramsMap.put("amount", amountMap);
        // 将参数转换成json字符串
        String jsonParams = JSONUtil.toJsonStr(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
    }


    /**
     * 解析响应参数
     */
    private <T> T buildBodyParams(CloseableHttpResponse response, Class<T> tClass) throws IOException {
        T bodyAsString = null;

        if (null != response.getEntity()) {
            String json = EntityUtils.toString(response.getEntity());//响应体
            bodyAsString = JSONUtil.toBean(json, tClass);
        }

        int statusCode = response.getStatusLine().getStatusCode();//响应状态码
        if (statusCode == 200) { //处理成功
            log.info("成功, 返回结果 = " + bodyAsString);
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功");
        } else if (statusCode == 404) { //处理成功,无返回Body
            log.info("没找到订单...");
        } else {
            log.info("响应:{}, {}", response.getEntity(), response.getStatusLine());
            log.info("失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
            throw new IOException("request failed");
        }
        return bodyAsString;
    }

上面代码新增了生成订单保存到数据库的过程并且优化了部分代码

启动程序 请求下单接口 /api/wx-pay/native/native/{productId}

{productId} 查看商品表数据的ID

复制返回的微信二维码地址

进入 https://cli.im/url 生成扫描二维码 使用微信扫描

image-20230611230137951

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

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

相关文章

阿里云+Nginx Proxy Manager 设置二级域名

这里我们以购买阿里云的域名为例 有域名的作用&#xff1a;当我们在浏览器上面访问主机的某一个端口时&#xff0c;必须输入主机ip端口号&#xff0c;这就会非常的麻烦&#xff0c;而且也会暴露出我们的主机名&#xff0c;很不安全&#xff0c;因此域名的好处就是可以将我们的主…

开关电源-FPC入门知识

01功率因数补偿和功率因数校正 功率因数补偿&#xff1a;在上世纪五十年代&#xff0c;已经针对具有感性负载的交流用电器具的电压和电流不同相&#xff08;图1&#xff09;从而引起的供电效率低下提出了改进方法&#xff08;由于感性负载的电流滞后所加电压&#xff0c;由于电…

JavaWeb笔记(二)

数据库基础 数据库是学习JavaWeb的一个前置&#xff0c;只有了解了数据库的操作和使用&#xff0c;我们才能更好地组织和管理网站应用产生的数据。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IJ1neG69-1686619058026)(null)] 什么是数据库 数…

2023中国纸业碳中和与可持续峰会,九月来袭!

制浆与造纸是资源密集型产业&#xff0c;具有高污染、高能耗的特点。“双碳”目标提出后&#xff0c;造纸行业同时面临需求和产能增长的生产现状与实现“双碳”目标的考验。如何在未来保证产量增长&#xff0c;还能实现节能减排和降耗&#xff0c;成为了所有制浆造纸及上下游产…

docker容器启动的问题 - docker容器和虚拟机的比较 - docker的底层隔离机制

目录 一、docker容器启动的问题&#xff1f; 二、什么是docker仓库&#xff1f; 三、虚拟机和docker容器的区别&#xff1a; docker的优势&#xff1a; docker的缺点&#xff1a; 对比&#xff1a; 四、docker的底层隔离机制 参考文献&#xff1a;LXC linux容器简介——…

前端项目架构怎么搭

前端项目架构 文章目录 **前端项目架构** **框架选型标准****异常处理****自动化构建** **&#xff08;打包&#xff09;****基础组件****公共方法封装****目录结构分配原则** 框架选型标准 ​ 框架的选型需要考虑很多因素&#xff0c;如该技术能否可以满足业务需求、浏览器支…

《大卫科波菲尔》社会网络分析

《大卫科波菲尔》社会网络分析 1.简介1.1数据集介绍1.2社会网络分析简介1.3《大卫科波菲尔》介绍 2.描述性统计3.网络概述4.社区发现5.好句摘抄6.总结和不足 1.简介 1.1数据集介绍 Newman教授的个人数据网站 网址&#xff1a;http://www-personal.umich.edu/~mejn/netdata/ 从…

网络设备容量测试该如何进行?

网络设备容量测试该如何进行? 网络设备容量测试是现代IT运维管理中的重要内容之一。随着企业对网络带宽需求的不断增加&#xff0c;为了更好地满足用户需求和提升网络性能&#xff0c;进行网络设备容量测试显得尤为重要。那么&#xff0c;网络设备容量测试该如何进行呢? 首先…

转行大数据该怎么学

大数据分析主要面向于离线计算。负责数据分析、报表统计等工作&#xff0c;重于数据价值的体现&#xff1b;数据的ETL调度&#xff0c;即E抽取、T转换、L加载&#xff0c;着重于离线数据的流转。虽然工作形式比较单一&#xff0c;但日常需求比较多&#xff0c;尤其是节假日的数…

UE特效案例 —— 武器附魔

一&#xff0c;环境配置 创建默认地形Landscape&#xff0c;如给地形上材质需确定比例&#xff1b;添加环境主光源DirectionalLight&#xff0c;设置相应的强度和颜色&#xff1b;PostProcessVolume设置曝光&#xff0c;设置Min/Max Brightness为1&#xff1b; 与关闭Game Sett…

从数据中台实践,浅谈数据质量管理

时代背景 近20年来&#xff0c;我国的科学技术发展日新月异&#xff0c;各种新兴技术层出不穷&#xff0c;深刻的改变着各行各业&#xff0c;也改变着我们的生活。大数据、云计算、人工智能的出现更是将技术革命推向了高潮。在这种背景下&#xff0c;继农业经济、工业经济之后&…

NSS周常刷密码(3)

[LitCTF 2023]md5的破解 from hashlib import md5 from string import ascii_lowercase,digits import itertools f1 LitCTF{md5can3derypt213thoughcrsh} for i in itertools.product(ascii_lowercasedigits, repeat4):t .join(i)flag f1[:13] t[:2] f1[13:16] t[2] f…

d2l_第四章学习_Classification/Softmax Regression

x.1 Classification 分类问题理论 x.1.1 Classification和Regression的区别 注意&#xff0c;广义上来讲&#xff0c;Classification/Softmax Regression 和 Linear Regression 都属于线性模型。但人们口语上更习惯用Classification表示Softmax Regression&#xff0c;而用Re…

13. ReentrantLock、ReentrantReadWriteLock、StampedLock讲解

13.1 关于锁的面试题 ● 你知道Java里面有那些锁 ● 你说说你用过的锁&#xff0c;锁饥饿问题是什么&#xff1f; ● 有没有比读写锁更快的锁 ● StampedLock知道吗&#xff1f;&#xff08;邮戳锁/票据锁&#xff09; ● ReentrantReadWriteLock有锁降级机制&#xff0c;你知道…

linux实验四 vi编辑器及用户管理

1、vi编辑器的详细使用 &#xff08;1&#xff09;在用户主目录下建一个名为vi的目录。 &#xff08;2&#xff09;进入vi目录。 &#xff08;3&#xff09;将文件/etc/man_db.conf复制到当前目录下&#xff0c;并用命令sudo修改man_db.conf的属性为所有用户可以读写。 &am…

华为OD机试真题 JavaScript 实现【贪心的商人】【2023Q1 100分】

一、题目描述 商人经营一家店铺&#xff0c;有number种商品&#xff0c;由于仓库限制每件商品的最大持有数量是item[index]&#xff0c;每种商品的价格在每天是item_price[item_index][day]&#xff0c;通过对商品的买进和卖出获取利润&#xff0c;请给出商人在days天内能获取…

SQL高级之order by优化

简介 MySQL支持两种方式的排序&#xff0c;FileSort和Index&#xff0c;其中Index的效率较高他是指MySQL扫描索引本身完成排序。FileSort方式效率较低 使用Index 一般情况下ORDER BY满足两种情况会使用索引排序 ORDER BY语句使用索引最左前列使用where子句与order by 子句条…

复习之linux虚拟化的介绍

一、虚拟化客户端及工具的安装 1.在虚拟机westos中列出&#xff1a; ps:虚拟机中安装虚拟机无意义&#xff0c;这里指是做实验看清楚虚拟机的创建&#xff01; # dnf group list --hidden irtualization Client &#xff1a;虚拟化客户端 Virtualization Tools &…

稳定性治理二,稳定性分析

目录 重新认识系统稳定性&#xff08;SLA与系分&#xff09;稳定性分析&#xff08;单点&#xff0c;容量和性能&#xff0c;依赖&#xff0c;数据保护&#xff0c;安全&#xff0c;资损&#xff0c;弹性能力&#xff0c;业务连续性&#xff0c;变更控制&#xff09;压测方案&a…

面向对象3构造器

类的成员之三&#xff1a;构造器(Constructor) 我们 new 完对象时&#xff0c;所有成员变量都是默认值&#xff0c;如果我们需要赋别的值&#xff0c;需要 挨个为它们再赋值&#xff0c;太麻烦了。我们能不能在 new 对象时&#xff0c;直接为当前对象的 某个或所有成员变量直接…