日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

微信支付API-V3和V2的區別

微信支付API-V3和之前V2版本最大的區別,應該就是加密方式的改變了。新版的支付接口,全部使用是SSL雙向加密。就是指微信服務器端、商戶端各自都有一套證書,兩者之間通訊必需使用自己證書的私鑰加密,使用對方的公鑰解密。具體流程圖,可以參考微信支付官網的這張圖:

Springboot使用OkHttp實現微信支付API-V3簽名、證書的管理和使用

 

上圖所在的文檔鏈接是:
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_0.shtml 有需要的可參考。

由于微信支付官方提供的JAVA Demo使用的是Httpclient,并且比較龐雜。所以我自己對接的時候,是使用OkHttp完成的。

接入前準備

每種支付方式的準備都稍有區別,但區別也不大。這里以JS-API支付為例,簡單說說一個全新的微信支付賬號,要做哪些配置。

  1. 綁定App ID和商戶號mch id:微信支付申請下來后,是沒辦法單獨使用的??隙ㄒ劳杏诠娞枴⑿〕绦颉PP或者網站等載體,這些載體都有自己的APP ID,需要在這些載體對應的后臺里,找到微信支付菜單,點進去把微信支付的mch id和app id綁定起來。
  2. 設置API KEY:這個key主要用來解密一些微信接口的返回結果,比如下載微信平臺證書的時候。(為什么在雙向證書的情況下,還需要這個key呢?因為這些證書,只使用來簽名的,并不能加密每次請求的body)
  3. 下載商戶證書:這個沒啥好說的,新版的微信支付V3接口,商戶和微信平臺,各自都有證書。
  4. 配置各種授權域名、授權目錄等。

以上就是接入前準備的簡單介紹,具體每一步的詳細操作,可以參考微信支付官方的文檔:

  • JSAPI接入前準備:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml
  • APP支付接入前準備https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_1.shtml
  • 小程序支付接入前準備https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml

使用OkHttp封裝自帶微信支付API-V3證書加解密、簽名的請求

微信支付本身對接起來不麻煩,無非就是下單、支付、等通知該狀態三步。對新手不友好的地方,主要還是各種加密、簽名等安全措施。接下來我介紹一下如何使用OkHttp封裝一個http請求類,包含各種安全驗證措施,外部調用的時候,只要當普通OkHttp接口調用就行。各種權限驗證,已經在類里面自己實現了。

簡單介紹一下封裝類的各個方法:

  • generateToken :當我們請求微信的接口的時候,首先得生成簽名信息,放到HTTP請求的Header里,名字叫Authorization。
  • checkResponseSign :拿到微信的返回結果后,我們得拿返回結果算一下簽名,然后和返回的簽名對比一下,看看這個請求結果是真的還是偽造的。
  • decodeWxPlatCert :微信的平臺證書,定期會自動更新,我們需要調用接口下載微信的平臺證書回來。這個接口的返回結果,是用我們前面“接入前準備”中提到的API KEY加密的,所以,我們得用這個方法解密。
  • wxGet wxPost :這兩個就是封裝好的HTTP GET和HTTP POST請求了,已經在內部實現了各種安全措施。

完整代碼如下,代碼依賴了很多常見的類庫,比如Apache-commons-lang3等,可以從代碼的import中看出來,自行添加maven pom。

package com.coderbbb.blogv2.utils;

import com.coderbbb.blogv2.database.dos.WxPlatCertDO;
import com.coderbbb.blogv2.database.dto.WxCertDataDTO;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class WxOkHttpUtil extends OkhttpUtil {

    private static final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"";

    private static final String CERT_LIST = "https://api.mch.weixin.qq.com/v3/certificates";

    public static ConcurrentHashMap<String, WxPlatCertDO> WX_PLAT_CERT = new ConcurrentHashMap<>();

    private final static Logger logger = LoggerFactory.getLogger(WxOkHttpUtil.class);

    /**
     * 生成請求微信接口時需要的Authorization頭
     * @param url
     * @param method
     * @param json
     * @return
     */
    public static String generateToken(String url, String method, String json) {
        if (json == null) {
            json = "";
        }
        url = url.substring(StringUtils.ordinalIndexOf(url, "/", 3));

        long timestamp = System.currentTimeMillis() / 1000;
        String timestampStr = String.valueOf(timestamp);
        String nonceStr = RandomStringUtils.random(16, true, true);

        String signatureStr = Stream.of(method.toUpperCase(Locale.ROOT), url, timestampStr, nonceStr, json).collect(Collectors.joining("n", "", "n"));

        WxCertDataDTO wxCertDataDTO = WxCertUtil.getCert();


        String signResult;
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(wxCertDataDTO.getPrivateKey());
            sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
            signResult = Base64.encodeBase64String(sign.sign());
        } catch (Exception e) {
            throw new RuntimeException("簽名失敗", e);
        }


        //開始拼接Token
        return String.format(TOKEN_PATTERN, wxCertDataDTO.getMchId(), nonceStr, timestamp, wxCertDataDTO.getSerialNumber(), signResult);
    }

    /**
     * 請求基本Header頭,微信規定的。
     * 文檔地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay2_0.shtml
     * @param headerMap
     * @return
     */
    private static HashMap<String, String> intHeader(HashMap<String, String> headerMap) {
        if (headerMap == null) {
            headerMap = new HashMap<>();
        }
        headerMap.put("content-type", "application/json;charset=UTF-8");
        headerMap.put("user-agent", "coderbbb");
        headerMap.put("accept", "application/json");
        return headerMap;
    }

    /**
     * 微信請求我們時(比如支付的異步通知),拿這個函數校驗微信的請求是否是合法的
     * 簡單說,就是驗證微信請求我們時,簽名是否正確的。
     * @param request
     * @param requestBody
     * @return
     */
    public static boolean checkServletRequestSign(HttpServletRequest request, String requestBody) {

        String wxCertSerialNumber = request.getHeader("Wechatpay-Serial");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String sign = request.getHeader("Wechatpay-Signature");

        if (!WX_PLAT_CERT.containsKey(wxCertSerialNumber)) {
            return false;
        }

        WxPlatCertDO wxPlatCertDO = WX_PLAT_CERT.get(wxCertSerialNumber);

        String signBody = Stream.of(timestamp, nonce, requestBody).collect(Collectors.joining("n", "", "n"));

        //開始驗簽
        try {
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initVerify(wxPlatCertDO.getCertificate());
            signature.update(signBody.getBytes(StandardCharsets.UTF_8));
            if (!signature.verify(Base64.decodeBase64(sign))) {
                return false;
            }
        } catch (Exception e) {
            logger.error("驗簽錯誤", e);
            return false;
        }
        return true;
    }

    /**
     * 我們請求微信的接口后,得到返回結果,用這個函數把返回結果生成簽名,和返回的簽名對比是否一致
     * 簡單說,就是請求微信的接口后,我們自己用返回結果生成一個簽名,和請求返回的簽名對比,看看簽名一樣不
     * @param response
     * @param lazyVerify
     * @return
     */
    private static String checkResponseSign(Response response, boolean lazyVerify) {
        String result = null;
        String wxCertSerialNumber;
        String timestamp;
        String nonce;
        String sign;

        try (ResponseBody body = response.body();) {
            if (body != null) {
                result = body.string();
            }

            if (response.code() != 200) {
                logger.warn("err response = " + result);
                throw new RuntimeException("請求http code異常:" + response.code());
            }

            wxCertSerialNumber = response.header("Wechatpay-Serial");
            timestamp = response.header("Wechatpay-Timestamp");
            nonce = response.header("Wechatpay-Nonce");
            sign = response.header("Wechatpay-Signature");
        } catch (IOException e) {
            throw new RuntimeException("wx okHttp read response err", e);
        }

        WxPlatCertDO wxPlatCertDO = null;
        if (!WX_PLAT_CERT.containsKey(wxCertSerialNumber)) {
            //平臺證書不在已有的列表內
            /**
             * 我們提供以下的機制,幫助商戶在平臺證書更新時實現平滑切換:
             *
             * 1.下載新平臺證書。我們將在舊證書過期前10天生成新證書。
             * 商戶可使用平臺證書下載API 下載新平臺證書,并在舊證書過期前5-10天部署新證書。
             *
             * 2.兼容使用新舊平臺證書。舊證書過期前5天至過期當天,新證書開始逐步放量用于應答和回調的簽名。
             * 商戶需根據證書序列號,使用對應版本的平臺證書。
             * (我們在所有API應答和回調的HTTP頭部Wechatpay-Serial,聲明了此次簽名所對應的平臺證書的序列號。)
             */
            //所以:定時任務拉取微信平臺證書,在這里,如果證書不在列表內,只有兩種情況:
            //1.該請求是第一次下載微信平臺證書的請求;2.惡意請求。
            // - 我們使用lazyVerify標記第一種情況
            if (!lazyVerify) {
                //不能延遲驗簽,拋出錯誤
                throw new RuntimeException("簽名校驗失敗");
            } else {
                //可以延遲驗簽,說明該請求是下載微信平臺證書的請求,直接讀取請求返回值,提取證書
                List<WxPlatCertDO> wxPlatCertData = decodeWxPlatCert(result);
                for (WxPlatCertDO item : wxPlatCertData) {
                    if (item.getSerialNumber().equals(wxCertSerialNumber)) {
                        wxPlatCertDO = item;
                    }
                }
            }
        } else {
            wxPlatCertDO = WX_PLAT_CERT.get(wxCertSerialNumber);
        }

        if (wxPlatCertDO == null) {
            throw new RuntimeException("平臺證書不存在,驗簽失敗");
        }

        String signBody = Stream.of(timestamp, nonce, result).collect(Collectors.joining("n", "", "n"));

        //開始驗簽
        try {
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initVerify(wxPlatCertDO.getCertificate());
            signature.update(signBody.getBytes(StandardCharsets.UTF_8));
            if (!signature.verify(Base64.decodeBase64(sign))) {
                throw new RuntimeException("簽名錯誤,請求不安全");
            }
        } catch (Exception e) {
            throw new RuntimeException("驗簽錯誤", e);
        }

        return result;
    }

    /**
     * 下載微信平臺證書時,用這個函數解密拿到的返回值,得到微信平臺證書列表
     * @param json
     * @return
     */
    private static List<WxPlatCertDO> decodeWxPlatCert(String json) {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonNode;
        JsonNode dataNode;
        try {
            jsonNode = mapper.readTree(json);
            dataNode = jsonNode.get("data");
        } catch (Exception e) {
            throw new RuntimeException("讀取證書JSON失敗", e);
        }

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

        List<WxPlatCertDO> certList = new ArrayList<>();
        for (JsonNode itemNode : dataNode) {
            JsonNode certNode = itemNode.get("encrypt_certificate");
            String cert = AesUtil.decryptJsonNodeToString(certNode);

            try {
                CertificateFactory cf = CertificateFactory.getInstance("X509");
                X509Certificate x509Cert = (X509Certificate) cf.generateCertificate(
                        new ByteArrayInputStream(cert.getBytes(StandardCharsets.UTF_8))
                );
                x509Cert.checkValidity();

                WxPlatCertDO wxPlatCertDO = new WxPlatCertDO();
                wxPlatCertDO.setCertificate(x509Cert);
                wxPlatCertDO.setCert(cert);
                wxPlatCertDO.setEffectiveTime(format.parse(itemNode.get("effective_time").asText()));
                wxPlatCertDO.setExpireTime(format.parse(itemNode.get("expire_time").asText()));
                wxPlatCertDO.setSerialNumber(itemNode.get("serial_no").asText());

                certList.add(wxPlatCertDO);
            } catch (Exception e) {
                logger.error("update wx plat cert err", e);
            }
        }
        return certList;
    }

    /**
     * 封裝的http get請求,直接使用即可,證書、簽名的生成和校驗已經集成
     * @param url
     * @param headerMap
     * @return
     */
    public static String wxGet(String url, HashMap<String, String> headerMap) {

        boolean lazyVerify = false;
        if (url.equals(CERT_LIST)) {
            //是下載證書的請求,允許延遲驗簽
            lazyVerify = true;
        }

        String token = generateToken(url, "get", null);

        headerMap = intHeader(headerMap);
        headerMap.put("Authorization", token);


        Request.Builder builder = new Request.Builder().url(url);

        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }

        Request request = builder.build();
        try (Response response = getClient().newCall(request).execute()) {
            return checkResponseSign(response, lazyVerify);
        } catch (IOException e) {
            throw new RuntimeException("wx okHttp get err", e);
        }
    }

    /**
     * 封裝的http post請求,直接使用即可,證書、簽名的生成和校驗已經集成
     * @param url
     * @param json
     * @param headerMap
     * @return
     */
    public static String wxPost(String url, String json, HashMap<String, String> headerMap) {
        RequestBody requestBody = RequestBody.create(json, MediaType.parse("application/json"));

        String token = generateToken(url, "post", json);

        headerMap = intHeader(headerMap);
        headerMap.put("Authorization", token);

        Request.Builder builder = new Request.Builder().url(url).post(requestBody);
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }
        try (Response response = getClient().newCall(builder.build()).execute()) {
            return checkResponseSign(response, false);
        } catch (IOException e) {
            throw new RuntimeException("wx okHttp post err", e);
        }
    }

    /**
     * 從微信下載微信平臺證書
     * @return
     */
    public static List<WxPlatCertDO> getCertList() {
        String s = wxGet(CERT_LIST, null);
        return decodeWxPlatCert(s);
    }

}

上面的代碼中,依賴了很多我自己寫的其他類庫,這里逐一介紹一下。

  • OkHttpUtil 基于OkHttp封裝的HTTP請求庫,代碼后面放。
  • AesUtil 敏感信息解密的類,比如微信下載平臺證書的請求,返回結果就是加密的,需要解密。
  • WxCertUtil 主要有兩個功能,一個是從本地文件讀取商戶自己的證書,給其他地方用;另一個是請求微信時,對敏感信息加密,這個功能和AesUtil是對應的。
  • 各種POJO類,比如加載證書信息后,證書信息有一個POJO類。這些類就不放代碼了,每個人需求不一樣,自己自定義即可。 好吧,好多讀者反饋需要,那我放到文章最后。
package com.coderbbb.blogv2.utils;

import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


public class OkhttpUtil {

    private static OkHttpClient client = null;

    private static final Logger logger = LoggerFactory.getLogger(OkhttpUtil.class);

    private synchronized static void createClient() {
        if (client == null) {
            OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();

            okHttpBuilder.protocols(Collections.singletonList(Protocol.HTTP_1_1));
            okHttpBuilder.connectTimeout(60, TimeUnit.SECONDS);
            okHttpBuilder.readTimeout(60, TimeUnit.SECONDS);
            okHttpBuilder.writeTimeout(60, TimeUnit.SECONDS);
            okHttpBuilder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String s, SSLSession sslSession) {
                    //支持所有類型https請求
                    return true;
                }
            });

            ConnectionPool pool = new ConnectionPool(200, 1, TimeUnit.SECONDS);
            okHttpBuilder.connectionPool(pool);

            client = okHttpBuilder.build();
            client.dispatcher().setMaxRequests(2000);
            client.dispatcher().setMaxRequestsPerHost(1000);
        }
    }

    public static OkHttpClient getClient() {
        if (client == null) {
            createClient();
        }
        return client;
    }

    public static String get(String url, HashMap<String, String> headerMap) {
        Request.Builder builder = new Request.Builder().url(url);

        if (headerMap != null) {
            for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                builder.addHeader(entry.getKey(), entry.getValue());
            }
        }

        Request request = builder.build();

        String result = null;
        try {
            result = excute(request);
        } catch (Exception e) {
            logger.warn("http get fail:" + url + "###" + e.getMessage());
        }
        return result;
    }

    public static String postRaw(String url, String contentType, String json, HashMap<String, String> headerMap) throws Exception {
        RequestBody requestBody = RequestBody.create(json,MediaType.parse(contentType));

        Request.Builder builder = new Request.Builder().url(url).post(requestBody);
        if (headerMap != null) {
            for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                builder.addHeader(entry.getKey(), entry.getValue());
            }
        }
        Request request = builder.build();
        String result = null;
        try {
            result = excute(request);
        } catch (Exception e) {
            logger.error("http post raw fail:" + url + "###" + e.getMessage());
        }
        return result;
    }

    public static String post(String url, HashMap<String, String> data, HashMap<String, String> headerMap) throws Exception {

        FormBody.Builder formBodyBuilder = new FormBody.Builder();
        for (Map.Entry<String, String> entry : data.entrySet()) {
            formBodyBuilder.add(entry.getKey(), entry.getValue());
        }
        RequestBody requestBody = formBodyBuilder.build();

        Request.Builder builder = new Request.Builder().url(url).post(requestBody);
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }
        Request request = builder.build();
        String result = null;
        try {
            result = excute(request);
        } catch (Exception e) {
            logger.error("http post fail:" + url + "###" + e.getMessage());
        }
        return result;
    }

    private static String excute(Request request) throws Exception {
        Response response = getClient().newCall(request).execute();
        ResponseBody body = response.body();
        if (body != null) {
            String str = body.string();
            body.close();
            response.close();
            return str;
        }
        return null;
    }
}

package com.coderbbb.blogv2.utils;

import com.fasterxml.jackson.databind.JsonNode;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AesUtil {


    private static final int TAG_LENGTH_BIT = 128;

    public static String aesKey;

    public static String decryptJsonNodeToString(JsonNode jsonNode){
        return decryptToString(
                jsonNode.get("associated_data").asText().getBytes(StandardCharsets.UTF_8),
                jsonNode.get("nonce").asText().getBytes(StandardCharsets.UTF_8),
                jsonNode.get("ciphertext").asText());
    }

    public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) {
        if (aesKey == null) {
            throw new RuntimeException("aesKey不能為空");
        }
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);

            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("aes解密失敗", e);
        }
    }
}

package com.coderbbb.blogv2.utils;

import com.coderbbb.blogv2.database.dto.WxCertDataDTO;
import org.apache.commons.codec.binary.Base64;
import org.springframework.core.io.ClassPathResource;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

/**
 * 微信支付證書解析
 *
 * @author longge93
 */
public class WxCertUtil {

    private static WxCertDataDTO wxCertDataDTO = null;

    public static String keyPass = null;

    public static String sslPath = "ssl/wx2.p12";

    private static synchronized WxCertDataDTO loadCert() {
        if (wxCertDataDTO != null) {
            return wxCertDataDTO;
        }

        if (keyPass == null) {
            throw new RuntimeException("還沒有設置證書密碼");
        }

        ClassPathResource classPathResource = new ClassPathResource(sslPath);

        String serialNumber;
        PublicKey publicKey;
        PrivateKey privateKey;
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(classPathResource.getInputStream(), keyPass.toCharArray());

            Enumeration<String> aliases = keyStore.aliases();
            X509Certificate cert = (X509Certificate) keyStore.getCertificate(aliases.nextElement());

            //證書序列號
            serialNumber = cert.getSerialNumber().toString(16).toUpperCase();
            // 證書公鑰
            publicKey = cert.getPublicKey();
            // 證書私鑰
            privateKey = (PrivateKey) keyStore.getKey(keyStore.getCertificateAlias(cert), keyPass.toCharArray());
        } catch (Exception e) {
            throw new RuntimeException("讀取證書失敗", e);
        }

        wxCertDataDTO = new WxCertDataDTO();
        wxCertDataDTO.setPublicKey(publicKey);
        wxCertDataDTO.setPrivateKey(privateKey);
        wxCertDataDTO.setSerialNumber(serialNumber);
        wxCertDataDTO.setMchId(keyPass);

        return wxCertDataDTO;
    }

    public static WxCertDataDTO getCert() {
        if (wxCertDataDTO != null) {
            return wxCertDataDTO;
        }
        return loadCert();
    }

    public static String rsaSign(String signatureStr){

        WxCertDataDTO wxCertDataDTO = getCert();

        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(wxCertDataDTO.getPrivateKey());
            sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
            return Base64.encodeBase64String(sign.sign());
        } catch (Exception e) {
            throw new RuntimeException("RSA簽名失敗");
        }
    }
}

源代碼使用方法

  • 首先,你得pom引入OkHttp和apache-commons-lang3(可能有遺漏,你如果有報錯,就自己加一下)
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>
  • 然后,把上面依賴的OkHttpUtil、AesUtil、WxCertUtil和WxOkHttpUtil放到一起,把報錯的import改一改(因為你和我的包名不一樣,肯定會報錯)
  • 關于證書管理:我的策略是,每次程序啟動(或定時任務),都會調用微信的接口,下載微信平臺證書,保存到數據庫,再保存到靜態變量中WxOkHttpUtil類的WX_PLAT_CERT變量。(所以,你使用代碼時,應該先調用下載WxOkHttpUtil中下載證書的接口,把證書加載到這個變量中,然后才能正常執行微信下單、退款等等操作)
  • WxCertUtil.keyPass是證書的密碼,其實就是微信商戶號
  • AesUtil.aesKey 就是前面接入準備中提到的API KEY

其他

到這一步,微信支付API-V3的各種安全校驗問題應該是解決了,專欄下一篇就介紹怎么使用本文封裝好的HTTP GET、HTTP POST來完成整個微信支付流程。

讀者要求的POJO類代碼

  • WxCertDataDTO
package com.coderbbb.book1.database.dto;

import lombok.Data;

import java.security.PrivateKey;
import java.security.PublicKey;

@Data
public class WxCertDataDTO {

    /**
     * 證書序列號
     */
    private String serialNumber;

    /**
     * 證書公鑰
     */
    private PublicKey publicKey;

    /**
     * 證書私鑰
     */
    private PrivateKey privateKey;

    /**
     * 商戶號
     */
    private String mchId;
}
  • WxPlatCertDO,這個就是把證書往數據庫存的實體類,繼承了一個MybatisBaseDO類(這個類你可以刪掉不繼承,里面是我封裝的數據庫主鍵、時間戳等等通用字段,你按自己的喜好搞)。
package com.coderbbb.blogv2.database.dos;

import lombok.Data;

import java.security.cert.X509Certificate;
import java.util.Date;

@Data
public class WxPlatCertDO extends MybatisBaseDO{

    /**
     * 證書序列號
     */
    private String serialNumber;

    /**
     * 證書過期時間
     */
    private Date expireTime;

    /**
     * 生效時間
     */
    private Date effectiveTime;

    /**
     * X509Certificate JSON序列化
     */
    private String cert;

    /**
     * 證書
     */
    private X509Certificate certificate;
}

版權聲明:《Java Springboot使用OkHttp實現微信支付API-V3簽名、證書的管理和使用》為CoderBBB作者「???」的原創文章,轉載請附上原文出處鏈接及本聲明。

原文鏈接:
https://www.coderbbb.com/articles/4

分享到:
標簽:Springboot
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定