2.14.9 微信公钥证书支持,新增pem证书支持

This commit is contained in:
egan
2025-08-14 10:39:30 +08:00
parent c9fb140bcd
commit 3e2d05a769
23 changed files with 328 additions and 227 deletions

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>pay-java-parent</artifactId>
<groupId>com.egzosn</groupId>
<version>2.14.8</version>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pay-java-wx</artifactId>

View File

@@ -1,17 +1,5 @@
package com.egzosn.pay.wx.v3.api;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.util.Map;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.message.BasicHeader;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@@ -30,6 +18,17 @@ import com.egzosn.pay.wx.bean.WxPayError;
import com.egzosn.pay.wx.v3.bean.WxTransactionType;
import com.egzosn.pay.wx.v3.utils.AntCertificationUtil;
import com.egzosn.pay.wx.v3.utils.WxConst;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.message.BasicHeader;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.util.Map;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
/**
* 默认的微信支付辅助服务
@@ -151,7 +150,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService {
//签名信息
String signText = StringUtils.joining("\n", method, canonicalUrl, String.valueOf(timestamp), nonceStr, body);
String sign = wxPayService.createSign(signText, payConfigStorage.getInputCharset());
String serialNumber = payConfigStorage.getCertEnvironment().getSerialNumber();
String serialNumber = payConfigStorage.getCertEnvironment().getMerchantSerialNumber();
// 生成token
String token = String.format(WxConst.TOKEN_PATTERN, payConfigStorage.getMchId(), nonceStr, timestamp, serialNumber, sign);
HttpStringEntity entity = new HttpStringEntity(body, ContentType.APPLICATION_JSON);
@@ -170,6 +169,14 @@ public class DefaultWxPayAssistService implements WxPayAssistService {
*/
@Override
public void refreshCertificate() {
if (null != payConfigStorage.getCertEnvironment().getPublicKey()){
return;
}
if (StringUtils.isNotEmpty(payConfigStorage.getPlatformCertificate()) && null == payConfigStorage.getCertEnvironment().getPublicKey()) {
AntCertificationUtil.loadCertificate(payConfigStorage.getPlatformSerialNumber(), new ByteArrayInputStream(payConfigStorage.getPlatformCertificate().getBytes(StandardCharsets.UTF_8)));
return;
}
JSONObject responseEntity = doExecute("", WxTransactionType.CERT);
if (null == responseEntity) {

View File

@@ -1,16 +1,20 @@
package com.egzosn.pay.wx.v3.api;
import java.io.IOException;
import java.io.InputStream;
import com.egzosn.pay.common.api.BasePayConfigStorage;
import com.egzosn.pay.common.bean.CertStoreType;
import com.egzosn.pay.common.bean.result.PayException;
import com.egzosn.pay.common.exception.PayErrorException;
import com.egzosn.pay.common.util.str.StringUtils;
import com.egzosn.pay.wx.v3.bean.CertEnvironment;
import com.egzosn.pay.wx.v3.utils.AntCertificationUtil;
import com.egzosn.pay.wx.v3.utils.WxConst;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
/**
* 微信配置存储
*
@@ -55,14 +59,26 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
* V2 Api密钥
*/
private String apiKey;
/**
* 微信支付V3 Api密钥
*/
private String v3ApiKey;
/**
* 商户API证书
* 包含商户的商户号、公司名称、公钥信息
* 详情 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml
*/
@Deprecated
private Object apiClientKeyP12;
private String merchantSerialNumber;
private String platformCertificate;
private String platformSerialNumber;
/**
* 证书存储类型
*/
@@ -134,16 +150,20 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
addAttr("apiKey", apiKey);
}
public void setV3ApiKey(String v3ApiKey) {
setKeyPrivate(v3ApiKey);
}
/**
* 为商户平台设置的密钥key
*
* @return 微信v3密钥
*/
public String getV3ApiKey() {
return getKeyPrivate();
return v3ApiKey;
}
public void setV3ApiKey(String v3ApiKey) {
this.v3ApiKey = v3ApiKey;
}
public String getMerchantSerialNumber() {
return merchantSerialNumber;
}
public void setMerchantSerialNumber(String merchantSerialNumber) {
this.merchantSerialNumber = merchantSerialNumber;
}
public void setAppId(String appId) {
@@ -239,6 +259,22 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
return certEnvironment;
}
public String getPlatformCertificate() {
return platformCertificate;
}
public void setPlatformCertificate(String platformCertificate) {
this.platformCertificate = platformCertificate;
}
public String getPlatformSerialNumber() {
return platformSerialNumber;
}
public void setPlatformSerialNumber(String platformSerialNumber) {
this.platformSerialNumber = platformSerialNumber;
}
public void setCertEnvironment(CertEnvironment certEnvironment) {
this.certEnvironment = certEnvironment;
}
@@ -250,12 +286,27 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
if (null != this.certEnvironment) {
return;
}
try (InputStream apiKeyCert = certStoreType.getInputStream(getApiClientKeyP12())) {
this.certEnvironment = AntCertificationUtil.initCertification(apiKeyCert, WxConst.CERT_ALIAS, getMchId());
}
catch (IOException e) {
throw new PayErrorException(new PayException("读取证书异常", e.getMessage()));
if (null != getApiClientKeyP12()) {
try (InputStream apiKeyCert = certStoreType.getInputStream(getApiClientKeyP12())) {
this.certEnvironment = AntCertificationUtil.initCertification(apiKeyCert, WxConst.CERT_ALIAS, getMchId());
} catch (IOException e) {
throw new PayErrorException(new PayException("读取证书异常", e.getMessage()));
}
} else if (null != getKeyPrivate()) {
this.certEnvironment = AntCertificationUtil.initCertification(getKeyPrivate(), getMerchantSerialNumber(), getKeyPublic(), getKeyPublicId());
if (StringUtils.isNotEmpty(getPlatformCertificate())) {
Certificate certificate = AntCertificationUtil.loadCertificate(getPlatformSerialNumber(), new ByteArrayInputStream(getPlatformCertificate().getBytes(StandardCharsets.UTF_8)));
this.certEnvironment.setPlatformSerialNumber(getPlatformSerialNumber());
this.certEnvironment.setPublicKey(certificate.getPublicKey());
}
}
}
public boolean isPartner() {

View File

@@ -1,46 +1,10 @@
package com.egzosn.pay.wx.v3.api;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;
import static com.egzosn.pay.wx.api.WxConst.OUT_TRADE_NO;
import static com.egzosn.pay.wx.api.WxConst.SANDBOXNEW;
import static com.egzosn.pay.wx.v3.utils.WxConst.FAILURE;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.egzosn.pay.common.api.BasePayService;
import com.egzosn.pay.common.api.TransferService;
import com.egzosn.pay.common.bean.AssistOrder;
import com.egzosn.pay.common.bean.BillType;
import com.egzosn.pay.common.bean.CurType;
import com.egzosn.pay.common.bean.MethodType;
import com.egzosn.pay.common.bean.NoticeParams;
import com.egzosn.pay.common.bean.NoticeRequest;
import com.egzosn.pay.common.bean.OrderParaStructure;
import com.egzosn.pay.common.bean.PayMessage;
import com.egzosn.pay.common.bean.PayOrder;
import com.egzosn.pay.common.bean.PayOutMessage;
import com.egzosn.pay.common.bean.RefundOrder;
import com.egzosn.pay.common.bean.RefundResult;
import com.egzosn.pay.common.bean.TransactionType;
import com.egzosn.pay.common.bean.TransferOrder;
import com.egzosn.pay.common.bean.*;
import com.egzosn.pay.common.bean.result.PayException;
import com.egzosn.pay.common.exception.PayErrorException;
import com.egzosn.pay.common.http.HttpConfigStorage;
@@ -70,6 +34,21 @@ import com.egzosn.pay.wx.v3.bean.response.WxRefundResult;
import com.egzosn.pay.wx.v3.bean.transfer.TransferDetail;
import com.egzosn.pay.wx.v3.utils.AntCertificationUtil;
import com.egzosn.pay.wx.v3.utils.WxConst;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.*;
import java.util.stream.Collectors;
import static com.egzosn.pay.wx.api.WxConst.OUT_TRADE_NO;
import static com.egzosn.pay.wx.api.WxConst.SANDBOXNEW;
import static com.egzosn.pay.wx.v3.utils.WxConst.FAILURE;
/**
* 微信支付服务
@@ -133,6 +112,7 @@ public class WxPayService extends BasePayService<WxPayConfigStorage> implements
if (null == assistService) {
assistService = new DefaultWxPayAssistService(this);
}
if (StringUtils.isEmpty(payConfigStorage.getKeyPublic())) {
//在这预先进行初始化
assistService.refreshCertificate();

View File

@@ -26,7 +26,7 @@ public class CertEnvironment {
/**
* 公钥序列
*/
private String serialNumber;
private String merchantSerialNumber;
/**
* 微信平台证书序列号
@@ -37,10 +37,17 @@ public class CertEnvironment {
public CertEnvironment() {
}
public CertEnvironment(PrivateKey privateKey, PublicKey publicKey, String serialNumber) {
public CertEnvironment(PrivateKey privateKey, PublicKey publicKey, String merchantSerialNumber) {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.serialNumber = serialNumber;
this.merchantSerialNumber = merchantSerialNumber;
}
public CertEnvironment(PrivateKey privateKey, String merchantSerialNumber, PublicKey publicKey, String publicSerialNumber) {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.merchantSerialNumber = merchantSerialNumber;
this.platformSerialNumber = publicSerialNumber;
}
public PrivateKey getPrivateKey() {
@@ -59,17 +66,17 @@ public class CertEnvironment {
this.publicKey = publicKey;
}
public String getSerialNumber() {
return serialNumber;
public String getMerchantSerialNumber() {
return merchantSerialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
public void setMerchantSerialNumber(String merchantSerialNumber) {
this.merchantSerialNumber = merchantSerialNumber;
}
public String getPlatformSerialNumber() {
if (StringUtils.isEmpty(platformSerialNumber)) {
setPlatformSerialNumber(serialNumber);
setPlatformSerialNumber(merchantSerialNumber);
}
return platformSerialNumber;
}

View File

@@ -1,16 +1,22 @@
package com.egzosn.pay.wx.v3.utils;
import com.egzosn.pay.common.exception.PayErrorException;
import com.egzosn.pay.common.util.sign.SignUtils;
import com.egzosn.pay.common.util.sign.encrypt.Base64;
import com.egzosn.pay.common.util.sign.encrypt.RSA;
import com.egzosn.pay.common.util.str.StringUtils;
import com.egzosn.pay.wx.bean.WxPayError;
import com.egzosn.pay.wx.v3.bean.CertEnvironment;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.management.openmbean.InvalidKeyException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -18,17 +24,6 @@ import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.management.openmbean.InvalidKeyException;
import com.egzosn.pay.common.exception.PayErrorException;
import com.egzosn.pay.common.util.sign.SignUtils;
import com.egzosn.pay.common.util.sign.encrypt.Base64;
import com.egzosn.pay.wx.bean.WxPayError;
import com.egzosn.pay.wx.v3.bean.CertEnvironment;
/**
* 证书文件可信校验
*
@@ -46,29 +41,31 @@ public final class AntCertificationUtil {
private AntCertificationUtil() {
}
private static final KeyStore PKCS12_KEY_STORE;
private static KeyStore PKCS12_KEY_STORE;
private static final CertificateFactory CERTIFICATE_FACTORY;
private static CertificateFactory CERTIFICATE_FACTORY;
static {
String javaVersion = System.getProperty("java.version");
if (javaVersion.contains("1.8") || javaVersion.startsWith("8")) {
init();
}
public synchronized static void init() {
if (null == PKCS12_KEY_STORE && null == CERTIFICATE_FACTORY) {
Security.setProperty("crypto.policy", "unlimited");
}
SignUtils.initBc();
try {
PKCS12_KEY_STORE = KeyStore.getInstance("PKCS12");
}
catch (KeyStoreException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, " keystore 初始化失败"), e);
SignUtils.initBc();
try {
PKCS12_KEY_STORE = KeyStore.getInstance("PKCS12");
} catch (KeyStoreException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, " keystore 初始化失败"), e);
}
try {
CERTIFICATE_FACTORY = CertificateFactory.getInstance("X509", WxConst.BC_PROVIDER);
} catch (NoSuchProviderException | CertificateException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, " keystore 初始化失败"), e);
}
}
try {
CERTIFICATE_FACTORY = CertificateFactory.getInstance("X509", WxConst.BC_PROVIDER);
}
catch (NoSuchProviderException | CertificateException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, " keystore 初始化失败"), e);
}
}
@@ -85,8 +82,7 @@ public final class AntCertificationUtil {
Certificate certificate = CERTIFICATE_FACTORY.generateCertificate(certificateStream);
CERTIFICATE_MAP.put(serialNo, certificate);
return certificate;
}
catch (CertificateException e) {
} catch (CertificateException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, " 在生成微信v3证书时发生错误原因是" + e.getMessage()), e);
}
@@ -103,6 +99,36 @@ public final class AntCertificationUtil {
}
/**
* 初始化证书信息
*
* @param privateKey 商户API私钥
* @param merchantSerialNumber 商户证书序列号
* @param publicKey 商户API证书公钥
* @param publicKeyId 商户API证书公钥ID
* @return 证书信息集合
*/
public static CertEnvironment initCertification(String privateKey, String merchantSerialNumber, String publicKey, String publicKeyId) {
try {
privateKey = privateKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");
PrivateKey privateKeyObj = RSA.getPrivateKey(privateKey);
PublicKey publicKeyObj = null;
if (StringUtils.isNotEmpty(publicKey)) {
publicKey = publicKey.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").replaceAll("\\s+", "");
publicKeyObj = RSA.getPublicKey(publicKey);
}
return new CertEnvironment(privateKeyObj, merchantSerialNumber, publicKeyObj, publicKeyId);
} catch (GeneralSecurityException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, "获取公私钥失败"), e);
} catch (IOException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, "私钥证书流加载失败"), e);
}
}
/**
* 获取公私钥.
*
@@ -112,7 +138,6 @@ public final class AntCertificationUtil {
* @return 证书信息集合
*/
public static CertEnvironment initCertification(InputStream keyCertStream, String keyAlias, String keyPass) {
char[] pem = keyPass.toCharArray();
try {
PKCS12_KEY_STORE.load(keyCertStream, pem);
@@ -122,14 +147,11 @@ public final class AntCertificationUtil {
PublicKey publicKey = certificate.getPublicKey();
PrivateKey privateKey = (PrivateKey) PKCS12_KEY_STORE.getKey(keyAlias, pem);
return new CertEnvironment(privateKey, publicKey, serialNumber);
}
catch (InvalidKeyException e) {
} catch (InvalidKeyException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, "获取公私钥失败, 解决方式替换jre包local_policy.jarUS_export_policy.jar"), e);
}
catch (GeneralSecurityException e) {
} catch (GeneralSecurityException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, "获取公私钥失败"), e);
}
catch (IOException e) {
} catch (IOException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, "私钥证书流加载失败"), e);
}
@@ -156,8 +178,7 @@ public final class AntCertificationUtil {
cipher.updateAAD(associatedData.getBytes(Charset.forName(characterEncoding)));
byte[] bytes = cipher.doFinal(Base64.decode(cipherText));
return new String(bytes, Charset.forName(characterEncoding));
}
catch (GeneralSecurityException e) {
} catch (GeneralSecurityException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, e.getMessage()), e);
}
}
@@ -189,8 +210,7 @@ public final class AntCertificationUtil {
byte[] cipherData = cipher.doFinal(data);
return Base64.encode(cipherData);
}
catch (GeneralSecurityException e) {
} catch (GeneralSecurityException e) {
throw new PayErrorException(new WxPayError(WxConst.FAILURE, e.getMessage()), e);
}
}

View File

@@ -1,4 +1,3 @@
import com.egzosn.pay.common.bean.CertStoreType;
import com.egzosn.pay.common.bean.MethodType;
import com.egzosn.pay.common.bean.PayOrder;
@@ -10,7 +9,10 @@ import com.egzosn.pay.wx.bean.WxSendredpackType;
import com.egzosn.pay.wx.bean.WxTransactionType;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.UUID;
@@ -24,7 +26,35 @@ import java.util.UUID;
*/
public class PayTest {
public static void main(String[] args) {
public static void main(String[] args) throws IOException {
com.egzosn.pay.wx.v3.api.WxPayConfigStorage wxPayConfigStorage = new com.egzosn.pay.wx.v3.api.WxPayConfigStorage();
wxPayConfigStorage.setAppId("wx5ce9f1a2****");
wxPayConfigStorage.setMchId("170330*****");
//V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml
wxPayConfigStorage.setV3ApiKey("KDBX2tbrKi9eWFEZ*****");
//验签、转账等接口使用9月份开始不允许获取证书方式了直接通过公钥字符来做
wxPayConfigStorage.setPlatformCertificate(Files.readString(Paths.get("wechatpay//wechatpay_72C2EF0EE5095C6D************.pem")));
wxPayConfigStorage.setPlatformSerialNumber("72C2EF0EE5095C6D************");
wxPayConfigStorage.setNotifyUrl("https://pay.egzosn.com/wxV3/payBack.json");
wxPayConfigStorage.setReturnUrl("https://pay.egzosn.com/wxV3/payBack.json");
wxPayConfigStorage.setInputCharset("utf-8");
//使用证书时设置为true
// wxPayConfigStorage.setCertSign(true);
//商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml
// wxPayConfigStorage.setApiClientKeyP12("http://pay.egzosn.com/yifenli_mall.p12");
// wxPayConfigStorage.setCertStoreType(CertStoreType.URL);
wxPayConfigStorage.setKeyPrivate(Files.readString(Paths.get("wechatpay/apiclient_key.pem")));
wxPayConfigStorage.setMerchantSerialNumber("2C1230A7BA8C7B197FC90852CCA****");
com.egzosn.pay.wx.v3.api.WxPayService service = new com.egzosn.pay.wx.v3.api.WxPayService(wxPayConfigStorage);
//微信海外支付:东南亚
// service.setApiServerUrl("https://apihk.mch.weixin.qq.com");
String qrPay = service.getQrPay(new PayOrder("测试订单", "测试商品", BigDecimal.valueOf(0.01), "2018091011111111"));
//设置回调消息处理
//TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack}
System.out.println();
}
public static void mainV2() {
WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage();
wxPayConfigStorage.setAppId("公众账号ID");