域名授权(Springboot使用OkHttp实现微信支付API)

微信支付API-V3和V2的区别微信支付API-V3和之前V2版本最大的区别,应该就是加密方式的改变了。新版的支付接口,全部使用是SSL双向加密。就是指微信服务器端、商户端各自都有一套证书,两者之间通讯必需使用自己证书的私钥加密,使用对方的公钥解密。具体流程图,可以参考微信支付官网的这张图:上图所在的文档链接是:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_0.shtml 有需要的可参考。由于微信支付官方提供的Java Demo使用的是Httpclient,并且比较庞杂。所以我自己对接的时候,是使用OkHttp完成的。接入前准备每种支付方式的准备都稍有区别,但区别也不大。这里以JS-API支付为例,简单说说一个全新的微信支付账号,要做哪些配置。绑定APP ID和商户号mch id:微信支付申请下来后,是没办法单独使用的。肯定要依托于公众号、小程序、APP或者网站等载体,这些载体都有自己的APP ID,需要在这些载体对应的后台里,找到微信支付菜单,点进去把微信支付的mch id和app id绑定起来。设置API KEY:这个key主要用来解密一些微信接口的返回结果,比如下载微信平台证书的时候。(为什么在双向证书的情况下,还需要这个key呢?因为这些证书,只使用来签名的,并不能加密每次请求的body)下载商户证书:这个没啥好说的,新版的微信支付V3接口,商户和微信平台,各自都有证书。配置各种授权域名、授权目录等。以上就是接入前准备的简单介绍,具体每一步的详细操作,可以参考微信支付官方的文档:JSAPI接入前准备:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtmlAPP支付接入前准备https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_1.shtml小程序支付接入前准备https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml使用OkHttp封装自带微信支付API-V3证书加解密、签名的请求微信支付本身对接起来不麻烦,无非就是下单、支付、等通知该状态三步。对新手不友好的地方,主要还是各种加密、签名等安全措施。接下来我介绍一下如何使用OkHttp封装一个http请求类,包含各种安全验证措施,外部调用的时候,只要当普通OkHttp接口调用就行。各种权限验证,已经在类里面自己实现了。简单介绍一下封装类的各个方法:generateToken :当我们请求微信的接口的时候,首先得生成签名信息,放到HTTP请求的Header里,名字叫Authorization。checkResponseSign :拿到微信的返回结果后,我们得拿返回结果算一下签名,然后和返回的签名对比一下,看看这个请求结果是真的还是伪造的。decodeWxPlatCert :微信的平台证书,定期会自动更新,我们需要调用接口下载微信的平台证书回来。这个接口的返回结果,是用我们前面“接入前准备”中提到的API KEY加密的,所以,我们得用这个方法解密。wxGet wxPost :这两个就是封装好的HTTP GET和HTTP POST请求了,已经在内部实现了各种安全措施。完整代码如下,代码依赖了很多常见的类库,比如apache-commons-lang3等,可以从代码的import中看出来,自行添加maven pom。package com.coderbbb.blogv2.utils;

import com.coderbbb.blogv2.database.dos.WxPlatCertDO;
import com.coderbbb.blogv2.database.dto.WxCertDataDTO;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class WxOkHttpUtil extends OkhttpUtil {

private static final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";

private static final String CERT_LIST = "https://api.mch.weixin.qq.com/v3/certificates";

public static ConcurrentHashMap<String, WxPlatCertDO> WX_PLAT_CERT = new ConcurrentHashMap<>();

private final static Logger logger = LoggerFactory.getLogger(WxOkHttpUtil.class);

/**
* 生成请求微信接口时需要的Authorization头
* @param url
* @param method
* @param json
* @return
*/
public static String generateToken(String url, String method, String json) {
if (json == null) {
json = "";
}
url = url.substring(StringUtils.ordinalIndexOf(url, "/", 3));

long timestamp = System.currentTimeMillis() / 1000;
String timestampStr = String.valueOf(timestamp);
String nonceStr = RandomStringUtils.random(16, true, true);

String signatureStr = Stream.of(method.toUpperCase(Locale.ROOT), url, timestampStr, nonceStr, json).collect(Collectors.joining("\n", "", "\n"));

WxCertDataDTO wxCertDataDTO = WxCertUtil.getCert();

String signResult;
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(wxCertDataDTO.getPrivateKey());
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
signResult = Base64.encodeBase64String(sign.sign());
} catch (Exception e) {
throw new RuntimeException("签名失败", e);
}

//开始拼接Token
return String.format(TOKEN_PATTERN, wxCertDataDTO.getMchId(), nonceStr, timestamp, wxCertDataDTO.getSerialNumber(), signResult);
}

/**
* 请求基本Header头,微信规定的。
* 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay2_0.shtml
* @param headerMap
* @return
*/
private static HashMap<String, String> intHeader(HashMap<String, String> headerMap) {
if (headerMap == null) {
headerMap = new HashMap<>();
}
headerMap.put("content-type", "application/json;charset=UTF-8");
headerMap.put("user-agent", "coderbbb");
headerMap.put("accept", "application/json");
return headerMap;
}

/**
* 微信请求我们时(比如支付的异步通知),拿这个函数校验微信的请求是否是合法的
* 简单说,就是验证微信请求我们时,签名是否正确的。
* @param request
* @param requestBody
* @return
*/
public static boolean checkServletRequestSign(HttpServletRequest request, String requestBody) {

String wxCertSerialNumber = request.getHeader("Wechatpay-Serial");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String sign = request.getHeader("Wechatpay-Signature");

if (!WX_PLAT_CERT.containsKey(wxCertSerialNumber)) {
return false;
}

WxPlatCertDO wxPlatCertDO = WX_PLAT_CERT.get(wxCertSerialNumber);

String signBody = Stream.of(timestamp, nonce, requestBody).collect(Collectors.joining("\n", "", "\n"));

//开始验签
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(wxPlatCertDO.getCertificate());
signature.update(signBody.getBytes(StandardCharsets.UTF_8));
if (!signature.verify(Base64.decodeBase64(sign))) {
return false;
}
} catch (Exception e) {
logger.error("验签错误", e);
return false;
}
return true;
}

/**
* 我们请求微信的接口后,得到返回结果,用这个函数把返回结果生成签名,和返回的签名对比是否一致
* 简单说,就是请求微信的接口后,我们自己用返回结果生成一个签名,和请求返回的签名对比,看看签名一样不
* @param response
* @param lazyVerify
* @return
*/
private static String checkResponseSign(Response response, boolean lazyVerify) {
String result = null;
String wxCertSerialNumber;
String timestamp;
String nonce;
String sign;

try (ResponseBody body = response.body();) {
if (body != null) {
result = body.string();
}

if (response.code() != 200) {
logger.warn("err response = " + result);
throw new RuntimeException("请求http code异常:" + response.code());
}

wxCertSerialNumber = response.header("Wechatpay-Serial");
timestamp = response.header("Wechatpay-Timestamp");
nonce = response.header("Wechatpay-Nonce");
sign = response.header("Wechatpay-Signature");
} catch (IOException e) {
throw new RuntimeException("wx okHttp read response err", e);
}

WxPlatCertDO wxPlatCertDO = null;
if (!WX_PLAT_CERT.containsKey(wxCertSerialNumber)) {
//平台证书不在已有的列表内
/**
* 我们提供以下的机制,帮助商户在平台证书更新时实现平滑切换:
*
* 1.下载新平台证书。我们将在旧证书过期前10天生成新证书。
* 商户可使用平台证书下载API 下载新平台证书,并在旧证书过期前5-10天部署新证书。
*
* 2.兼容使用新旧平台证书。旧证书过期前5天至过期当天,新证书开始逐步放量用于应答和回调的签名。
* 商户需根据证书序列号,使用对应版本的平台证书。
* (我们在所有API应答和回调的HTTP头部Wechatpay-Serial,声明了此次签名所对应的平台证书的序列号。)
*/
//所以:定时任务拉取微信平台证书,在这里,如果证书不在列表内,只有两种情况:
//1.该请求是第一次下载微信平台证书的请求;2.恶意请求。
// – 我们使用lazyVerify标记第一种情况
if (!lazyVerify) {
//不能延迟验签,抛出错误
throw new RuntimeException("签名校验失败");
} else {
//可以延迟验签,说明该请求是下载微信平台证书的请求,直接读取请求返回值,提取证书
List<WxPlatCertDO> wxPlatCertData = decodeWxPlatCert(result);
for (WxPlatCertDO item : wxPlatCertData) {
if (item.getSerialNumber().equals(wxCertSerialNumber)) {
wxPlatCertDO = item;
}
}
}
} else {
wxPlatCertDO = WX_PLAT_CERT.get(wxCertSerialNumber);
}

if (wxPlatCertDO == null) {
throw new RuntimeException("平台证书不存在,验签失败");
}

String signBody = Stream.of(timestamp, nonce, result).collect(Collectors.joining("\n", "", "\n"));

//开始验签
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(wxPlatCertDO.getCertificate());
signature.update(signBody.getBytes(StandardCharsets.UTF_8));
if (!signature.verify(Base64.decodeBase64(sign))) {
throw new RuntimeException("签名错误,请求不安全");
}
} catch (Exception e) {
throw new RuntimeException("验签错误", e);
}

return result;
}

/**
* 下载微信平台证书时,用这个函数解密拿到的返回值,得到微信平台证书列表
* @param json
* @return
*/
private static List<WxPlatCertDO> decodeWxPlatCert(String json) {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode;
JsonNode dataNode;
try {
jsonNode = mapper.readTree(json);
dataNode = jsonNode.get("data");
} catch (Exception e) {
throw new RuntimeException("读取证书JSON失败", e);
}

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

List<WxPlatCertDO> certList = new ArrayList<>();
for (JsonNode itemNode : dataNode) {
JsonNode certNode = itemNode.get("encrypt_certificate");
String cert = AesUtil.decryptJsonNodeToString(certNode);

try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate x509Cert = (X509Certificate) cf.generateCertificate(
new ByteArrayInputStream(cert.getBytes(StandardCharsets.UTF_8))
);
x509Cert.checkValidity();

WxPlatCertDO wxPlatCertDO = new WxPlatCertDO();
wxPlatCertDO.setCertificate(x509Cert);
wxPlatCertDO.setCert(cert);
wxPlatCertDO.setEffectiveTime(format.parse(itemNode.get("effective_time").asText()));
wxPlatCertDO.setExpireTime(format.parse(itemNode.get("expire_time").asText()));
wxPlatCertDO.setSerialNumber(itemNode.get("serial_no").asText());

certList.add(wxPlatCertDO);
} catch (Exception e) {
logger.error("update wx plat cert err", e);
}
}
return certList;
}

/**
* 封装的http get请求,直接使用即可,证书、签名的生成和校验已经集成
* @param url
* @param headerMap
* @return
*/
public static String wxGet(String url, HashMap<String, String> headerMap) {

boolean lazyVerify = false;
if (url.equals(CERT_LIST)) {
//是下载证书的请求,允许延迟验签
lazyVerify = true;
}

String token = generateToken(url, "get", null);

headerMap = intHeader(headerMap);
headerMap.put("Authorization", token);

Request.Builder builder = new Request.Builder().url(url);

for (Map.Entry<String, String> entry : headerMap.entrySet()) {
builder.addHeader(entry.getKey(), entry.getValue());
}

Request request = builder.build();
try (Response response = getClient().newCall(request).execute()) {
return checkResponseSign(response, lazyVerify);
} catch (IOException e) {
throw new RuntimeException("wx okHttp get err", e);
}
}

/**
* 封装的http post请求,直接使用即可,证书、签名的生成和校验已经集成
* @param url
* @param json
* @param headerMap
* @return
*/
public static String wxPost(String url, String json, HashMap<String, String> headerMap) {
RequestBody requestBody = RequestBody.create(json, MediaType.parse("application/json"));

String token = generateToken(url, "post", json);

headerMap = intHeader(headerMap);
headerMap.put("Authorization", token);

Request.Builder builder = new Request.Builder().url(url).post(requestBody);
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
builder.addHeader(entry.getKey(), entry.getValue());
}
try (Response response = getClient().newCall(builder.build()).execute()) {
return checkResponseSign(response, false);
} catch (IOException e) {
throw new RuntimeException("wx okHttp post err", e);
}
}

/**
* 从微信下载微信平台证书
* @return
*/
public static List<WxPlatCertDO> getCertList() {
String s = wxGet(CERT_LIST, null);
return decodeWxPlatCert(s);
}

}

上面的代码中,依赖了很多我自己写的其他类库,这里逐一介绍一下。OkHttpUtil 基于OkHttp封装的HTTP请求库,代码后面放。AesUtil 敏感信息解密的类,比如微信下载平台证书的请求,返回结果就是加密的,需要解密。WxCertUtil 主要有两个功能,一个是从本地文件读取商户自己的证书,给其他地方用;另一个是请求微信时,对敏感信息加密,这个功能和AesUtil是对应的。各种POJO类,比如加载证书信息后,证书信息有一个POJO类。这些类就不放代码了,每个人需求不一样,自己自定义即可。 好吧,好多读者反馈需要,那我放到文章最后。package com.coderbbb.blogv2.utils;

import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class OkhttpUtil {

private static OkHttpClient client = null;

private static final Logger logger = LoggerFactory.getLogger(OkhttpUtil.class);

private synchronized static void createClient() {
if (client == null) {
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();

okHttpBuilder.protocols(Collections.singletonList(Protocol.HTTP_1_1));
okHttpBuilder.connectTimeout(60, TimeUnit.SECONDS);
okHttpBuilder.readTimeout(60, TimeUnit.SECONDS);
okHttpBuilder.writeTimeout(60, TimeUnit.SECONDS);
okHttpBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
//支持所有类型https请求
return true;
}
});

ConnectionPool pool = new ConnectionPool(200, 1, TimeUnit.SECONDS);
okHttpBuilder.connectionPool(pool);

client = okHttpBuilder.build();
client.dispatcher().setMaxRequests(2000);
client.dispatcher().setMaxRequestsPerHost(1000);
}
}

public static OkHttpClient getClient() {
if (client == null) {
createClient();
}
return client;
}

public static String get(String url, HashMap<String, String> headerMap) {
Request.Builder builder = new Request.Builder().url(url);

if (headerMap != null) {
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
builder.addHeader(entry.getKey(), entry.getValue());
}
}

Request request = builder.build();

String result = null;
try {
result = excute(request);
} catch (Exception e) {
logger.warn("http get fail:" + url + "###" + e.getMessage());
}
return result;
}

public static String postRaw(String url, String contentType, String json, HashMap<String, String> headerMap) throws Exception {
RequestBody requestBody = RequestBody.create(json,MediaType.parse(contentType));

Request.Builder builder = new Request.Builder().url(url).post(requestBody);
if (headerMap != null) {
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
builder.addHeader(entry.getKey(), entry.getValue());
}
}
Request request = builder.build();
String result = null;
try {
result = excute(request);
} catch (Exception e) {
logger.error("http post raw fail:" + url + "###" + e.getMessage());
}
return result;
}

public static String post(String url, HashMap<String, String> data, HashMap<String, String> headerMap) throws Exception {

FormBody.Builder formBodyBuilder = new FormBody.Builder();
for (Map.Entry<String, String> entry : data.entrySet()) {
formBodyBuilder.add(entry.getKey(), entry.getValue());
}
RequestBody requestBody = formBodyBuilder.build();

Request.Builder builder = new Request.Builder().url(url).post(requestBody);
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
builder.addHeader(entry.getKey(), entry.getValue());
}
Request request = builder.build();
String result = null;
try {
result = excute(request);
} catch (Exception e) {
logger.error("http post fail:" + url + "###" + e.getMessage());
}
return result;
}

private static String excute(Request request) throws Exception {
Response response = getClient().newCall(request).execute();
ResponseBody body = response.body();
if (body != null) {
String str = body.string();
body.close();
response.close();
return str;
}
return null;
}
}

package com.coderbbb.blogv2.utils;

import com.fasterxml.jackson.databind.JsonNode;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AesUtil {

private static final int TAG_LENGTH_BIT = 128;

public static String aesKey;

public static String decryptJsonNodeToString(JsonNode jsonNode){
return decryptToString(
jsonNode.get("associated_data").asText().getBytes(StandardCharsets.UTF_8),
jsonNode.get("nonce").asText().getBytes(StandardCharsets.UTF_8),
jsonNode.get("ciphertext").asText());
}

public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) {
if (aesKey == null) {
throw new RuntimeException("aesKey不能为空");
}
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);

return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("aes解密失败", e);
}
}
}

package com.coderbbb.blogv2.utils;

import com.coderbbb.blogv2.database.dto.WxCertDataDTO;
import org.apache.commons.codec.binary.Base64;
import org.springframework.core.io.ClassPathResource;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

/**
* 微信支付证书解析
*
* @author longge93
*/
public class WxCertUtil {

private static WxCertDataDTO wxCertDataDTO = null;

public static String keyPass = null;

public static String sslPath = "ssl/wx2.p12";

private static synchronized WxCertDataDTO loadCert() {
if (wxCertDataDTO != null) {
return wxCertDataDTO;
}

if (keyPass == null) {
throw new RuntimeException("还没有设置证书密码");
}

ClassPathResource classPathResource = new ClassPathResource(sslPath);

String serialNumber;
PublicKey publicKey;
PrivateKey privateKey;
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(classPathResource.getInputStream(), keyPass.toCharArray());

Enumeration<String> aliases = keyStore.aliases();
X509Certificate cert = (X509Certificate) keyStore.getCertificate(aliases.nextElement());

//证书序列号
serialNumber = cert.getSerialNumber().toString(16).toUpperCase();
// 证书公钥
publicKey = cert.getPublicKey();
// 证书私钥
privateKey = (PrivateKey) keyStore.getKey(keyStore.getCertificateAlias(cert), keyPass.toCharArray());
} catch (Exception e) {
throw new RuntimeException("读取证书失败", e);
}

wxCertDataDTO = new WxCertDataDTO();
wxCertDataDTO.setPublicKey(publicKey);
wxCertDataDTO.setPrivateKey(privateKey);
wxCertDataDTO.setSerialNumber(serialNumber);
wxCertDataDTO.setMchId(keyPass);

return wxCertDataDTO;
}

public static WxCertDataDTO getCert() {
if (wxCertDataDTO != null) {
return wxCertDataDTO;
}
return loadCert();
}

public static String rsaSign(String signatureStr){

WxCertDataDTO wxCertDataDTO = getCert();

try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(wxCertDataDTO.getPrivateKey());
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64.encodeBase64String(sign.sign());
} catch (Exception e) {
throw new RuntimeException("RSA签名失败");
}
}
}

源代码使用方法首先,你得pom引入OkHttp和apache-commons-lang3(可能有遗漏,你如果有报错,就自己加一下)<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>然后,把上面依赖的OkHttpUtil、AesUtil、WxCertUtil和WxOkHttpUtil放到一起,把报错的import改一改(因为你和我的包名不一样,肯定会报错)关于证书管理:我的策略是,每次程序启动(或定时任务),都会调用微信的接口,下载微信平台证书,保存到数据库,再保存到静态变量中WxOkHttpUtil类的WX_PLAT_CERT变量。(所以,你使用代码时,应该先调用下载WxOkHttpUtil中下载证书的接口,把证书加载到这个变量中,然后才能正常执行微信下单、退款等等操作)WxCertUtil.keyPass是证书的密码,其实就是微信商户号AesUtil.aesKey 就是前面接入准备中提到的API KEY其他到这一步,微信支付API-V3的各种安全校验问题应该是解决了,专栏下一篇就介绍怎么使用本文封装好的HTTP GET、HTTP POST来完成整个微信支付流程。读者要求的POJO类代码WxCertDataDTOpackage com.coderbbb.book1.database.dto;

import lombok.Data;

import java.security.PrivateKey;
import java.security.PublicKey;

@Data
public class WxCertDataDTO {

/**
* 证书序列号
*/
private String serialNumber;

/**
* 证书公钥
*/
private PublicKey publicKey;

/**
* 证书私钥
*/
private PrivateKey privateKey;

/**
* 商户号
*/
private String mchId;
}WxPlatCertDO,这个就是把证书往数据库存的实体类,继承了一个MybatisBaseDO类(这个类你可以删掉不继承,里面是我封装的数据库主键、时间戳等等通用字段,你按自己的喜好搞)。package com.coderbbb.blogv2.database.dos;

import lombok.Data;

import java.security.cert.X509Certificate;
import java.util.Date;

@Data
public class WxPlatCertDO extends MybatisBaseDO{

/**
* 证书序列号
*/
private String serialNumber;

/**
* 证书过期时间
*/
private Date expireTime;

/**
* 生效时间
*/
private Date effectiveTime;

/**
* X509Certificate JSON序列化
*/
private String cert;

/**
* 证书
*/
private X509Certificate certificate;
}版权声明:《Java Springboot使用OkHttp实现微信支付API-V3签名、证书的管理和使用》为CoderBBB作者「ʘᴗʘ」的原创文章,转载请附上原文出处链接及本声明。原文链接:https://www.coderbbb.com/articles/4

本文出自快速备案,转载时请注明出处及相应链接。

本文永久链接: https://www.175ku.com/27634.html