评论

微信支付 API V3 创建订单

微信支付 API V3 创建订单

package org.jeecg.modules.milk.utils;

import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Random;

@Slf4j
public class WxV3Authorization {
    /**
     * 请求头【authorization】的组装(【Authorization: 认证类型 签名信息】)
     * 1、举例【WECHATPAY2-SHA256-RSA2048 mchid="商户号",
     *      nonce_str="随机字符串32位",
     *      signature="签名",timestamp="时间戳",
     *      serial_no="证书序列号"】
     * 2、注意:前缀固定值(WECHATPAY2-SHA256-RSA2048 )后面有空格
     * 3、组装过程封装为一个工具类【WxV3Authorization】
     * 4、参考页面【https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/qian-ming-sheng-cheng】
     */
    /**
     * @param method    请求方法(与接口请求的方法保持一致【必需大写】)
     * @param url       请求的url(即,把请求的url配置在这里,不需要参数)
     * @param body      请求体(需要转换为json字符串)
     * @param mchid     商户号
     * @param serialNo  商户证书序列号
     * @param authorizationType 认证类型,目前格式固定【WECHATPAY2-SHA256-RSA2048】
     * @param serialPath    证书路径(其中存放的是私钥)
     * @return          返回组装好的(Authorization请求头)
     */
    public static String getAuthorization(String method, HttpUrl url, String body, String mchid, String serialNo, String authorizationType, String serialPath){
        //准备32位的随机字符串
        String nonceStr = generateNonceStr();
        //时间戳(去掉后面三个0)
        long timestamp = System.currentTimeMillis() / 1000;
        //组装需要签名的数据
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        //使用私钥对数据进行签名(加密),【数据签名之后需要转换为字节数组(采用utf-8)】
        String signature = null;
        try {
            signature = sign(message.getBytes("utf-8"), serialPath);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return  authorizationType + "mchid=\"" + mchid + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "signature=\"" + signature + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + serialNo + "\"";
    }

    /**对字节数据进行私钥签名(加密)*/
    private static String sign(byte[] message, String serialPath){
        try {
            //签名方式(固定SHA256withRSA)
            Signature sign = Signature.getInstance("SHA256withRSA");
            //使用私钥进行初始化签名(私钥需要从私钥文件【证书】中读取)
            sign.initSign(getPrivateKey(serialPath));
            //签名更新
            sign.update(message);
            //对签名结果进行Base64编码
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 组装需要签名的字符串
     * @param method    请求接口的方法
     * @param url       请求接口的url(截取其中:1、域名后;2、?问题前面-》中间部分的内容)
     * @param timestamp 时间戳
     * @param nonceStr  随机32位字符串
     * @param body      请求接口提交的数据(json数据字符串)
     * @return  返回组装之后的数据(注意,每一种数据后面都需要换行【包括最后】)
     */
    private static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    private static PrivateKey getPrivateKey(String filename){
        try {
            String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            log.debug("当前Java环境不支持RSA");
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            log.debug("无效的密钥格式");
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取随机字符串 Nonce Str
     * 随机字符从symbols获取
     * SecureRandom真随机数
     * @return String 随机字符串
     */
    private static String generateNonceStr() {
        String symbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        Random random = new SecureRandom();
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = symbols.charAt(random.nextInt(symbols.length()));
        }
        return new String(nonceChars);
    }
}


点赞 1
收藏
评论
登录 后发表内容