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


    一、前言

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

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

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

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

    本次项目使用技术栈

    后端: 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 nativePay(Long productId) {
    log.info("调用统一下单API");
    //调用统一下单API
    HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
    // 请求body参数
    Map 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 builderRequestParams(String description, int total) {
    Map 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 amountMap = new HashMap<>();
    amountMap.put("total", total); // 分
    amountMap.put("currency", "CNY");
    // 设置金额
    paramsMap.put("amount", amountMap);
    return paramsMap;
    }
    /**
    * 解析响应参数
    */
    private T buildBodyParams(CloseableHttpResponse response, Class 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> 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 {
    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 nativePay(Long productId) {
    log.info("生成订单");
    // 生成订单
    OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, null);
    String codeUrl = orderInfo.getCodeUrl();
    // 下下面这段代码限制了商品只能购买一次在有效期期间不能继续创建订单
    if (StrUtil.isNotEmpty(codeUrl)) {
    log.info("订单已存在,二维码已保存");
    // 返回二维码
    Map 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 saveCodeUrl(OrderInfo orderInfo, String codeUrl) {
    //保存二维码
    orderInfoService.lambdaUpdate().eq(OrderInfo::getOrderNo, orderInfo.getOrderNo()).set(OrderInfo::getCodeUrl, codeUrl).update();
    //返回二维码
    Map 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 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 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 buildBodyParams(CloseableHttpResponse response, Class 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

  • 相关阅读:
    垂直分表为什么能够加快查询效率?
    U3D热更新技术
    IDEA的使用(五)IDEA中关联数据库(IntelliJ IDEA 2022.1.3版本)
    element UI DatePicker 日期选择器 点击时间点可选限制范围前后十五天
    机器学习 day33(误差分析、添加数据、迁移学习)
    Kafka
    C++~auto关键字
    或许是 WebGIS 下一代的数据规范 - OGC API 系列
    boom lab分析
    transforms.Normalize()
  • 原文地址:https://www.cnblogs.com/Yangbuyi/p/17474397.html