一、前言

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

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

本次项目使用技术栈

后端: 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

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

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

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

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

修改 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 生成扫描二维码 使用微信扫描

从零玩转系列之微信支付实战PC端接口搭建的更多相关文章

  1. app微信支付-java服务端接口 支付-查询-退款

    个人不怎么看得懂微信的文档,看了很多前辈的写法,终于调通了,在这里做一下记录. 首先来定义各种处理类(微信支付不需要特殊jar包,很多处理需要自己封装,当然也可以自己写完打个jar包) 参数要用jdo ...

  2. python - 对接微信支付(PC)和 注意点

    注:本文仅提供 pc 端微信扫码支付(模式一)的示例代码. 关于对接过程中遇到的问题总结在本文最下方. 参考: 官方文档,    https://blog.csdn.net/lm_is_dc/arti ...

  3. 微信支付(PC扫码支付和H5公众号支付)

    最近在做微信支付,微信支付比较坑,官方居然只有.NET.C#.PHP的demo居然没有java的demo.然后微信支付是不提供测试账号的需要直接用正式的公众号.首先来介绍下微信扫码支付吧,微信扫码有两 ...

  4. 【原创分享·微信支付】C# MVC 微信支付教程系列之现金红包

            微信支付教程系列之现金红包           最近最弄这个微信支付的功能,然后扫码.公众号支付,这些都做了,闲着无聊,就看了看微信支付的其他功能,发现还有一个叫“现金红包”的玩意,想 ...

  5. 【原创分享·微信支付】 C# MVC 微信支付教程系列之扫码支付

    微信支付教程系列之扫码支付                  今天,我们来一起探讨一下这个微信扫码支付.何为扫码支付呢?这里面,扫的码就是二维码了,就是我们经常扫一扫的那种二维码图片,例如,我们自己添 ...

  6. 【原创分享·微信支付】 C# MVC 微信支付教程系列之公众号支付

    微信支付教程系列之公众号支付         今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付.现在,我们讲讲这个公众号支付.公众号支付的应用环境常见的用户通过公众号,然后再通 ...

  7. C# 微信支付教程系列之扫码支付

    微信支付教程系列之扫码支付            今天,我们来一起探讨一下这个微信扫码支付.何为扫码支付呢?这里面,扫的码就是二维码了,就是我们经常扫一扫的那种二维码图片,例如,我们自己添加好友的时候 ...

  8. 《从零玩转JavaWeb+项目实战》-系列课堂录制计划

    点击试听课程 前言 很多自学编程的同学经常和我说想学一门语言自己到网上找一些教程看到一半就像背单词背到ambulance一样坚持不下去了....究其原因基本上都是:内容太多,太枯燥,专业术语听不懂,学 ...

  9. C# MVC 微信支付教程系列之公众号支付

    微信支付教程系列之公众号支付           今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付.现在,我们讲讲这个公众号支付.公众号支付的应用环境常见的用户通过公众号,然后 ...

  10. 【分享·微信支付】 C# MVC 微信支付教程系列之公众号支付

    微信支付教程系列之公众号支付           今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付.现在,我们讲讲这个公众号支付.公众号支付的应用环境常见的用户通过公众号,然后 ...

随机推荐

  1. 如何快速体验ChatGPT-4模型

    OpenAI免费版基于Gpt3.5,无法使用最新发布的 GPT-4 模型,必须开通 ChatGPT Plus.但是 OpenAI 不但屏蔽了中国的 IP 地址,连国内的 Visa 信用卡也一同屏蔽,即 ...

  2. Unity3D中的Attribute详解(五)

    今天主要来讲一下Unity中带Menu的Attribute. 首先是AddComponentMenu.这是UnityEngine命名空间下的一个Attribute. 按照官方文档的说法,会在Compo ...

  3. must be reducible node 错误

    "must be reducible node"错误通常是由于使用了无法转换为表达式树的代码或表达式. 场景再现:在项目中使用GroupBy的时候,对字段进行了类型转换,接下来正常 ...

  4. day14:列表/集合/字典推导式&生成器表达式&生成器函数

    推导式 推导式的定义: 通过一行循环判断,遍历一系列数据的方式 推导式的语法: val for val in Iterable 三种方式: [val for val in Iterable] {val ...

  5. python:生成半年内的巡检日报execl

    问题描述:使用脚本来生成半年内的数据,数据内容大概为每天的数据库巡检日报,临时抱佛脚.数据不可能是真实的,都是随机生成的,想要使用真实的数据后面直连操作系统或者数据库.后期可以慢慢实现自动化生成每天的 ...

  6. 随手记:Redis 部署到linux上面后,本地无法连接

    修改redis的配置文件 redis.conf 1. bind 设置为 0.0.0.0 2. protected-mode 设置为no   (也就是关闭保护模式) 3.    daemonize 设置 ...

  7. React课堂笔记1

    一.概要 React是用于构建用户界面的MVVM框架. React拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它.认为它可能是将来Web开发的主流工具之一. 官网:https://z ...

  8. 使用vue-cli创建第一个vue项目

    命令提示符切换至需要创建项目的目录: 直接在路径输入cmd在按键盘的enter键打开的终端就直接切换到该目录下 (1)输入以下命令: vue create 项目名称 (2)我这里选手动选择,键盘上下按 ...

  9. 【对比】文心一言对飚ChatGPT实操对比体验

    前言 缘由 百度[文心一言]体验申请通过 本狗中午干饭时,天降短信,告知可以体验文心一言,苦等一个月的实操终于到来.心中这好奇的对比心理油然而生,到底是老美的[ChatGPT]厉害,还是咱度娘的[文心 ...

  10. InnoSetup打包 添加.NET环境安装

    这是封装出来的针对.NET环境安装的精简流程 根据流程新建一个配置文件 教程都是很简单的,可以参考<InnoSetup 客户端程序打包教程> 添加.NET安装基本的函数及辅助方法 在[Se ...