【微信支付】微信v3支付案例,SpringBoot集成IJPay实现微信v3支付

news2024/11/25 7:42:14

前言

这篇文章主要实现一下通过IJPay来实现微信v3支付案例,本篇文章使用的是JSAPI即小程序支付

IJPay码云仓库:https://gitee.com/javen205/IJPay/tree/dev
IJPay官方文档:https://javen205.gitee.io/ijpay/

准备工作

导入依赖

	   <dependency>
            <groupId>com.github.javen205</groupId>
            <artifactId>IJPay-WxPay</artifactId>
            <version>2.9.6</version>
        </dependency>
  • appId 由微信生成的应用ID,全局唯一。
  • mchId 直连商户的商户号,由微信支付生成并下发。
  • apiV3Key v3密钥
  • mchSerialNo 商户证书序列号

还需要三个证书文件
在这里插入图片描述

  1. 通过微信官方指引下载证书并解压缩后得到的文件。apiclient_cert.pem称为商户证书apiclient_key.pem成为商户证书密钥
  2. 通过IJPay的接口获取,称之为微信平台证书。(一般没有这个文件,所以暂时先不管,一会获取的时候会讲这个)

在这里我们给这三个文件起个别名,以便下方代码更容易区分。

  • apiclient_cert.pem:privateCertPath
  • apiclient_key.pem:privateKeyPath
  • wx_platform_cert.pem:platformCertPath

开始

1 首先将上面所有的参数配置到application.yml文件中,或者配置到nacos上。
在这里插入图片描述

  • wxJsapiUrl:是获取微信预支付参数的url路径
  • 最下面的三个是文件的绝对路径

2 读取配置文件的配置项,新建WxPayConfig

@Data
@Component
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayConfig {
	/**
	 * appId
	 */
	private String appId;

	/**
	 * 商户号
	 */
	private String mchId;

	/**
	 * 商户证书序列号
	 */
	private String mchSerialNo;

	/**
	 * apiv3密钥
	 */
	private String apiV3Key;

	/**
	 * 支付回调地址
	 */
	private String notifyUrl;

	/**
	 * 微信支付请求url
	 */
	private String wxJsapiUrl;

	/**
	 * 私钥路径
	 */
	private String privateKeyPath;

	/**
	 * 商户证书路径
	 */
	private String privateCertPath;

	/**
	 * 微信平台证书路径
	 */
	private String platformCertPath;
}

3 首先获取到微信平台证书

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.mjfz.fc.weixin.WxPayConfig;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.OtherApiEnum;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.FileWriter;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;


	@Resource
	private WxPayConfig wxPayConfig;

	public void v3Get() {
		// 获取平台证书列表
		try {
			IJPayHttpResponse response = WxPayApi.v3(
					RequestMethodEnum.GET,
					WxDomainEnum.CHINA.toString(),
					OtherApiEnum.GET_CERTIFICATES.toString(),
					wxPayConfig.getMchId(),
					wxPayConfig.getMchSerialNo(),
					null,
					wxPayConfig.getPrivateKeyPath(),
					""
			);

			String timestamp = response.getHeader("Wechatpay-Timestamp");
			String nonceStr = response.getHeader("Wechatpay-Nonce");
			String serialNumber = response.getHeader("Wechatpay-Serial");
			String signature = response.getHeader("Wechatpay-Signature");

			String body = response.getBody();
			int status = response.getStatus();

			log.info("serialNumber: {}", serialNumber);
			log.info("status: {}", status);
			log.info("body: {}", body);
			if (status == 200) {
				JSONObject jsonObject = JSONUtil.parseObj(body);
				JSONArray dataArray = jsonObject.getJSONArray("data");
				// 默认认为只有一个平台证书
				JSONObject encryptObject = dataArray.getJSONObject(0);
				JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
				String associatedData = encryptCertificate.getStr("associated_data");
				String cipherText = encryptCertificate.getStr("ciphertext");
				String nonce = encryptCertificate.getStr("nonce");
				String serialNo = encryptObject.getStr("serial_no");
				final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, wxPayConfig.getPlatformCertPath());
				log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
			}
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(response, wxPayConfig.getPlatformCertPath());
			System.out.println("verifySignature:" + verifySignature);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
		try {
			AesUtil aesUtil = new AesUtil(apiV3key.getBytes(StandardCharsets.UTF_8));
			// 平台证书密文解密
			// encrypt_certificate 中的  associated_data nonce  ciphertext
			String publicKey = aesUtil.decryptToString(
					associatedData.getBytes(StandardCharsets.UTF_8),
					nonce.getBytes(StandardCharsets.UTF_8),
					cipherText
			);
			// 保存证书
			FileWriter writer = new FileWriter(certPath);
			writer.write(publicKey);
			writer.close();
			// 获取平台证书序列号
			X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
			return certificate.getSerialNumber().toString(16).toUpperCase();
		} catch (Exception e) {
			e.printStackTrace();
			return e.getMessage();
		}
	}

4 支付和回调

//支付
	@RequestMapping("/jsApiPay")
	@ResponseBody
	public String jsApiPay(String openId) {
		try {
			String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
			UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
					.setAppid(wxPayConfig.getAppId())
					.setMchid(wxPayConfig.getMchId())
					.setDescription("IJPay 让支付触手可及")
					.setOut_trade_no(PayKit.generateStr())
					.setTime_expire(timeExpire)
					.setAttach("微信系开发脚手架 https://gitee.com/javen205/TNWX")
					.setNotify_url(wxPayConfig.getNotifyUrl())
					.setAmount(new Amount().setTotal(1))
					.setPayer(new Payer().setOpenid(openId));

			log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
			IJPayHttpResponse response = WxPayApi.v3(
					RequestMethodEnum.POST,
					WxDomainEnum.CHINA.toString(),
					BasePayApiEnum.JS_API_PAY.toString(),
					wxPayConfig.getMchId(),
					wxPayConfig.getMchSerialNo(),
					null,
					wxPayConfig.getPrivateKeyPath(),
					JSONUtil.toJsonStr(unifiedOrderModel)
			);

			log.info("统一下单响应 {}", response);
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(response, wxPayConfig.getPlatformCertPath());
			log.info("verifySignature: {}", verifySignature);
			if (response.getStatus() == 200 && verifySignature) {
				String body = response.getBody();
				JSONObject jsonObject = JSONUtil.parseObj(body);
				String prepayId = jsonObject.getStr("prepay_id");
				Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayConfig.getAppId(), prepayId, wxPayConfig.getPrivateKeyPath());
				log.info("唤起支付参数:{}", map);
				return JSONUtil.toJsonStr(map);
			}
			return JSONUtil.toJsonStr(response);
		} catch (Exception e) {
			e.printStackTrace();
			return e.getMessage();
		}
	}

	@RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
	@ResponseBody
	public void payNotify(HttpServletRequest request, HttpServletResponse response) {
		Map<String, String> map = new HashMap<>(12);
		try {
			String timestamp = request.getHeader("Wechatpay-Timestamp");
			String nonce = request.getHeader("Wechatpay-Nonce");
			String serialNo = request.getHeader("Wechatpay-Serial");
			String signature = request.getHeader("Wechatpay-Signature");

			log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
			String result = HttpKit.readData(request);
			log.info("支付通知密文 {}", result);

			// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
			String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
					wxPayConfig.getApiV3Key(), wxPayConfig.getPlatformCertPath());

			log.info("支付通知明文 {}", plainText);

			if (StrUtil.isNotEmpty(plainText)) {
				response.setStatus(200);
				map.put("code", "SUCCESS");
				map.put("message", "SUCCESS");
			} else {
				response.setStatus(500);
				map.put("code", "ERROR");
				map.put("message", "签名错误");
			}
			response.setHeader("Content-type", ContentType.JSON.toString());
			response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
			response.flushBuffer();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

总结

简单实现微信v3支付,如有问题可以指在评论区,制作不易,点个赞吧!

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

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

相关文章

Python学习48:简易英汉字典

类型&#xff1a;字典‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ 描述‪‬‪‬…

SpringBoot+Mybatis 实现长字符串保存和更新

数据库字段&#xff0c;写成长字符 插入xml&#xff0c;注意两个点&#xff0c;否则会报错 1、长字符串字段不能写if判断&#xff1a;<if test"name ! null and name ! "> 2、插入的时候做字符串转码&#xff1a;<![CDATA[#{fileCode}]]> <insert …

验证FeignClient类型:FeignClient集成OkHttp

验证FeignClient类型 验证FeignClient类型 验证FeignClient类型 在SynchronousMethodHandler类中查看client类型

【自监督学习1】SimCLR论文阅读

文章目录 一、摘要二、引言三、方法3.1 主要框架3.2 训练一个大的batchsize 四、数据增强4.1 实验一 数据增强的组合对学习好的特征表达非常重要4.2 对比学习需要更多的数据增强 五、一些实验证明5.1 大模型更有利于无监督对比学习5.2 非线性层的预测头增加了特征表示5.3可调节…

字符设备实现内部驱动原理及分步注册流程

字符设备实现内部驱动原理&#xff1a; 应用层&#xff1a;open函数回调到驱动中open操作方法的路线&#xff1a; open&#xff08;&#xff09;--->sys_open()--->struct inode结构体--->struct cdev结构体--->struct file_operations结构体--->mycdev_open()…

RVEA多目标优化

A Reference Vector Guided Evolutionary Algorithm for Many-objective Optimization 目标函数预备知识参考向量引导选择更新参考向量流程整体框架参考向量引导选择参考向量自适应 for Many-objective Optimization) 目标函数 min ⁡ X f ( X ) ( f 1 ( X ) , f 2 ( X ) , . …

游泳耳机买什么牌子好一点?推荐四款出色的游泳耳机

游泳和跑步类似&#xff0c;短距离冲刺时&#xff0c;大脑没什么想法&#xff0c;而中长距离的有氧运动时&#xff0c;肉体是疲惫的&#xff0c;大脑是异常清晰的&#xff0c;时间却是格外难熬的。如何打发时间&#xff0c;让游泳锻炼变得不无聊&#xff0c;这是我从孩子时期就…

从零开始 Spring Boot 41:事件

从零开始 Spring Boot 41&#xff1a;事件 图源&#xff1a;简书 (jianshu.com) Spring 实现了一个简单、实用的事件框架&#xff0c;利用它我们可以在多个组件之间进行松耦合式的通信。 简单示例 让我们从一个简单的示例开始&#xff1a; public record Email(String addr…

sqli-labs靶场通关(21-30)

Less-21 还是adminadmin登录 可以看出uname是base64加密过的&#xff0c;解码得到&#xff1a;admin。 那么本关和less-20相似&#xff0c;只是cookie的uname值经过base64编码了。 抓包看一下也是如此 那么我们只需要上传paylaod的时候base64加密一下就可以了 base64加密工…

多线程-线程的创建的方式3、4:实现Callable与线程池

JDK5.0新增线程创建方式 简要概况&#xff1a; 1. 创建多线程的方式三&#xff1a;实现Callable&#xff08;jdk5.0新增的&#xff09;与之前的方式的对比&#xff1a;与Runnable方式的对比的好处 > call()可以有返回值&#xff0c;更灵活 > call()可以使用throws的方式…

模板模式(十六)

相信自己&#xff0c;请一定要相信自己 上一章简单介绍了代理模式(十五), 如果没有看过, 请观看上一章 一. 模板模式 引用 菜鸟教程里面的 模板模式介绍: https://www.runoob.com/design-pattern/template-pattern.html 在模板模式&#xff08;Template Pattern&#xff09;…

简要介绍 | 三维点云配准:理论、方法与挑战

三维点云配准&#xff1a;理论、方法与挑战 注&#xff1a;”简要介绍“系列仅从概念上对某一领域进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解 三维点云配准 是计算机视觉和机器人领域的重要课题&#xff0c;涉及从不同视角或时间点采集的三维点云数据之间寻找最…

面试半年,总结了 1000 道 2023 年 Java 架构师岗面试题

半年前还在迷茫该学什么&#xff0c;怎样才能走出现在的困境&#xff0c;半年后已经成功上岸阿里&#xff0c;感谢在这期间帮助我的每一个人。 面试中总结了 1000 道经典的 Java 面试题&#xff0c;里面包含面试要回答的知识重点&#xff0c;并且我根据知识类型进行了分类&…

Linux5.5 Mysql索引、事务与存储引擎

文章目录 计算机系统5G云计算第四章 LINUX Mysql索引、事务与存储引擎一、Mysql索引1. 索引的概念2.索引的作用3.索引的副作用4.创建索引的原则依据5.索引的分类和创建1&#xff09;普通索引2&#xff09;唯一索引3&#xff09;主键索引4&#xff09;组合索引5&#xff09;全文…

验证attention是否在图像分类问题上起决定性作用

来源&#xff1a;投稿 作者&#xff1a;摩卡 编辑&#xff1a;学姐 Motivation 现阶段出现了大量的Transformer-style图像分类模型&#xff0c;并且这些模型在ImageNet上取得了不俗的成绩&#xff0c;这些Transformer-style模型将取得高性能的功劳归功于Multi-head attention注…

【软件设计】模块设计耦合的其中类型

一.什么是高内聚、低耦合&#xff1f; 在结构化分析与模块设计方法中&#xff0c;模块化是一个很重要的概念&#xff0c;它是将一个待开发的软件分解成为若干个小的模块&#xff0c;每个模块可以独立地开发、测试。使得复杂问题的“分而治之”&#xff0c;令程序的结构清晰、易…

vue3+router4的基本使用

一、安装router npm i vue-router二、路由跳转 2.1 创建路由实例 在src目录下创建router文件夹&#xff0c;在其中创建一个index.js文件&#xff0c;创建路由实例。 通过vue-router的createRouter方法创建一个router对象。其中有history和routes 1.history&#xff1a; histo…

微机保护的数据采集系统(2)

&#xff08;二&#xff09;采样保持电路&#xff08;S&#xff0f;H&#xff09;和模拟低通滤波器&#xff08;ALF&#xff09; 1&#xff0e;采样保持电路&#xff08;S&#xff0f;H&#xff09; &#xff08;1&#xff09;采样保持原理。 S&#xff0f;H电路的作用是在一个…

Linux进程间通信 - 共享内存

之前的文章中我们讲述了匿名管道与命名管道相关的知识点&#xff0c;在本文中我们将继续讲述一种进程间通信的方式&#xff1a;共享内存。 systemV共享内存 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递不再涉及到…

Downie 4 4.6.18 MAC上最新最好用的一款视频下载工具

Downie for Mac 简介 Downie是Mac下一个简单的下载管理器&#xff0c;可以让您快速将不同的视频网站上的视频下载并保存到电脑磁盘里然后使用您的默认媒体播放器观看它们。 Downie 4 下载 Downie 4 for Mac Downie 4 for Mac软件特点 支持许多站点 -当前支持1000多个不同的…