package cc.mrbird.febs.pay.service.impl;
|
|
import cc.mrbird.febs.common.properties.XcxProperties;
|
import cc.mrbird.febs.common.utils.SpringContextHolder;
|
import cc.mrbird.febs.pay.service.WxFaPiaoService;
|
import cc.mrbird.febs.pay.util.RandomStringGenerator;
|
import lombok.RequiredArgsConstructor;
|
import lombok.SneakyThrows;
|
import lombok.extern.slf4j.Slf4j;
|
import okhttp3.HttpUrl;
|
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.Base64Utils;
|
|
import java.io.*;
|
import java.nio.charset.StandardCharsets;
|
import java.security.*;
|
import java.security.cert.X509Certificate;
|
import java.util.Map;
|
import java.util.stream.Collectors;
|
import java.util.stream.Stream;
|
|
@Slf4j
|
@Service
|
@RequiredArgsConstructor
|
public class WxFaPiaoServiceImpl implements WxFaPiaoService {
|
|
private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class);
|
|
@Override
|
public String createAuthorization(String method, String canonicalUrl, String body, KeyPair keyPair) {
|
String nonceStr = RandomStringGenerator.getRandomStringByLength(32);//随机字符串
|
long timestamp = System.currentTimeMillis() / 1000;//时间戳
|
String signature = sign(method, canonicalUrl, timestamp, nonceStr, body, keyPair);//签名加密
|
return "mchid=\"" + xcxProperties.getWecharpayMchid() + "\","
|
+ "nonce_str=\"" + nonceStr + "\","
|
+ "timestamp=\"" + timestamp + "\","
|
+ "serial_no=\"" + "50F37206347BCC9E6AC9860DAACE52AC035F7C24" + "\","//证书序列号
|
+ "signature=\"" + signature + "\"";
|
}
|
|
@Override
|
public KeyPair getPrivateKey() {
|
return createPKCS12("Tenpay Certificate", "1658958205");
|
}
|
/**
|
* V3 SHA256withRSA 签名.
|
*
|
* @param method 请求方法 GET POST PUT DELETE 等
|
* @param canonicalUrl 例如 https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1
|
* @param timestamp 当前时间戳 因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
|
* @param nonceStr 随机字符串 要和TOKEN中的保持一致
|
* @param body 请求体 GET 为 "" POST 为JSON
|
* @param keyPair 商户API 证书解析的密钥对 实际使用的是其中的私钥
|
* @return the string
|
*/
|
@SneakyThrows
|
public String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair) {
|
String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
|
.collect(Collectors.joining("\n", "", "\n"));
|
Signature sign = Signature.getInstance("SHA256withRSA");
|
sign.initSign(keyPair.getPrivate());
|
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
|
return Base64Utils.encodeToString(sign.sign());
|
}
|
|
/**
|
* 获取公私钥.通过证书
|
*/
|
private KeyStore store;
|
private final Object lock = new Object();
|
public KeyPair createPKCS12(String keyAlias, String keyPass) {
|
ClassPathResource resource = new ClassPathResource(xcxProperties.getCertLocalPath());
|
// File file = new File("src/main/resources/wxP12/apiclient_cert.p12");
|
char[] pem = keyPass.toCharArray();
|
try {
|
synchronized (lock) {
|
if (store == null) {
|
synchronized (lock) {
|
store = KeyStore.getInstance("PKCS12");
|
store.load(resource.getInputStream(), pem);
|
// store.load(new FileInputStream(file), pem);
|
}
|
}
|
}
|
X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
|
certificate.checkValidity();
|
// 证书的序列号 也有用 50F37206347BCC9E6AC9860DAACE52AC035F7C24
|
String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
|
// 证书的 公钥
|
PublicKey publicKey = certificate.getPublicKey();
|
// 证书的私钥
|
PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);
|
return new KeyPair(publicKey, storeKey);
|
} catch (Exception e) {
|
throw new IllegalStateException("Cannot load keys from store: " , e);
|
}
|
}
|
|
public static void main(String[] args) {
|
try {
|
System.out.println(new ClassPathResource("wxP12/apiclient_cert.p12").getFile().exists());
|
} catch (IOException e) {
|
e.printStackTrace();
|
}
|
}
|
|
}
|