JAVA开发微信特约商户进件/提交申请单

bianmaren 发布于 2020-03-24 17:53:14    访问

标签 : JAVA 微信服务商

流程

chapter2_2_1.png


接口文档


证书下载

  1. 下载商户证书[证书以及私钥] 详情请参考 微信文档

  2. 下载平台证书请参考 微信文档GitHub


代码

工具类

package com.jxyunge.utils.wechat.payapplyment;


import okhttp3.HttpUrl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.UUID;


public class WeChatUtil {

    public static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36";
    
    // 商户证书路径
    public static final String apiclient_cert = "/apiclient_cert.pem";
    // 平台证书路径
    public static final String publicKeyPath = "/wechatpay.pem";
    // 商户私钥路径
    public static final String privageKeyPath = "/apiclient_key.pem";

    /**
     * 获取证书序列号
     *
     * @param certPath 获取商户证书序列号 传递商号证书路径 apiclient_cert
     * @return
     * @throws IOException
     */
    public static String getSerialNo(String certPath) throws IOException {
        X509Certificate certificate = getCertificate(certPath);
        return certificate.getSerialNumber().toString(16).toUpperCase();
    }

    /**
     * 获取证书。
     *
     * @param filename 证书文件路径  (required)
     * @return X509证书
     */
    public static X509Certificate getCertificate(String filename) throws IOException {
        InputStream fis = new FileInputStream(filename);
        try (BufferedInputStream bis = new BufferedInputStream(fis)) {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书文件", e);
        }
    }

    /**
     * 获取签名
     * method(请求类型GET、POST url(请求url)
     * body(请求body,GET请求时body传"",POST请求时body为请求参数的json串)
     * merchantId(商户号)
     * certSerialNo(API证书序列号)
     * keyPath(API证书路径)
     *
     * @param method       请求方式
     * @param url          请求路径
     * @param body         请求参数
     * @param merchantId   商户号
     * @param certSerialNo API证书序列号
     * @return
     * @throws Exception
     */
    public static String getToken(String method, String url, String body, String merchantId, String certSerialNo) throws Exception {
        String signStr = "";
        HttpUrl httpurl = HttpUrl.parse(url);
        String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
        long timestamp = System.currentTimeMillis() / 1000;
        if (StringUtils.isEmpty(body)) {
            body = "";
        }
        assert httpurl != null;
        String message = buildMessage(method, httpurl, timestamp, nonceStr, body);
        String signature = sign(message.getBytes(StandardCharsets.UTF_8), privageKeyPath);
        signStr = "mchid=\"" + merchantId
                + "\",nonce_str=\"" + nonceStr
                + "\",timestamp=\"" + timestamp
                + "\",serial_no=\"" + certSerialNo
                + "\",signature=\"" + signature + "\"";
        return signStr;
    }

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


    public static String sign(byte[] message, String keyPath) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(keyPath));
        sign.update(message);
        return Base64.encodeBase64String(sign.sign());
    }

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

    public static Cipher getCipher(X509Certificate certificate) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
            return cipher;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        }
    }

    /**
     * 加密
     *
     * @param message
     * @param cipher  通过getcipher方法获取
     * @return
     * @throws IllegalBlockSizeException
     */
    public static String rsaEncryptOAEP(String message, Cipher cipher) throws IllegalBlockSizeException {
        try {
            if (StringUtils.isBlank(message)) {
                return null;
            }
            byte[] data = message.getBytes(StandardCharsets.UTF_8);
            byte[] cipherdata = cipher.doFinal(data);
            return Base64.encodeBase64String(cipherdata);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }


}


单元测试

package com.jxyunge.utils.wechat.payapplyment;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lgx
 * @Description
 * @create 2020-02-21 21:31:00
 */
public class WechartTest {

   /***
   查询申请单
   */
    @Test
    public void test() throws Exception {
    
        String url = "https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/applyment_id/微信支付分配的申请单号";
        String merchantId = "商户ID";
        String serialNo = WeChatUtil.getSerialNo(WeChatUtil.apiclient_cert);
        String token = WeChatUtil.getToken("GET", url, null, merchantId, serialNo);
        String authorization = "WECHATPAY2-SHA256-RSA2048 " + token;
        // 平台证书路径 是必须的
        serialNo = WeChatUtil.getSerialNo(WeChatUtil.publicKeyPath);
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Wechatpay-Serial", serialNo);
        httpGet.setHeader("Accept", "application/json");
        httpGet.setHeader("Content-Type", "application/json");
        httpGet.setHeader("user-agent", WeChatUtil.DEFAULT_USER_AGENT);
        httpGet.setHeader("Authorization", authorization);

        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        CloseableHttpClient httpClient = httpClientBuilder.build();
        CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
        HttpEntity httpResponseEntity = httpResponse.getEntity();
        String responseEntityStr = EntityUtils.toString(httpResponseEntity);
        httpResponse.close();
        System.out.println(responseEntityStr);
     

    }

   /***
      进件接口
   */
   @Test
    public void test2() throws Exception {
        String url = "https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/";
        // TODO 
        String merchantId = "商户ID";
        String serialNo = WeChatUtil.getSerialNo(WeChatUtil.apiclient_cert);
        Map<String,Object> requestMap = new HashMap<>(1);
        requestMap.put("business_code","业务申请编号");
        
        // 参数自己根据文档进行组装
        
        String requestParams = JSONObject.toJSONString(requestMap);
        String token = WeChatUtil.getToken("POST", url, requestParams, merchantId, serialNo);
        String authorization = "WECHATPAY2-SHA256-RSA2048 " + token;
        // 平台证书路径 是必须的
        serialNo = WeChatUtil.getSerialNo(WeChatUtil.publicKeyPath);
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Wechatpay-Serial", serialNo);
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("Content-Type", "application/json");
        httpPost.setHeader("user-agent", WeChatUtil.DEFAULT_USER_AGENT);
        httpPost.setHeader("Authorization", authorization);
        httpPost.setEntity(new StringEntity(requestParams, "UTF-8"));
        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        CloseableHttpClient httpClient = httpClientBuilder.build();
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpResponseEntity = httpResponse.getEntity();
        String responseEntityStr = EntityUtils.toString(httpResponseEntity);
        httpResponse.close();
        System.out.println(responseEntityStr);

    }

    /**
     * 加密消息
     *
     * @throws IOException
     */
    @Test
    public void test1() throws IOException, IllegalBlockSizeException {
        X509Certificate certificate = WeChatUtil.getCertificate(WeChatUtil.publicKeyPath);
        Cipher cipher = WeChatUtil.getCipher(certificate);
        String s = WeChatUtil.rsaEncryptOAEP("xx", cipher);
        System.out.println(s);
    }
}


运行效果

WX20200325-103905@2x.png