微信支付API v3简介

微信官方文档地址

为了在 保证支付 安全的前提下,带给商户 简单、一致且易用的开发体验,我们推出了全新的微信支付API v3。

相较于之前的微信支付API,主要区别是:

  • 遵循统一的REST ful的设计风格
  • 使用JSON作为数据交互的格式,不再使用XML
  • 使用基于非对称密钥的SHA256-RSA的数字签名算法,不再使用MD5或HMAC-SHA256
  • 不再要求HTTPS客户端证书
  • 使用AES-256-GCM,对回调中的关键信息进行加密保护
    在接口规则中,你将了解到微信支付API v3的基础约定,如数据格式,参数兼容性,错误处理等。随后我们重点介绍了微信支付API v3新的认证机制。你可以跟随着签名指南,使用命令行或者你熟悉的编程语言,一步一步实践如何签名和验签。

java微信app支付签名 微信支付api签名错误_java


在进行v3签名开发之前,可以先了解一下微信v3签名基本规则 所需要获取的各种证书需要提前准备,签名需要用到商户号,微信支付平台序列号,商户API的私钥, 这里不讲解怎么获取, 网上一大堆,进入此微信官方链接了解证书

java微信app支付签名 微信支付api签名错误_其他_02


开始开发,可参考微信签名生成文档

注:代码框架使用springboot

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

SignV3Utils

import okhttp3.HttpUrl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
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("${wechat.v3.merchantId}")
    public void setMerchantId(String merchantId) {
        SignV3Utiles.merchantId = merchantId;
    }
    //配置文件配置好序列号
    @Value("${wechat.v3.certificateSerialNo}")
    public void setCertificateSerialNo(String certificateSerialNo) {
        SignV3Utiles.certificateSerialNo = certificateSerialNo;
    }
	//配置文件配置好私钥
    @Value("${wechat.v3.privateKey}")
    public void setPrivateKey(String privateKey) {
        SignV3Utiles.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 {
        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 {
        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 {
        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, InvalidKeySpecException, InvalidKeyException, SignatureException {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPKCS8PrivateKey(privateKey));
        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;

    }
}

简单调用实例demo

@RequestMapping(value = "/test", method = RequestMethod.POST, produces = "application/json; charset=utf-8")
@ApiOperation("测试")
public void test() throws Exception{
        //处理请求参数
        String param = JSON.toJSONString("请求参数");
        //获取签名请求头
        HashMap<String, String> heads = SignV3Utils.getSignMap("POST", "微信接口url", param);
        //请求微信接口
        HttpUtils.requestPostBody("微信接口url", param, heads);
    }

具体每个接口使用文档,请参考微信API文档 请求方式,请求参数不用写法都不同,这里就不一一列举,有兴趣的兄弟可以沟通交流下。