从零玩转系列之微信支付实战PC端接口搭建
一、前言
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端接口搭建的更多相关文章
- app微信支付-java服务端接口 支付-查询-退款
个人不怎么看得懂微信的文档,看了很多前辈的写法,终于调通了,在这里做一下记录. 首先来定义各种处理类(微信支付不需要特殊jar包,很多处理需要自己封装,当然也可以自己写完打个jar包) 参数要用jdo ...
- python - 对接微信支付(PC)和 注意点
注:本文仅提供 pc 端微信扫码支付(模式一)的示例代码. 关于对接过程中遇到的问题总结在本文最下方. 参考: 官方文档, https://blog.csdn.net/lm_is_dc/arti ...
- 微信支付(PC扫码支付和H5公众号支付)
最近在做微信支付,微信支付比较坑,官方居然只有.NET.C#.PHP的demo居然没有java的demo.然后微信支付是不提供测试账号的需要直接用正式的公众号.首先来介绍下微信扫码支付吧,微信扫码有两 ...
- 【原创分享·微信支付】C# MVC 微信支付教程系列之现金红包
微信支付教程系列之现金红包 最近最弄这个微信支付的功能,然后扫码.公众号支付,这些都做了,闲着无聊,就看了看微信支付的其他功能,发现还有一个叫“现金红包”的玩意,想 ...
- 【原创分享·微信支付】 C# MVC 微信支付教程系列之扫码支付
微信支付教程系列之扫码支付 今天,我们来一起探讨一下这个微信扫码支付.何为扫码支付呢?这里面,扫的码就是二维码了,就是我们经常扫一扫的那种二维码图片,例如,我们自己添 ...
- 【原创分享·微信支付】 C# MVC 微信支付教程系列之公众号支付
微信支付教程系列之公众号支付 今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付.现在,我们讲讲这个公众号支付.公众号支付的应用环境常见的用户通过公众号,然后再通 ...
- C# 微信支付教程系列之扫码支付
微信支付教程系列之扫码支付 今天,我们来一起探讨一下这个微信扫码支付.何为扫码支付呢?这里面,扫的码就是二维码了,就是我们经常扫一扫的那种二维码图片,例如,我们自己添加好友的时候 ...
- 《从零玩转JavaWeb+项目实战》-系列课堂录制计划
点击试听课程 前言 很多自学编程的同学经常和我说想学一门语言自己到网上找一些教程看到一半就像背单词背到ambulance一样坚持不下去了....究其原因基本上都是:内容太多,太枯燥,专业术语听不懂,学 ...
- C# MVC 微信支付教程系列之公众号支付
微信支付教程系列之公众号支付 今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付.现在,我们讲讲这个公众号支付.公众号支付的应用环境常见的用户通过公众号,然后 ...
- 【分享·微信支付】 C# MVC 微信支付教程系列之公众号支付
微信支付教程系列之公众号支付 今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付.现在,我们讲讲这个公众号支付.公众号支付的应用环境常见的用户通过公众号,然后 ...
随机推荐
- VS2013连接SQLSERVER数据库时显示无法添加数据连接
VS2013连接sqlserver2008时出现如下错误: VS2013添加Microsoft SQL Server数据源时,会遇到无法添加数据连接. Could not load file or a ...
- IconJar - Mac 上的一款多功能图标素材管理工具
IconJar 是一个多功能的图标管理工具,由世界各地的设计师和开发人员使用.在一个应用程序中搜索.组织.预览和检索图标,而不是创建大量的文件夹来存储你的收藏.这款应用针对黑暗模式进行了优化,并支持S ...
- 职场「OKR」,魔幻又内卷
个人习惯称之为[O-KR-KPI]组合: 01 从进厂实习那天开始,就接触了KPI的概念: 互联网公司,年初入职,可能因为那天是周五,又赶上月底,少不了要把KPI搬出来折腾一番: 天时,地利,人和: ...
- 随机服务系统模拟—R实现(一)
排队论--随机服务系统 日常生活中存在大量有形和无形的排队或拥挤现象,如旅客购票排队,市内电话占线等现象.排队论的基本思想是 1909 年丹麦数学家.科学家,工程师 A. K. 埃尔朗在解决自动电话设 ...
- python入门教程之十二Open及file操作
读和写文件 open() 将会返回一个 file 对象,基本语法格式如下: open(filename, mode) filename:包含了你要访问的文件名称的字符串值. mode:决定了打开文件的 ...
- day60:Linux压缩与打包&用户管理&用户提权sudo&grep,sed,awk,sort,uniq
目录 1.文件管理-压缩与打包 2.用户管理 用户怎么查 如何创建用户 创建的用户信息都存储在哪? 用户存储密码的文件 如何为用户设定密码? 3.用户组 4.用户提权相关 5.Extra:额外补充 文 ...
- NTP 4.2.6p5版本导致多个系统安全漏洞
问题描述:通过漏洞扫描发现NTP 4.2.6p5版本导致多个系统漏洞,需要升级版本更高的ntp,一般刚开始都是yum直接装ntp包,现在需要重新卸载安装源码包 下载链接:http://distfile ...
- c++基本数据结构
基本数据结构: 一.线性表 1.顺序结构 线性表可以用普通的一维数组存储. 你可以让线性表可以完成以下操作(代码实现很简单,这里不再赘述): 返回元素个数. 判断线性表是否为空. 得到位置为p的元素. ...
- 2023 年十大 API 管理趋势
本文探讨了 API 管理在数字化转型中的重要性,以及 API 管理面临的挑战和发展机遇.文章重点介绍了十大 API 管理发展趋势,包括 API 安全性.API 标准化.云端 API 管理解决方案.低代 ...
- JVM有意思的图-持续更新
放一些JVM有意思的图 通过一行代码联想JVM: