KKSU
2024-01-17 94278307c7bb6fb9e47650e73691f04a54513854
fapiao
3 files added
3 files modified
320 ■■■■■ changed files
src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java 3 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPCertificateVo.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPCertificates.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPEncryptCertificate.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java 3 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java 280 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java
@@ -42,6 +42,7 @@
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -253,7 +254,7 @@
     */
    @Transactional(rollbackFor = Exception.class)
    @RequestMapping(value = "/fapiaoCallBack")
    public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
    public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, ParseException {
        return wxFaPiaoService.fapiaoCallBack(request,requestBody);
    }
src/main/java/cc/mrbird/febs/pay/model/FPCertificateVo.java
New file
@@ -0,0 +1,10 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
import java.util.List;
@Data
public class FPCertificateVo {
    private List<FPCertificates> data;
}
src/main/java/cc/mrbird/febs/pay/model/FPCertificates.java
New file
@@ -0,0 +1,13 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
import java.util.List;
@Data
public class FPCertificates {
    private String serial_no;
    private String effective_time;
    private String expire_time;
    private FPEncryptCertificate encrypt_certificate;
}
src/main/java/cc/mrbird/febs/pay/model/FPEncryptCertificate.java
New file
@@ -0,0 +1,11 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
@Data
public class FPEncryptCertificate {
    private String algorithm;
    private String nonce;
    private String associated_data;
    private String ciphertext;
}
src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java
@@ -10,6 +10,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.text.ParseException;
import java.util.Map;
public interface WxFaPiaoService {
@@ -22,5 +23,5 @@
    String sendPost(String url, String params, String token);
    Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException;
    Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, ParseException;
}
src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java
@@ -5,6 +5,9 @@
import cc.mrbird.febs.common.utils.SpringContextHolder;
import cc.mrbird.febs.mall.entity.MallOrderInfo;
import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
import cc.mrbird.febs.pay.model.FPCertificateVo;
import cc.mrbird.febs.pay.model.FPCertificates;
import cc.mrbird.febs.pay.model.FPEncryptCertificate;
import cc.mrbird.febs.pay.model.HeaderDto;
import cc.mrbird.febs.pay.service.WxFaPiaoService;
import cc.mrbird.febs.pay.util.RandomStringGenerator;
@@ -50,8 +53,10 @@
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -210,7 +215,7 @@
    }
    @Override
    public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
    public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, ParseException {
        Map<String,Object> map = new HashMap<>();
        String signature = request.getHeader("Wechatpay-Signature");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
@@ -224,20 +229,9 @@
        log.info("头信息---平台证书序列号:" + serial);
        log.info("获取到的body信息:" + body);
        //验签
        boolean signCheck = verifySign(request, body);
//        boolean signCheck = signCheck(timestamp, nonce, requestBody, signature);
        boolean signCheck = signCheck(timestamp, nonce, requestBody, signature);
        log.info("验签结果:" + signCheck);
        if (signCheck) {
//            //解密参数
//            Resource resource = com.alibaba.fastjson.JSONObject.parseObject(com.alibaba.fastjson.JSONObject.toJSONString(requestBody.get("resource")), Resource.class);
//            AesUtil aesUtil = new AesUtil(CommonParameters.apiV3Key.getBytes("utf-8"));
//            String string = aesUtil.decryptToString(resource.getAssociated_data().getBytes("utf-8"), resource.getNonce().getBytes("utf-8"), resource.getCiphertext());
//            ComplaintInfo complaintInfo = JSONObject.parseObject(string, ComplaintInfo.class);
//            //获取投诉详情
//            ComplaintDetail complaintDetail = CommonUtils.GetComplaintsInfo(complaintInfo.getComplaint_id());
//            data.put("code", "SUCCESS");
//            data.put("message", "成功");
//            return data;
            try {
                //解析请求体
//            Resource resource = com.alibaba.fastjson.JSONObject.parseObject(com.alibaba.fastjson.JSONObject.toJSONString(requestBody.get("resource")), Resource.class);
@@ -287,198 +281,102 @@
        return map;
    }
//    /**
//     * 验证签名
//     *
//     * @param timestamp   微信平台传入的时间戳
//     * @param nonce       微信平台传入的随机字符串
//     * @param requestBody 微信平台传入的消息体
//     * @param signature   微信平台传入的签名
//     * @return
//     * @throws NoSuchAlgorithmException
//     * @throws SignatureException
//     * @throws IOException
//     * @throws InvalidKeyException
//     */
//    public static boolean signCheck(String timestamp, String nonce, Map<String, Object> requestBody, String signature) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
//        //构造验签名串
//        String signatureStr = timestamp + "\n" + nonce + "\n" + com.alibaba.fastjson.JSONObject.toJSONString(requestBody) + "\n";
//        // 加载SHA256withRSA签名器
//        Signature signer = Signature.getInstance("SHA256withRSA");
//        // 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法)
//        signer.initVerify(getCertificates());
//        // 把我们构造的验签名串更新到签名器中
//        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
//        // 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证
//        boolean result = signer.verify(Base64Utils.decodeFromString(signature));
//        return result;
//    }
    /**
     * 验证签名
     *
     * @param timestamp   微信平台传入的时间戳
     * @param nonce       微信平台传入的随机字符串
     * @param requestBody 微信平台传入的消息体
     * @param signature   微信平台传入的签名
     * @return
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws IOException
     * @throws InvalidKeyException
     */
    public boolean signCheck(String timestamp, String nonce, Map<String, Object> requestBody, String signature) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException, ParseException {
        //构造验签名串
        String signatureStr = timestamp + "\n" + nonce + "\n" + com.alibaba.fastjson.JSONObject.toJSONString(requestBody) + "\n";
        // 加载SHA256withRSA签名器
        Signature signer = Signature.getInstance("SHA256withRSA");
        // 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法)
        signer.initVerify(getCertificates());
        // 把我们构造的验签名串更新到签名器中
        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        // 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证
        boolean result = signer.verify(Base64Utils.decodeFromString(signature));
        return result;
    }
    /**
     * 获取平台证书
     */
//    public static X509Certificate getCertificates() throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
//        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
//        CloseableHttpClient httpClient = CommonUtils.httpClient();
//        //请求URL
//        HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates");
//        httpGet.setHeader("Accept", "application/json");
//        //生成签名
//        httpGet.setHeader("Authorization ", SignUtils.getSign("GET", HttpUrl.parse("https://api.mch.weixin.qq.com/v3/certificates"), ""));
//        httpGet.setHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");
//        //完成签名并执行请求
//        CloseableHttpResponse response = httpClient.execute(httpGet);
//        X509Certificate x509Certificate = null;
//        try {
//            int statusCode = response.getStatusLine().getStatusCode();
//            if (statusCode == 200) { //处理成功
////                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
//                CertificateVo certificateVo = JSONObject.parseObject(EntityUtils.toString(response.getEntity()), CertificateVo.class);
//                for (Certificates certificates : certificateVo.getData()) {
//                    if (format.parse(certificates.getEffective_time()).before(new Date()) && format.parse(certificates.getExpire_time()).after(new Date())) {
//                        EncryptCertificate encrypt_certificate = certificates.getEncrypt_certificate();
//                        //解密
//                        AesUtil aesUtil = new AesUtil(CommonParameters.apiV3Key.getBytes("utf-8"));
//                        String pulicKey = aesUtil.decryptToString(encrypt_certificate.getAssociated_data().getBytes("utf-8"), encrypt_certificate.getNonce().getBytes("utf-8"), encrypt_certificate.getCiphertext());
//
//               //获取平台证书
//                        final CertificateFactory cf = CertificateFactory.getInstance("X509");
//
//                        ByteArrayInputStream inputStream = new ByteArrayInputStream(pulicKey.getBytes(StandardCharsets.UTF_8));
//
//                        x509Certificate = (X509Certificate) cf.generateCertificate(inputStream);
//                    }
//                }
//                return x509Certificate;
//            } else if (statusCode == 204) { //处理成功,无返回Body
//                System.out.println("success");
//                return x509Certificate;
//            } else {
//                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
//                return x509Certificate;
//            }
//        } catch (GeneralSecurityException | ParseException e) {
//            e.printStackTrace();
//            return null;
//        } finally {
//            response.close();
//            CommonUtils.after(httpClient);
//        }
//    }
    public X509Certificate getCertificates() throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException, ParseException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        CloseableHttpClient httpClient = HttpClients.createDefault();
    /**
     * 功能描述: 验证签名
     * 注意:使用微信支付平台公钥验签
     * Wechatpay-Signature 微信返签名
     * Wechatpay-Serial 微信平台证书序列号
     *
     * @return java.lang.String
     * @author 影子
     */
    @SneakyThrows
    public boolean verifySign(HttpServletRequest request,String body) {
        boolean verify = false;
        PrivateKey privateKey = this.getPrivateKeyV3();
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/certificates";
        String postStr = null;
        try {
            String wechatPaySignature = request.getHeader("Wechatpay-Signature");
            String wechatPayTimestamp = request.getHeader("Wechatpay-Timestamp");
            String wechatPayNonce = request.getHeader("Wechatpay-Nonce");
            String wechatPaySerial = request.getHeader("Wechatpay-Serial");
            //组装签名串
            String signStr = Stream.of(wechatPayTimestamp, wechatPayNonce, body)
                    .collect(Collectors.joining("\n", "", "\n"));
            //获取平台证书
            AutoUpdateCertificatesVerifier verifier = getVerifier(wechatPaySerial);
            //获取失败 验证失败
            if (verifier != null) {
                Signature signature = Signature.getInstance("SHA256withRSA");
                signature.initVerify(verifier.getValidCertificate());
                //放入签名串
                signature.update(signStr.getBytes(StandardCharsets.UTF_8));
                verify = signature.verify(Base64.getDecoder().decode(wechatPaySignature.getBytes()));
            }
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            postStr = this.createAuthorization(
                    "GET",
                    baseUrl+canonicalUrl,
                    "",
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return verify;
    }
    /**
     * 保存微信平台证书
     */
    private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>();
    /**
     * 功能描述:获取平台证书,自动更新
     * 注意:这个方法内置了平台证书的获取和返回值解密
     */
    public AutoUpdateCertificatesVerifier getVerifier(String mchSerialNo) {
        AutoUpdateCertificatesVerifier verifier = null;
        if (verifierMap.isEmpty() || !verifierMap.containsKey(mchSerialNo)) {
            verifierMap.clear();
            try {
                //传入证书
                PrivateKey privateKey = getPrivateKeyV3();
                //刷新
                PrivateKeySigner signer = new PrivateKeySigner(mchSerialNo, privateKey);
                WechatPay2Credentials credentials = new WechatPay2Credentials(xcxProperties.getWecharpayMchid(), signer);
                verifier = new AutoUpdateCertificatesVerifier(credentials
                        , xcxProperties.getWecharpaySecretV3().getBytes("utf-8"));
                verifierMap.put(verifier.getValidCertificate().getSerialNumber()+"", verifier);
//                AutoUpdateCertificatesVerifier verifierNew = new AutoUpdateCertificatesVerifier(
//                        new WechatPay2Credentials(
//                                xcxProperties.getWecharpayMchid(),
//                                new PrivateKeySigner(
//                                        mchSerialNo,
//                                        privateKey)),
//                        xcxProperties.getWecharpaySecretV3().getBytes("utf-8"));
//                new WechatPay2Validator(verifierNew).;
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            verifier = verifierMap.get(mchSerialNo);
        }
        return verifier;
    }
    /**
     * 获取公私钥.通过证书
     */
    private KeyStore store;
    private final Object lock = new Object();
    public KeyPair createPKCS12(String keyAlias, String keyPass) {
//        ClassPathResource resource = new ClassPathResource(xcxProperties.getCertLocalPath());
        ClassPathResource resource = new ClassPathResource("wxP12/apiclient_cert.p12");
//        File file = new File("src/main/resources/wxP12/apiclient_cert.p12");
        char[] pem = keyPass.toCharArray();
        //请求URL
        HttpGet httpGet = new HttpGet(baseUrl+canonicalUrl);
        httpGet.setHeader("Accept", "application/json");
        //生成签名
        httpGet.setHeader("Authorization ", "WECHATPAY2-SHA256-RSA2048"+postStr);
        httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36");
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpGet);
        X509Certificate x509Certificate = null;
        try {
            synchronized (lock) {
                if (store == null) {
                    synchronized (lock) {
                        store = KeyStore.getInstance("PKCS12");
                        store.load(resource.getInputStream(), pem);
//                        store.load(new FileInputStream(file), pem);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
//                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
                FPCertificateVo certificateVo = com.alibaba.fastjson.JSONObject.parseObject(EntityUtils.toString(response.getEntity()), FPCertificateVo.class);
                for (FPCertificates certificates : certificateVo.getData()) {
                    if (format.parse(certificates.getEffective_time()).before(new Date())
                            && format.parse(certificates.getExpire_time()).after(new Date())) {
                        FPEncryptCertificate encrypt_certificate = certificates.getEncrypt_certificate();
                        //解密
                        AesUtil aesUtil = new AesUtil(xcxProperties.getWecharpaySecretV3().getBytes("utf-8"));
                        String pulicKey = aesUtil.decryptToString(
                                encrypt_certificate.getAssociated_data().getBytes("utf-8"),
                                encrypt_certificate.getNonce().getBytes("utf-8"),
                                encrypt_certificate.getCiphertext());
                        //获取平台证书
                        final CertificateFactory cf = CertificateFactory.getInstance("X509");
                        ByteArrayInputStream inputStream = new ByteArrayInputStream(pulicKey.getBytes(StandardCharsets.UTF_8));
                        x509Certificate = (X509Certificate) cf.generateCertificate(inputStream);
                    }
                }
                return x509Certificate;
            } else if (statusCode == 204) { //处理成功,无返回Body
                System.out.println("success");
                return x509Certificate;
            } else {
                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
                return x509Certificate;
            }
            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);
        } catch (GeneralSecurityException | ParseException e) {
            e.printStackTrace();
            return null;
        } finally {
            response.close();
            httpClient.close();
        }
    }