com.github.binarywang.wxpay.config.WxPayConfig Maven / Gradle / Ivy
The newest version!
package com.github.binarywang.wxpay.config;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.util.HttpProxyUtils;
import com.github.binarywang.wxpay.util.ResourcesUtils;
import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder;
import com.github.binarywang.wxpay.v3.auth.*;
import com.github.binarywang.wxpay.v3.util.PemUtils;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Optional;
import javax.net.ssl.SSLContext;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.ssl.SSLContexts;
/**
* 微信支付配置
*
* @author Binary Wang (...)
*/
@Data
@Slf4j
@ToString(exclude = "verifier")
@EqualsAndHashCode(exclude = "verifier")
public class WxPayConfig {
private static final String DEFAULT_PAY_BASE_URL = "https://api.mch.weixin.qq.com";
private static final String PROBLEM_MSG = "证书文件【%s】有问题,请核实!";
private static final String NOT_FOUND_MSG = "证书文件【%s】不存在,请核实!";
/**
* 微信支付接口请求地址域名部分.
*/
private String payBaseUrl = DEFAULT_PAY_BASE_URL;
/**
* http请求连接超时时间.
*/
private int httpConnectionTimeout = 5000;
/**
* http请求数据读取等待时间.
*/
private int httpTimeout = 10000;
/**
* 公众号appid.
*/
private String appId;
/**
* 服务商模式下的子商户公众账号ID.
*/
private String subAppId;
/**
* 商户号.
*/
private String mchId;
/**
* 商户密钥.
*/
private String mchKey;
/**
* 企业支付密钥.
*/
private String entPayKey;
/**
* 服务商模式下的子商户号.
*/
private String subMchId;
/**
* 微信支付异步回掉地址,通知url必须为直接可访问的url,不能携带参数.
*/
private String notifyUrl;
/**
* 交易类型.
*
* JSAPI--公众号支付
* NATIVE--原生扫码支付
* APP--app支付
*
*/
private String tradeType;
/**
* 签名方式.
* 有两种HMAC_SHA256 和MD5
*
* @see com.github.binarywang.wxpay.constant.WxPayConstants.SignType
*/
private String signType;
private SSLContext sslContext;
/**
* p12证书base64编码
*/
private String keyString;
/**
* p12证书文件的绝对路径或者以classpath:开头的类路径.
*/
private String keyPath;
/**
* apiclient_key.pem证书base64编码
*/
private String privateKeyString;
/**
* apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
*/
private String privateKeyPath;
/**
* apiclient_cert.pem证书base64编码
*/
private String privateCertString;
/**
* apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.
*/
private String privateCertPath;
/**
* apiclient_key.pem证书文件内容的字节数组.
*/
private byte[] privateKeyContent;
/**
* apiclient_cert.pem证书文件内容的字节数组.
*/
private byte[] privateCertContent;
/**
* 公钥ID
*/
private String publicKeyId;
/**
* pub_key.pem证书base64编码
*/
private String publicKeyString;
/**
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
*/
private String publicKeyPath;
/**
* pub_key.pem证书文件内容的字节数组.
*/
private byte[] publicKeyContent;
/**
* apiV3 秘钥值.
*/
private String apiV3Key;
/**
* apiV3 证书序列号值
*/
private String certSerialNo;
/**
* 微信支付分serviceId
*/
private String serviceId;
/**
* 微信支付分回调地址
*/
private String payScoreNotifyUrl;
/**
* 微信支付分授权回调地址
*/
private String payScorePermissionNotifyUrl;
private CloseableHttpClient apiV3HttpClient;
/**
* 支持扩展httpClientBuilder
*/
private HttpClientBuilderCustomizer httpClientBuilderCustomizer;
private HttpClientBuilderCustomizer apiV3HttpClientBuilderCustomizer;
/**
* 私钥信息
*/
private PrivateKey privateKey;
/**
* 证书自动更新时间差(分钟),默认一分钟
*/
private int certAutoUpdateTime = 60;
/**
* p12证书文件内容的字节数组.
*/
private byte[] keyContent;
/**
* 微信支付是否使用仿真测试环境.
* 默认不使用
*/
private boolean useSandboxEnv = false;
/**
* 是否将接口请求日志信息保存到threadLocal中.
* 默认不保存
*/
private boolean ifSaveApiData = false;
private String httpProxyHost;
private Integer httpProxyPort;
private String httpProxyUsername;
private String httpProxyPassword;
/**
* v3接口下证书检验对象,通过改对象可以获取到X509Certificate,进一步对敏感信息加密
* 文档
*/
private Verifier verifier;
/**
* 返回所设置的微信支付接口请求地址域名.
*
* @return 微信支付接口请求地址域名
*/
public String getPayBaseUrl() {
if (StringUtils.isEmpty(this.payBaseUrl)) {
return DEFAULT_PAY_BASE_URL;
}
return this.payBaseUrl;
}
@SneakyThrows
public Verifier getVerifier() {
if (verifier == null) {
//当改对象为null时,初始化api v3的请求头
initApiV3HttpClient();
}
return verifier;
}
/**
* 初始化ssl.
*
* @return the ssl context
* @throws WxPayException the wx pay exception
*/
public SSLContext initSSLContext() throws WxPayException {
if (StringUtils.isBlank(this.getMchId())) {
throw new WxPayException("请确保商户号mchId已设置");
}
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
this.keyContent, "p12证书")) {
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] partnerId2charArray = this.getMchId().toCharArray();
keystore.load(inputStream, partnerId2charArray);
this.sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
return this.sslContext;
} catch (Exception e) {
throw new WxPayException("证书文件有问题,请核实!", e);
}
}
/**
* 初始化api v3请求头 自动签名验签
* 方法参照 微信支付官方api项目
*
* @return org.apache.http.impl.client.CloseableHttpClient
* @author doger.wang
**/
public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
if (StringUtils.isBlank(this.getApiV3Key())) {
throw new WxPayException("请确保apiV3Key值已设置");
}
// 尝试从p12证书中加载私钥和证书
PrivateKey merchantPrivateKey = null;
X509Certificate certificate = null;
Object[] objects = this.p12ToPem();
if (objects != null) {
merchantPrivateKey = (PrivateKey) objects[0];
certificate = (X509Certificate) objects[1];
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
try {
if (merchantPrivateKey == null) {
if (StringUtils.isNotBlank(this.getPrivateKeyString())) {
this.setPrivateKeyString(Base64.getEncoder().encodeToString(this.getPrivateKeyString().getBytes()));
}
try (InputStream keyInputStream = this.loadConfigInputStream(this.getPrivateKeyString(), this.getPrivateKeyPath(),
this.privateKeyContent, "privateKeyPath")) {
merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream);
}
}
if (certificate == null && StringUtils.isBlank(this.getCertSerialNo())) {
try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(),
this.privateCertContent, "privateCertPath")) {
certificate = PemUtils.loadCertificate(certInputStream);
}
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
PublicKey publicKey = null;
if (this.getPublicKeyString() != null || this.getPublicKeyPath() != null || this.publicKeyContent != null) {
try (InputStream pubInputStream =
this.loadConfigInputStream(this.getPublicKeyString(), this.getPublicKeyPath(),
this.publicKeyContent, "publicKeyPath")) {
publicKey = PemUtils.loadPublicKey(pubInputStream);
}
}
//构造Http Proxy正向代理
WxPayHttpProxy wxPayHttpProxy = getWxPayHttpProxy();
Verifier certificatesVerifier;
if (publicKey == null) {
certificatesVerifier =
new AutoUpdateCertificatesVerifier(
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(),
this.getPayBaseUrl(), wxPayHttpProxy);
} else {
certificatesVerifier = new PublicCertificateVerifier(publicKey, publicKeyId);
}
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
.withValidator(new WxPayValidator(certificatesVerifier));
//初始化V3接口正向代理设置
HttpProxyUtils.initHttpProxy(wxPayV3HttpClientBuilder, wxPayHttpProxy);
// 提供自定义wxPayV3HttpClientBuilder的能力
Optional.ofNullable(apiV3HttpClientBuilderCustomizer).ifPresent(e -> {
e.customize(wxPayV3HttpClientBuilder);
});
CloseableHttpClient httpClient = wxPayV3HttpClientBuilder.build();
this.apiV3HttpClient = httpClient;
this.verifier = certificatesVerifier;
this.privateKey = merchantPrivateKey;
return httpClient;
} catch (WxPayException e) {
throw e;
} catch (Exception e) {
throw new WxPayException("v3请求构造异常!", e);
}
}
/**
* 初始化一个WxPayHttpProxy对象
*
* @return 返回封装的WxPayHttpProxy对象。如未指定代理主机和端口,则默认返回null
*/
private WxPayHttpProxy getWxPayHttpProxy() {
if (StringUtils.isNotBlank(this.getHttpProxyHost()) && this.getHttpProxyPort() > 0) {
return new WxPayHttpProxy(getHttpProxyHost(), getHttpProxyPort(), getHttpProxyUsername(), getHttpProxyPassword());
}
return null;
}
private InputStream loadConfigInputStream(String configString, String configPath, byte[] configContent,
String fileName) throws WxPayException {
InputStream inputStream;
if (configContent != null) {
inputStream = new ByteArrayInputStream(configContent);
} else if (StringUtils.isNotEmpty(configString)) {
configContent = configString.getBytes(StandardCharsets.UTF_8);
inputStream = new ByteArrayInputStream(configContent);
} else {
if (StringUtils.isBlank(configPath)) {
throw new WxPayException("请确保证书文件地址【" + fileName + "】或者内容已配置");
}
inputStream = this.loadConfigInputStream(configPath);
}
return inputStream;
}
/**
* 从配置路径 加载配置 信息(支持 classpath、本地路径、网络url)
*
* @param configPath 配置路径
* @return .
* @throws WxPayException .
*/
private InputStream loadConfigInputStream(String configPath) throws WxPayException {
String fileHasProblemMsg = String.format(PROBLEM_MSG, configPath);
String fileNotFoundMsg = String.format(NOT_FOUND_MSG, configPath);
final String prefix = "classpath:";
InputStream inputStream;
if (configPath.startsWith(prefix)) {
String path = RegExUtils.removeFirst(configPath, prefix);
if (!path.startsWith("/")) {
path = "/" + path;
}
try {
inputStream = ResourcesUtils.getResourceAsStream(path);
if (inputStream == null) {
throw new WxPayException(fileNotFoundMsg);
}
return inputStream;
} catch (Exception e) {
throw new WxPayException(fileNotFoundMsg, e);
}
}
if (configPath.startsWith("http://") || configPath.startsWith("https://")) {
try {
inputStream = new URL(configPath).openStream();
if (inputStream == null) {
throw new WxPayException(fileNotFoundMsg);
}
return inputStream;
} catch (IOException e) {
throw new WxPayException(fileNotFoundMsg, e);
}
} else {
try {
File file = new File(configPath);
if (!file.exists()) {
throw new WxPayException(fileNotFoundMsg);
}
//使用Files.newInputStream打开公私钥文件,会存在无法释放句柄的问题
//return Files.newInputStream(file.toPath());
return new FileInputStream(file);
} catch (IOException e) {
throw new WxPayException(fileHasProblemMsg, e);
}
}
}
/**
* 分解p12证书文件
*/
private Object[] p12ToPem() {
String key = getMchId();
if (StringUtils.isBlank(key) ||
(StringUtils.isBlank(this.getKeyPath()) && this.keyContent == null && StringUtils.isBlank(this.keyString))) {
return null;
}
// 分解p12证书文件
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
this.keyContent, "p12证书")) {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(inputStream, key.toCharArray());
String alias = keyStore.aliases().nextElement();
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, key.toCharArray());
Certificate certificate = keyStore.getCertificate(alias);
X509Certificate x509Certificate = (X509Certificate) certificate;
return new Object[]{privateKey, x509Certificate};
} catch (Exception e) {
log.error("加载p12证书时发生异常", e);
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy