因项目需要,有用到微信支付,这里对java微信支付的开发流程和注意事项到做一次记录,以遍后面有需要的时候翻阅,方便回顾。
快速开发一个功能,需要了解整个开发过程以及开发过程中需要注意的点,开发的功能才能足够安全高效
如下是微信支付官方给出的微信支付V3流程图:
1、首先是接入前准备
具体参考微信支付V3接入前准备:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_1.shtml
2、开发准备
搭建和配置开发环境,根据自身开发语言,选择对应的开发库构建项目,笔者这里使用的是Java,所以使用的是wechatpay-apache-httpclient,只要将对应的maven依赖引入项目即可
com.github.wechatpay-apiv3
wechatpay-apache-httpclient
0.2.1
,最新依赖版本可参考https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
3、API接口列表
需要注意的是接口的适用接入模式,请求参数,接口地址,返回参数一定要跟微信官方文档的一致。
4、接口规则
配置信息
wechat:
appId: ###
mchId: ###
aesKey: ###
unifiedOrder:
url: https://api.mch.weixin.qq.com/v3/pay/transactions/app
notify:
url: ###
queryOrder:
url: https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s
certificates:
url: https://api.mch.weixin.qq.com/v3/certificates
package com.hjy.ft.config;
import com.hjy.pay.WxpayV3Util;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.*;
import java.security.cert.X509Certificate;
/**
* @version 1.0
* @className WeChatStartUpRunner
* @description 程序启动后加载
* @since 2021/4/19 14:47
*/
@Component
@Order(value = 1)
public class WeChatStartUpRunner implements CommandLineRunner {
/**
* 微信商户号
*/
@Value("${wechat.mchId}")
private String wechatMchId;
/**
* 应用id
*/
@Value("${wechat.appId}")
private String wechatAppId;
/**
* API v3密钥
*/
@Value("${wechat.aesKey}")
private String wechatAesKey;
/**
* 统一下单地址
*/
@Value("${wechat.unifiedOrder.url}")
private String unifiedOrderUrl;
/**
* 异步接收微信支付 回调地址
*/
@Value("${wechat.notify.url}")
private String notifyUrl;
/**
* 微信订单查询地址
*/
@Value("${wechat.queryOrder.url}")
private String queryOrderUrl;
/**
* 获取平台证书列表
*/
@Value("${wechat.certificates.url}")
private String certificatesUrl;
@Override
public void run(String... args) throws Exception {
WxpayV3Util.wechatMchId = wechatMchId;
WxpayV3Util.wechatAppId = wechatAppId;
WxpayV3Util.wechatAesKey = wechatAesKey;
WxpayV3Util.unifiedOrderUrl = unifiedOrderUrl;
WxpayV3Util.notifyUrl = notifyUrl;
WxpayV3Util.queryOrderUrl = queryOrderUrl;
X509Certificate certificate = PemUtil.loadCertificate(new FileInputStream(readPayPath("apiclient_cert.pem")));
WxpayV3Util.wechatCertificatePath = certificate;
//获取证书序列号
WxpayV3Util.serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
WxpayV3Util.wechatKeyPath = PemUtil.loadPrivateKey(new FileInputStream(readPayPath("apiclient_key.pem")));
WxpayV3Util.certificatesUrl = certificatesUrl;
//获取平台证书(由于微信证书存在有限期限制,微信支付会不定期更换平台证书以确保交易安全)
WxpayV3Util.certificateMap = WxpayV3Util.refreshCertificate();
}
/**
* 读取文件地址,适用发布环境
* @param fileName (文件路径)
* @return 临时文件路径
*/
public String readPayPath(String fileName) {
String url = null;
//返回读取指定资源的输入流
InputStream is = this.getClass().getResourceAsStream("/wxpay/"+fileName);
//InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("/alipay/"+fileName);
String path = System.getProperty("user.dir");
//create folder
String dirPath = path + File.separator + "uploadWxPayFiles";
File dir = new File(dirPath);
dir.mkdirs();
//create file
String filePath = dirPath + File.separator + fileName;
File file = new File(filePath);
//判断文件是否存在
if (!file.exists()) {
try {
file.createNewFile();
//文件不存,创建流输入数据到新文件
inputStreamToFile(is, file);
} catch (IOException e) {
e.printStackTrace();
}
url = filePath;
}else{
url = filePath;
}
return url;
}
public void inputStreamToFile(InputStream ins, File file) {
OutputStream os = null;
try {
os = new FileOutputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[1024];
while ((bytesRead = ins.read(buffer, 0, 1024)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
ins.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
工具类
package com.hjy.pay;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @version 1.0
* @className WxpayV3Util
* @description TODO
*/
public class WxpayV3Util {
private static Logger logger = LoggerFactory.getLogger(WxpayV3Util.class);
//应用id
public static String wechatAppId;
//商户id
public static String wechatMchId;
//API v3密钥
public static String wechatAesKey;
//统一下单地址
public static String unifiedOrderUrl;
//微信回调地址
public static String notifyUrl;
//订单查询地址
public static String queryOrderUrl;
//证书序列号
public static String serialNo;
//微信证书地址
public static X509Certificate wechatCertificatePath;
//微信证书私钥地址
public static PrivateKey wechatKeyPath;
//平台证书更换
public static String certificatesUrl;
public static Map certificateMap = new ConcurrentHashMap<>();
/**
* 获取请求文体
* @param request
* @return
* @throws IOException
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
/**
* 回调数据解密
* @param associatedData
* @param nonce
* @param ciphertext
* @return
*/
public static String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(wechatAesKey.getBytes("UTF-8"), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes("UTF-8"));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData.getBytes("UTF-8"));
byte[] bytes;
try {
bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException(e);
}
return new String(bytes, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 证书更新
* @return
*/
public static Map refreshCertificate(){
try {
String url = WxpayV3Util.certificatesUrl;
//设置参数
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建实例方法
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Authorization", getToken("GET",url,""));
HttpResponse response = httpClient.execute(httpGet);
String result = "";
if(response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(response.getEntity(),"UTF-8");
}
JSONObject jsonObject = JSONObject.parseObject(result);
List certificateList = JSON.parseArray(jsonObject.getString("data"),CertificateVO.class);
//JSONObject jsonObject = PayRequestUtils.wechatHttpGet(StaticParameter.wechatCertificatesUrl,"", JSONObject.class);
Date newestTime = null;
CertificateVO newestCertificate = null;
for (CertificateVO certificate:certificateList) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
if (newestTime == null){
newestCertificate = certificate;
newestTime = formatter.parse(certificate.getEffective_time());
}else{
Date effectiveTime = formatter.parse(certificate.getEffective_time());
//证书启用时间大于最新时间
if(effectiveTime.getTime() > newestTime.getTime()){
//更换证书
newestCertificate = certificate;
}
}
}
CertificateVO.EncryptCertificate encryptCertificate = newestCertificate.getEncrypt_certificate();
String publicKey = decryptResponseBody(encryptCertificate.getAssociated_data(),encryptCertificate.getNonce(),encryptCertificate.getCiphertext());
CertificateFactory cf = CertificateFactory.getInstance("X509");
//获取证书
ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes("UTF-8"));
X509Certificate certificate = (X509Certificate) cf.generateCertificate(inputStream);
//保存平台证书及序列号
Map certificateMap = new ConcurrentHashMap<>();
// 清理HashMap
certificateMap.clear();
// 放入证书
certificateMap.put(newestCertificate.getSerial_no(), certificate);
return certificateMap;
}catch (Exception e){
logger.error("微信平台证书更新报错:",e);
}
return null;
}
/**
* 签名串
* @param method 请求方式post | get
* @param url app 统一下单API
* @param body 请求内容
*/
public static String getToken(String method, String url,String body){
//时间戳
Long timestamp = System.currentTimeMillis()/1000;
//随机串
String nonceStr = UUID.randomUUID().toString().replace("-","");
//签名值
String signature = getSign(method,url,body,timestamp,nonceStr);
final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
// 生成token
return String.format(TOKEN_PATTERN,
WxpayV3Util.wechatMchId,
nonceStr, timestamp, WxpayV3Util.serialNo, signature);
}
/**
* 对签名串进行SHA256 with RSA签名,并进行Base64编码得到签名值
* @param method 请求方法 GET POST 等
* @param canonicalUrl 请求地址
* @param body 请求体 GET 为 "" POST 为JSON
* @param timestamp 时间戳
* @param nonceStr 随机串
*/
public static String getSign(String method, String canonicalUrl, String body, long timestamp, String nonceStr){
try{
URL url = new URL(canonicalUrl);
String signUrl;
if ("GET".equals(method)&&url.getQuery()!=null) {
signUrl = url.getPath() + "?" + url.getQuery();
}else{
signUrl = url.getPath();
}
//有序切割
String signatureStr = Stream.of(method, signUrl, String.valueOf(timestamp), nonceStr, body)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(WxpayV3Util.wechatKeyPath);
sign.update(signatureStr.getBytes("UTF-8"));
return Base64Utils.encodeToString(sign.sign());
}catch(Exception ex){
logger.error("签名值出错:",ex);
}
return null;
}
}
生成订单信息
@ApiOperation(value = "生成微信支付订单(直连模式)", notes = "生成微信支付订单(直连模式)")
@PostMapping("/get_wechat_pay_v3_order_info")
public JsonResponse wechatPayV3OrderInfo(WechatModel wechatModel) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
//请求URL
HttpPost httpPost = new HttpPost(WxpayV3Util.unifiedOrderUrl);
//订单号
Long orderNumber = 0L;
//订单总额(单位:分)
Integer total = new BigDecimal(wechatModel.getTotalAmount()).multiply(new BigDecimal(100)).intValue();
//业务逻辑处理
//下单对象
Transaction transaction = new Transaction(WxpayV3Util.wechatAppId,WxpayV3Util.wechatMchId,wechatModel.getDescription(), String.valueOf(orderNumber),WxpayV3Util.notifyUrl,total,"CNY");
String jsonStr = JSON.toJSONString(transaction);
httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
httpPost.setHeader("Accept", "application/json");
//Authorization 头:认证类型与签名信息组成 | 请求数据做加密,防止被拦截后改动
String token = WxpayV3Util.getToken("POST",WxpayV3Util.unifiedOrderUrl,jsonStr);
httpPost.setHeader("Authorization", token);
if(null != jsonStr){
StringEntity entity = new StringEntity(jsonStr,"UTF-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
}
//完成签名并执行请求
HttpResponse response = httpClient.execute(httpPost);
//获取返回状态
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
String result = EntityUtils.toString(response.getEntity(),"UTF-8");
JSONObject object = JSONObject.parseObject(result);
//获取预付单
String prepayId = object.getString("prepay_id");
//生成签名
Long timestamp = System.currentTimeMillis()/1000;
String nonceStr = UUID.randomUUID().toString().replace("-","");
String paySign = appPaySign(timestamp,nonceStr,prepayId);
Map payMap = new HashMap();
payMap.put("timestamp",timestamp);
payMap.put("nonceStr",nonceStr);
payMap.put("prepayId",prepayId);
payMap.put("paySign",paySign);
return new JsonResponse(1,"success",payMap);
} else if (statusCode == 204) { //处理成功,无返回Body
return new JsonResponse(1,"success",null);
} else {
return new JsonResponse(1,"error",null);
}
}
/**
* 签名串
* @param method 请求方式post | get
* @param url app 统一下单API
* @param body 请求内容
*/
public static String getToken(String method, String url,String body){
//时间戳
Long timestamp = System.currentTimeMillis()/1000;
//随机串
String nonceStr = UUID.randomUUID().toString().replace("-","");
//签名值
String signature = getSign(method,url,body,timestamp,nonceStr);
final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
// 生成token
return String.format(TOKEN_PATTERN,
WxpayV3Util.wechatMchId,
nonceStr, timestamp, WxpayV3Util.serialNo, signature);
}
/**
* 对签名串进行SHA256 with RSA签名,并进行Base64编码得到签名值
* @param method 请求方法 GET POST PUT DELETE 等
* @param canonicalUrl 请求地址
* @param body 请求体 GET 为 "" POST 为JSON
* @param timestamp 时间戳
* @param nonceStr 随机串
*/
public static String getSign(String method, String canonicalUrl, String body, long timestamp, String nonceStr){
try{
URL url = new URL(canonicalUrl);
String signUrl;
if ("GET".equals(method)&&url.getQuery()!=null) {
signUrl = url.getPath() + "?" + url.getQuery();
}else{
signUrl = url.getPath();
}
//有序切割
String signatureStr = Stream.of(method, signUrl, String.valueOf(timestamp), nonceStr, body)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(WxpayV3Util.wechatKeyPath);
sign.update(signatureStr.getBytes("UTF-8"));
return Base64Utils.encodeToString(sign.sign());
}catch(Exception ex){
logger.error("签名值出错:",ex);
}
return null;
}
/**
* 生成带签名支付信息
* @param timestamp 时间戳
* @param nonceStr 随机数
* @param prepayId 预付单
* @return 支付信息
* @throws Exception
*/
public String appPaySign(long timestamp, String nonceStr, String prepayId) throws Exception {
String signatureStr = Stream.of(WxpayV3Util.wechatAppId, String.valueOf(timestamp), nonceStr, prepayId)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(WxpayV3Util.wechatKeyPath);
sign.update(signatureStr.getBytes("UTF-8"));
return Base64Utils.encodeToString(sign.sign());
}
微信异步通知
@ApiOperation(value = "微信异步通知", notes = "微信异步通知")
@PostMapping(value = "/wechat_callback")
public Map wechatCallback(HttpServletRequest request){
Map map = new HashMap<>(2);
try {
//微信返回的请求体
String body = WxpayV3Util.getRequestBody(request);
//如果验证签名序列号通过
if (verifiedSign(request,body)){
//微信支付通知实体类
PayNotify payNotify = JSONObject.parseObject(body,PayNotify.class);
//如果支付成功
if ("TRANSACTION.SUCCESS".equals(payNotify.getEvent_type())){
//通知资源数据
PayNotify.Resource resource = payNotify.getResource();
//解密后资源数据
String notifyResourceStr = WxpayV3Util.decryptResponseBody(resource.getAssociated_data(),resource.getNonce(),resource.getCiphertext());
//通知资源数据对象
NotifyResource notifyResourceVO = JSONObject.parseObject(notifyResourceStr,NotifyResource.class);
String outTradeNo = notifyResourceVO.getOut_trade_no();
//逻辑实现
//do something
}else{
logger.info("微信返回支付错误摘要:{}",payNotify.getSummary());
//updateStatus(outTradeNo,RechargeUtil.FAIL_PAY);
}
//通知微信正常接收到消息,否则微信会轮询该接口
map.put("code","SUCCESS");
map.put("message","成功");
return map;
}
} catch (IOException e) {
logger.error("微信回调错误:"+e);
}
return map;
}
/**
* 获取请求报文
* @param request
* @return
* @throws IOException
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
/**
* 验签
* @param request request请求
* @param body 微信返回请求体
* @return true,false
*/
public boolean verifiedSign(HttpServletRequest request,String body){
try{
//微信返回的证书序列号
String serialNo = request.getHeader("Wechatpay-Serial");
//微信返回的随机字符串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信返回的时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//微信返回的签名
String wechatSign = request.getHeader("Wechatpay-Signature");
//组装签名字符串
String signStr = Stream.of(timestamp, nonceStr, body)
.collect(Collectors.joining("\n", "", "\n"));
//判断证书序列号是否相同
if(WxpayV3Util.certificateMap.isEmpty() || !WxpayV3Util.certificateMap.containsKey(serialNo)){
//刷新证书
WxpayV3Util.refreshCertificate();
}
X509Certificate certificate = WxpayV3Util.certificateMap.get(serialNo);
//获取失败 验证失败
if (certificate == null){
return false;
}
//SHA256withRSA签名
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(certificate);
signature.update(signStr.getBytes());
//返回验签结果
return signature.verify(Base64Utils.decodeFromString(wechatSign));
}catch (Exception ex){
logger.error("验签报错:",ex);
}
return false;
}
/**
* 解密
* @param associatedData 附加数据包
* @param nonce 加密使用的随机串
* @param ciphertext Base64编码后的密文
* @return
*/
public static String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(wechatAesKey.getBytes("UTF-8"), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes("UTF-8"));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData.getBytes("UTF-8"));
byte[] bytes;
try {
bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException(e);
}
return new String(bytes, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
订单查询:
@ApiOperation(value = "商户订单号查询", notes = "商户订单号查询")
@GetMapping(value = "/query_transactions_out_trade_no")
public JsonResponse queryTransactionsOutTradeNo(String outTradeNo){
String result = "";
try{
String url = String.format(WxpayV3Util.queryOrderUrl,outTradeNo,WxpayV3Util.wechatMchId);
//设置参数
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建实例方法
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Authorization", WxpayV3Util.getToken("GET",url,""));
HttpResponse response = httpClient.execute(httpGet);
//状态码为200,为正常返回
if(response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(response.getEntity(),"UTF-8");
}
if(StringUtils.isNotEmpty(result)){
JSONObject jsonObject = JSONObject.parseObject(result);
String tradeState = jsonObject.getString("trade_state");
String tradeStateDesc = jsonObject.getString("trade_state_desc");
return new JsonResponse(1,tradeState,tradeStateDesc);
}
}catch(Exception ex){
logger.error("商户订单号查询错误",ex);
}
return new JsonResponse(1,"NOTFOUND","没有找到该订单");
}
证书更新:
/**
* 证书更新
* @return
*/
public static Map refreshCertificate(){
try {
String url = WxpayV3Util.certificatesUrl;
//设置参数
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建实例方法
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Authorization", getToken("GET",url,""));
HttpResponse response = httpClient.execute(httpGet);
String result = "";
if(response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(response.getEntity(),"UTF-8");
}
JSONObject jsonObject = JSONObject.parseObject(result);
List certificateList = JSON.parseArray(jsonObject.getString("data"),CertificateVO.class);
//JSONObject jsonObject = PayRequestUtils.wechatHttpGet(StaticParameter.wechatCertificatesUrl,"", JSONObject.class);
Date newestTime = null;
CertificateVO newestCertificate = null;
for (CertificateVO certificate:certificateList) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
if (newestTime == null){
newestCertificate = certificate;
newestTime = formatter.parse(certificate.getEffective_time());
}else{
Date effectiveTime = formatter.parse(certificate.getEffective_time());
//证书启用时间大于最新时间
if(effectiveTime.getTime() > newestTime.getTime()){
//更换证书
newestCertificate = certificate;
}
}
}
CertificateVO.EncryptCertificate encryptCertificate = newestCertificate.getEncrypt_certificate();
String publicKey = decryptResponseBody(encryptCertificate.getAssociated_data(),encryptCertificate.getNonce(),encryptCertificate.getCiphertext());
CertificateFactory cf = CertificateFactory.getInstance("X509");
//获取证书
ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes("UTF-8"));
X509Certificate certificate = (X509Certificate) cf.generateCertificate(inputStream);
//保存平台证书及序列号
Map certificateMap = new ConcurrentHashMap<>();
// 清理HashMap
certificateMap.clear();
// 放入证书
certificateMap.put(newestCertificate.getSerial_no(), certificate);
return certificateMap;
}catch (Exception e){
logger.error("微信平台证书更新报错:",e);
}
return null;
}
遇到的问题:
前端使用的是uniapp,经调试,支付成功或者失败,微信支付返回的只有状态信息,没有包含支付信息,那就不能验证微信支付Api v3流程图中19步骤的流程,只能直接跳转到支付成功或者支付失败页面。
解决方法:后端定时查询商户待支付订单记录,验证支付订单状态
参考地址:https://blog.csdn.net/qq_39706128/article/details/111558994