JAVA之微信小程序支付功能实现涉及微信双向证书(PKCS12证书设置与SSL请求封装)

JAVA之微信小程序支付功能实现涉及微信双向证书(PKCS12证书设置与SSL请求封装)

问题背景

微信小程序下单和退款不一样,退款需要支持双向证书,本篇讲解双向证书的使用以及退款工具类。

解决方案

我们分两个部分,一个是业务参数拼接与Sign签名,一个是https请求/ssl请求与pkcs12证书(微信API证书),用到的包org.apache.httpcomponents/httpclient 发生post请求。

微信API证书(双向证书)

登录微信支付商户平台,进入【账户中心】->【账户设置】->【API安全】
若页面效果如下图,表示你的证书类型为“微信支付颁发的API证书”,可点击“下载证书”按钮,并按页面指引操作即可,如果没有请点击“申请证书”按钮。
JAVA之微信小程序支付功能实现涉及微信双向证书(PKCS12证书设置与SSL请求封装)

微信小程序支付工具类

用到的maven库是apache的httpclient,里面包含大量的SSL请求相关,引入即可。

<dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>

核心业务请求,大部分基于httpclient,需要手工设置filepath,也可以自己修改成一个变量传进来。

mchId=商户id用于解码秘钥
refund_url=请求的url
data是上文封装好的xml数据

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.KeyStore;

/**
 * 微信小程序支付工具类
 */
public class WxPayUtil {


    //微信小程序读取双向证书
    private static CloseableHttpClient readCertificate(String mchId) throws Exception{
        /**
         * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        //P12文件目录 证书路径,这里需要你自己修改,linux下还是windows下的根路径
        String filepath = "F:\\WX\\";
        System.out.println("filepath->" + filepath);
        FileInputStream instream = new FileInputStream(filepath + "apiclient_cert.p12");
        try {
            keyStore.load(instream, mchId.toCharArray());//这里写密码..默认是你的MCHID
        } finally {
            instream.close();
        }
        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();//这里也是写密码的
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        return HttpClients.custom().setSSLSocketFactory(sslsf).build();
    }

    //微信小程序不读取双向证书
    private static CloseableHttpClient notReadCertificate() throws Exception {

        CloseableHttpClient httpClient = null;
        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
            // 信任所有
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        }).build();

        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
        httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

        return httpClient;
    }

    //微信小程序统一下单or查询订单or关闭订单(不需要双向证书)
    public static String doSendRequest(String url, String data) throws Exception{

        CloseableHttpClient httpClient = notReadCertificate();
        try {
            HttpPost httpost = new HttpPost(url); // 设置响应头信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpClient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } catch (Exception e){
            throw new RuntimeException(e);
        } finally {
            httpClient.close();
        }
    }

    //微信小程序退款订单(需要双向证书)
    public static String doRefundRequest(String mchId, String url, String data) throws Exception {

        //小程序退款需要调用双向证书的认证
        CloseableHttpClient httpClient = readCertificate(mchId);
        try {
            HttpPost httpost = new HttpPost(url); // 设置响应头信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpClient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } catch (Exception e){
            throw new RuntimeException(e);
        } finally {
            httpClient.close();
        }
    }

    //微信小程序下载对账单
    public static String doDownloadbill(String url, String data) throws Exception{

        CloseableHttpClient httpClient = notReadCertificate();
        try {
            HttpPost httpost = new HttpPost(url); // 设置响应头信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpClient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } catch (Exception e){
            throw new RuntimeException(e);
        } finally {
            httpClient.close();
        }
    }

    //微信小程序下载资金账单
    public static String doDownloadfundflow(String mchId, String url, String data) throws Exception {

        //小程序下载资金账单需要调用双向证书的认证
        CloseableHttpClient httpClient = readCertificate(mchId);
        try {
            HttpPost httpost = new HttpPost(url); // 设置响应头信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpClient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } catch (Exception e){
            throw new RuntimeException(e);
        } finally {
            httpClient.close();
        }
    }

}