java微信支付V3生成签名

1. 前言

最近在折腾微信支付,证书还是比较烦人的,在看了不少博主写的文章之后,发现都是大同小异,对新手来说可能有点看不懂,特别是私钥加密那一块,自己也踩了很多坑,微信支付的官方文档对新手特别是java还是不太友好的,这里我自己写了一个工具类,  所以有必要分享一些经验,减少你在开发微信支付时的踩坑。目前微信支付的 API 已经发展到V3版本,采用了流行的 Restful 风格。

2. 需要的支付参数

//V3主商户号
private static String merchantId;
//微信商户平台APIv3证书序列号
private static String certificateSerialNo;
//私钥
private static String privateKey;

这些都可以在微信支付官方获取到的

废话不多说,直接上代码

3. 需要的jar包

		<!-- 引入jar包>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.5.0</version>
        </dependency>

4.工具类:

package com.ruoyi.web.util;

import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import okhttp3.HttpUrl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Random;

@Component
public class SignV3Utils {

        //V3主商户ID
        private static String merchantId;
        //微信商户平台APIv3证书序列号
        private static String certificateSerialNo;
        //私钥(不要把私钥文件暴露在公共场合,如上传到Github,写在客户端代码等。)
        private static String privateKey;

        //配置文件配置好主商户号
        @Value("${wx-gzh.mchid}")
        public void setMerchantId(String merchantId) {
            SignV3Utils.merchantId = merchantId;
        }
        //配置文件配置好序列号
        @Value("${wx-gzh.certificateSerialNo}")
        public void setCertificateSerialNo(String certificateSerialNo) {
            SignV3Utils.certificateSerialNo = certificateSerialNo;
        }
        //配置文件配置好私钥
        @Value("${wx-gzh.privateKey}")
        public void setPrivateKey(String privateKey) {
            SignV3Utils.privateKey = privateKey;
        }
        /**
         * 使用方法
         * @param method 请求方法
         * @param url 请求url
         * @param body 请求内容
         * @return
         */
        public static HashMap<String, String> getSignMap(String method, String url, String body) throws InvalidKeySpecException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException, SignatureException, FileNotFoundException {
            String authorization = getSign(method, url, body);

            HashMap<String, String> headsMap = new HashMap<>();
            headsMap.put("Authorization", authorization);
            headsMap.put("Content-Type", "application/json");
            headsMap.put("Accept", "application/json");

            return headsMap;
        }

        public static String getSign(String method, String url, String body) throws NoSuchAlgorithmException, SignatureException, InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, FileNotFoundException {
            return "WECHATPAY2-SHA256-RSA2048 " + getToken(method, HttpUrl.parse(url), body);
        }

        public static String getToken(String method, HttpUrl url, String body) throws UnsupportedEncodingException, SignatureException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, FileNotFoundException {
            String nonceStr = nonceString();
            long timestamp = System.currentTimeMillis() / 1000;
            String message = buildMessage(method, url, timestamp, nonceStr, body);
            String signature = sign(message.getBytes("utf-8"));
            return "mchid=\"" + merchantId + "\","
                    + "nonce_str=\"" + nonceStr + "\","
                    + "timestamp=\"" + timestamp + "\","
                    + "serial_no=\"" + certificateSerialNo + "\","
                    + "signature=\"" + signature + "\"";
        }

        public static String sign(byte[] message) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, FileNotFoundException {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(ResourceUtils.getFile(privateKey)));
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(merchantPrivateKey);
            sign.update(message);

            return Base64.getEncoder().encodeToString(sign.sign());
        }

        public 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";
        }


        private static PrivateKey getPKCS8PrivateKey(String strPk) throws NoSuchAlgorithmException, InvalidKeySpecException {
            String realPK = strPk.replaceAll("-----END PRIVATE KEY-----", "")
                    .replaceAll("-----BEGIN PRIVATE KEY-----", "")
                    .replaceAll("\n", "");

            byte[] b1 = Base64.getDecoder().decode(realPK);

            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b1);

            KeyFactory kf = KeyFactory.getInstance("RSA");

            return kf.generatePrivate(spec);
        }

        public static String nonceString() {

            String currTime = String.format("%d", (long) System.currentTimeMillis() / 1000);

            String strTime = currTime.substring(8, currTime.length());

            Random random = new Random();
            int num = (int) (random.nextDouble() * (1000000 - 100000) + 100000);
            String code = String.format("%06d", num);

            String nonce_str = currTime.substring(2) + code;
            return nonce_str;

        }

}

5.实例demo

public static void main(String[] args) {
        //处理请求参数
        String param = JSON.toJSONString("请求参数");

        //获取签名请求头
        HashMap<String, String> heads = SignV3Utils.getSignMap("POST", "微信接口url", param);

        //如果是GET请求那么,param这个参数就传"",不能传null
   HashMap<String, String> heads = SignV3Utils.getSignMap("GET", "微信接口url", "");

        //请求微信接口
        HttpUtils.requestPostBody("微信接口url", param, heads);
    }

 最后说一下我踩的坑,一个是jdk版本,最好不要低于 1.8.0-262,不知道怎么查看版本的可以输入cmd命令查看:

还有一个就是传给工具类的url,一定要是全路径,不让的话会报错,例如:https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no; 如果是get请求的话,那么url后面要带上请求的参数

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值