微信支付APIv3
Posted 杀手不太冷!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微信支付APIv3相关的知识,希望对你有一定的参考价值。
文章目录
- 微信支付
- 之前我的密钥啥的都是放到配置文件里面以后可以再写一个文件
- 基础支付APIv3介绍
- 获取验签和HttpClient
- 微信支付的SDK工具
- Native支付流程
- 我们程序运行时的日志也可以使用log.debug
- 在方法堆栈里面查看我们程序执行的方法调用顺序
- 内网穿透
- 微信支付相关的实体
- 用户支付成功之后微信回调商户的通知接口,我们的商户自己的回调通知接口必须要进行验签
- 用户支付成功之后微信回调商户的通知接口,一定要加锁,并且一定要处理微信发来的重复请求
- 微信支付通知回调商户接口的时候携带的参数有密文需要我们自己解析
- 如果用户支付成功之后微信一直没有回调商户接口,那么商户会主动调用微信的查询订单API去查询订单情况
- 微信支付APIv3版本与微信支付APIv2版本的区别
- 微信支付V3版本引入的SDK是何时被用到的?
- 代码托管地址
微信支付
之前我的密钥啥的都是放到配置文件里面以后可以再写一个文件
之前,我做微信支付的时候,我把所有的商户号,商户证书序列号,商户证书私钥都放到了配置文件里面,以后像这类信息,可以和配置文件区分开,单独写一个文件,然后把它们放到这个文件里面,如下图:
然后我们如果想要取出来商户的相关信息,我们只需要通过WxPayConfig配置类的get方法就可以获取,如下图:
把我们的wxpay.properties变成SpringBoot的配置文件,如下图:
其实这些信息我们还是放到了配置文件里面,只不过取出来的时候,我们用的不再是@Value注解了,但其实我觉得还是使用@Value注解的这种方式比较简便。
基础支付APIv3介绍
内容:
1.引入支付参数
2.加载商户私钥
3.获取平台证书和验签器
4.获取HttpClient对象
5.Api字典和接口规则
6.内网穿透:微信向我们的开发服务器发送请求的时候,我们的开发服务器必须有一个微信可以访问的外网地址,而我们的开发服务器一般都是局域网环境的,是没有独立ip的,因此需要进行内网穿透,让微信那边可以访问到我们开发环境的接口。
7.API v3
获取验签和HttpClient
APIv3证书与密钥使用说明
微信地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_0.shtml,如下图:
解释上述流程:商户先使用自己的私钥对签名进行加密,然后微信端再用商户的公钥对签名解密并验证,然后微信端再拿自己的私钥对签名进行加密,接着商户端再拿微信的公钥进行解密,最上面这部分是典型的非对称加密,先拿自己的私钥加密,对方再拿我的公钥解密。
微信支付的SDK工具
社区源码如下图:
获取PrivateKey对象如下图:
如何获取证书对象,如下图:
对接微信支付SDKV3版本的时候,我们需要根据社区源码中的介绍写一个WxPayConfig配置类,用来做商户对接微信的前期准备(即自动的进行签名验证,加密解密和证书相关的操作),其实也就是需要在生成CloseableHttpClient对象之前,要自动的进行微信的验签,加密解密,敏感数据处理等一些列的操作,这样我们开发的时候就不用管这些细节了,微信的SDK已经自动的帮助我们处理了,如下图:
Native支付流程
官方网址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_4.shtml,如下图:
我们程序运行时的日志也可以使用log.debug
在方法堆栈里面查看我们程序执行的方法调用顺序
注意我们Frames里面的方法堆栈,只有当我们打断点的时候可以看到,如果不打断点是看不到方法堆栈信息的,如下图:
内网穿透
当微信回调我们的时候,我们本地必须要有一个公网可以访问的地址,所以我们必须要使用内网穿透,如下图:
先去ngrok官网下载工具,如下图:
然后双击运行我们的额ngrok.exe内网穿透工具,如下图:
接着去官网找两条命令依次执行,如下图:
注意,必须要先创建一个用户,这样登录ngrok之后才可以获取到第一条命令的token的值,如下图:
这两条命令的运行结果如下图:
正常情况下我们得到的最后的外网地址,是可以在外网访问的,但是我这里却不能,不知道是什么原因,但是原理就是这个原理,知道这个思想原理就可以了。
后来我又发现了一个特别好用的内网穿透工具,叫做快解析
首先去官网注册账号,如下图:
然后点击免费下载,下载之后的结果如下图:
双击运行之后,配置内网映射,如下图:
最后就可以获取到内网穿透地址了,如下图:
然后在商户那里设置我们的外网地址,如下图:
微信支付相关的实体
微信支付包括用户方,商户方,和微信方。
所以对于一个用户在某个商户使用微信付款购买了某个商品之后,都会牵涉到那些数据库表呢?
- 首先肯定是会有一个微信订单表的,每成功进行一次订单交易,微信方都需要记录这次订单的相关信息,包括本次的微信订单号,用户的基本信息(包括手机号,姓名等),商户的基本信息(包括商户号等信息),用户购买的商品的基本信息(包括商品的名字,商品的金额,商品的数量等)
- 对于商户来说,它也是要把这笔订单的信息存放到商户订单表中的,包括本次的商户订单号,用户购买的商品的基本信息(包括商品的名字,商品的金额,商品的数量等),用户的基本信息(包括手机号,姓名等),还有退款表(用户购买商品之后可能会退款,我们也需要用一个数据库记录,这里面要包括微信退款id,微信单号,商户订单号,商品退款理由等)
- 所以关于微信支付,我现在能想到的数据库表有商户订单表,微信订单表,商品信息表,用户信息表这四个
商户方有商户方自己的商户订单表和商户退款表,所以商户方有商户的商户订单号和商户退款号;
微信方有微信方自己的微信订单表和微信退款表,所以微信方有微信的微信订单号和微信退款号。
用户支付成功之后微信回调商户的通知接口,我们的商户自己的回调通知接口必须要进行验签
微信支付流程中,当微信回调商户的接口的时候,要求这个商户的这个接口必须要对发来的请求进行验签,以确保这个请求是从微信发送过来的,而不是其他的黑客伪造的,如下图:
我们程序里的验签代码,如下图:
用户支付成功之后微信回调商户的通知接口,一定要加锁,并且一定要处理微信发来的重复请求
官网说明,如下图:
首先,**先来说一下为什么要在商户的微信回调接口中加锁?**因为你假如,现在有两个微信回调请求,同时到达,这个时候商户就会进行两次处理,而本来商户如果接收到微信发来的回调请求(请求中包含用户的支付成功信息),这个时候商户会给用户进行发货,但如果商户接收了两次这样的请求,它就会发两次货,这样就会造成资金损失。怎么加锁呢?如下图:
**再来说一下怎么处理微信重复的回调请求?**本来,在商户接收微信发来的第一个回调请求的时候,会更新订单状态,更新为支付成功,然后会存放一条日志,如下图:
但是如果微信回调商户,商户给微信返回的结果不对,(比如说由于网络延迟,商户返回给微信的结果超过了5秒钟,那么这个时候微信就会再次回调商户)那么微信重复再发送好多次回调请求,那么商户就会更新订单状态为支付成功(微信回调商户的时候,其实就是把用户支付成功的结果告诉商户,商户再去把它的订单表的订单状态修改为支付成功),更新很多次,这倒无所谓,尽管再更新结果都是一样的;但是对于支付日志就不一样了,会存放很多条重复的信息,这就会对结果产生影响了,所以为了解决这个问题,需要在商户的支付通知接口中验证一下订单状态,如果不是未支付状态就不再更新数据库了,如下图:
微信支付通知回调商户接口的时候携带的参数有密文需要我们自己解析
resource解密后的明文信息如下图:
解密代码如下图:
如果用户支付成功之后微信一直没有回调商户接口,那么商户会主动调用微信的查询订单API去查询订单情况
微信官方的流程图如下图:
我们程序中是把商户主动查询微信订单这部分功能写到了定时任务里面,如果商户那边的订单一直超时未支付,到一定的时间之后,商户会去主动的去微信官方查询这笔订单,然后再根据微信方返回结果中的订单状态进行相关的处理,比如如果微信那边返回的交易状态为成功,就更新商户订单状态为成功,如果微信那边返回的交易状态为失败,那么就让商户再向微信发送一个关闭订单的请求,并更新商户订单状态为关闭。
微信支付APIv3版本与微信支付APIv2版本的区别
目前微信的大部分功能都已经用V3版的接口实现了,但是仍然有一小部分功能还没有用V3版的接口实现,但是后续会陆续的使用V3版本的接口去实现,如下图:
对于V3版本来说,无论我们是给微信接口传递参数还是微信给我们的响应结果都是json格式的;但是对于V2版本来说,无论我们是给微信接口传递参数还是微信给我们的响应结果都是xml格式的;所以对于V3版本我们设计到的转换主要是map集合和json的转换,而对于V2版本我们涉及到的转换主要是map集合和xml的转换。
微信支付V3版本引入的SDK是何时被用到的?
首先我们的请求行和我们最后执行请求使用的类一个是HttpPost类,一个是CloseableHttpResponse类,这两个类都是我们SpringBoot项目中自带的类,不需要我们特别的引入,当商户调用微信的统一下单API接口的时候,是如何使用这两个类的呢?如下图:
而现在我们使用的无论是请求行对象还是执行请求的客户端对象,都不是微信支付V3版本为我们提供的SDK中的,如下图:
那么现在问题来了,我们既然引入了微信支付V3版本的SDK,那么我们是什么时候使用的这个SDK的呢?如下图:
而虽然引入了微信的这个SDK即wechatpay,但是我们在控制器方法中发起请求,执行请求并不是运用的这个方法,而是运用的我们SpringBoot项目自带的包,只不过我们执行wxPayClient.execute(httpPost)这个方法的时候会去调用我们微信的SDK,会调用wechatpay包里面的方法,进行签名验证,加解密等系列操作,为什么会这样呢?因为我们的wxPayClient变量其实是CloseableHttpClient类,而这个类我们已经加入到了IOC中,并且从IOC容器中得到这个CloseableHttpClient类的时候,会调用微信支付V3的SDK中的方法,如下图:
所以如果想使用微信支付V3版本,最关键的就是要写一个WxPayConfig配置类,对CloseableHttpClient对象进行前期准备,并且把微信支付相关的证书序列号,商户号,私钥,APIv3等一系列信息都给存到这个WxPayConfig配置类里面,如下:
package com.atguigu.paymentdemo2.config;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyPath;
// APIv3密钥
private String apiV3Key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址
private String notifyDomain;
// APIv2密钥
private String partnerKey;
/**
* 获取商户的私钥文件
* @param filename
* @return
*/
private PrivateKey getPrivateKey(String filename)
try
return PemUtil.loadPrivateKey(new FileInputStream(filename));
catch (FileNotFoundException e)
throw new RuntimeException("私钥文件不存在", e);
/**
* 获取签名验证器
* @return
*/
@Bean
public Verifier getVerifier() throws HttpCodeException, GeneralSecurityException, IOException, NotFoundException
log.info("获取签名验证器");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, privateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
// ... 若有多个商户号,可继续调用putMerchant添加商户信息
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(mchId);
return verifier;
/**
* 获取http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier)
log.info("获取httpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient()
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, mchSerialNo, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
log.info("== getWxPayNoSignClient END ==");
return httpClient;
这样我们使用CloseableHttpClient执行请求的时候,如下图:
因为wxPayClient变量其实就是我们注入到IOC容器中的CloseableHttpRequest对象,所以在它执行execute方法的过程中就会进行和微信的验签,加解密,证书相关的一系列操作,这个时候它就会调用我们的微信支付V3版本的SDK的API了,如下图:
代码托管地址
https://gitee.com/xuanyuanzy/payment-demo.git
以上是关于微信支付APIv3的主要内容,如果未能解决你的问题,请参考以下文章