微信支付JAVA V3APP下單整合


1. 背景介紹

v3版微信支付通過商戶證書和平台證書加強了安全性,java版sdk包wechatpay-apache-httpclient內部封裝了安全性相關的簽名、驗簽、加密和解密工作,降低了開發難度。下面幾個特性的實現,更方便了開發者。

  1. 平台證書自動更新,無需開發者關注平台證書有效性,無需手動下載更新;
  2. 執行請求時將自動攜帶身份認證信息,並檢查應答的微信支付簽名。

如果文檔中有錯誤的地方,需要路過的大佬指出,我會盡快更改。跪謝。。。

2. API證書

2.1 API 密鑰設置( 一定要設置的!!!! )

請登錄商戶平台進入【賬戶中心】->【賬戶設置】->【API安全】->【APIv3密鑰】中設置 API 密鑰。

具體操作步驟請參見:什么是APIv3密鑰?如何設置?

2.2 獲取 API 證書

請登錄商戶平台進入【賬戶中心】->【賬戶設置】->【API安全】根據提示指引下載證書。

具體操作步驟請參見:什么是API證書?如何獲取API證書?

簡單敘述:(詳細信息請看官方文檔

證書 描述 備注
apiclient_cert.p12 包含了私鑰信息的證書文件 是商戶證書文件(雙向證書)
apiclient_cert.pem 從apiclient_cert.p12中導出證書部分的文件,為pem格式 簡單理解:公鑰
apiclient_key.pem 從apiclient_key.pem中導出密鑰部分的文件 簡單理解:私鑰

3. 創建client(請參照官方文檔

import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

@Slf4j
@Aspect
@Component
public class WxPayHttpClientFactory {

    public static CloseableHttpClient httpClient;
    public static Verifier verifier;

    @Before("execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))" +
            "||execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))")
    public void initWXPayClient() {
        try {
            // 加載商戶私鑰(privateKey:私鑰字符串)
            PrivateKey merchantPrivateKey = PemUtil
                    .loadPrivateKey(new ClassPathResource("apiclient_key.pem classpath路徑").getInputStream());

            X509Certificate certificate = PemUtil.loadCertificate(new ClassPathResource("apiclient_cert.pem classpath路徑").getInputStream());
            String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
            //merchantId:商戶號,serialNo:商戶證書序列號
            // 獲取證書管理器實例
            CertificatesManager certificatesManager = CertificatesManager.getInstance();
            // 向證書管理器增加需要自動更新平台證書的商戶信息
            certificatesManager.putMerchant("商戶號", new WechatPay2Credentials("商戶號",
                    new PrivateKeySigner(serialNo, merchantPrivateKey)), wechatAppPayConfig.api_v3.getBytes(StandardCharsets.UTF_8));
            // 從證書管理器中獲取verifier
            //版本>=0.4.0可使用 CertificatesManager.getVerifier(mchId) 得到的驗簽器替代默認的驗簽器。
            // 它會定時下載和更新商戶對應的微信支付平台證書 (默認下載間隔為UPDATE_INTERVAL_MINUTE)。
            verifier = certificatesManager.getVerifier("商戶號");

            //創建一個httpClient
            httpClient = WechatPayHttpClientBuilder.create()
                    .withMerchant("商戶號", serialNo, merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier)).build();
        } catch (IOException e) {
            e.printStackTrace();
            log.error("加載秘鑰文件失敗");
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            log.error("獲取平台證書失敗");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @After("execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))" +
            "||execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))")
    public void closeWXClient() {
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

官方文檔指出 自動攜帶身份認證信息,並檢查應答的微信支付簽名 。所以上面的代碼就沒攜帶任何簽名。

1648619975768.png

4. 生成APP支付訂單(請參照官方文檔

    /**
     * 微信支付POST請求
     *
     * @param reqUrl       請求地址 示例:https://api.mch.weixin.qq.com/v3/pay/transactions/app
     * @param paramJsonStr 請求體 json字符串 此參數與微信官方文檔一致
     * @return 訂單支付的參數
     * @throws Exception
     */
    public static String V3PayPost(String reqUrl, String paramJsonStr) throws Exception {
        //創建post方式請求對象
        HttpPost httpPost = new HttpPost(reqUrl);
        //裝填參數
        StringEntity s = new StringEntity(paramJsonStr, "utf-8");
        s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
                "application/json"));
        //設置參數到請求對象中
        httpPost.setEntity(s);
        //指定報文頭【Content-type】、【User-Agent】
        httpPost.setHeader("Content-type", "application/json");
        httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
        httpPost.setHeader("Accept", "application/json");
        //執行請求操作,並拿到結果(同步阻塞)
        CloseableHttpResponse response = WxPayHttpClientFactory.httpClient.execute(httpPost);
        int statusCode = response.getStatusLine().getStatusCode();
        //獲取數據,並釋放資源
        String body = closeHttpResponse(response);
        if (statusCode == 200) { //處理成功
            switch (reqUrl) {
                case "https://api.mch.weixin.qq.com/v3/pay/transactions/app"://返回APP支付所需的參數
                    return JSONObject.parseObject(body).getString("prepay_id");
                case "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"://返回APP退款結果
                    return body;
            }
        }
        return null;
    }
    /**
     * 獲取數據,並釋放資源
     *
     * @param response
     * @return
     * @throws IOException
     */
    public static String closeHttpResponse(CloseableHttpResponse response) throws IOException {
        String body = "";
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) { //處理成功
            //獲取結果實體
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                //按指定編碼轉換結果實體為String類型
                body = EntityUtils.toString(entity, "utf-8");
            }
            //EntityUtils.consume將會釋放所有由httpEntity所持有的資源
            EntityUtils.consume(entity);
        }
        //釋放鏈接
        response.close();
        return body;
    }

至此就成功 在微信支付服務后台生成預支付交易單,並且拿到prepay_id,但是調起APP支付,還需其他字段,也就是所謂的二簽。可以直接使用下面的方法WxAppPayTuneUp()

5.APP調起支付(請參照官方文檔

    /**
     * 微信調起支付參數
     * 返回參數如有不理解 請訪問微信官方文檔
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
     *
     * @param prepayId         微信下單返回的prepay_id
     * @param appId            應用ID
     * @param mch_id           商戶號
     * @param private_key_path 私鑰路徑
     * @return 當前調起支付所需的參數
     * @throws Exception
     */
    public static String WxAppPayTuneUp(String prepayId, String appId, String mch_id, String private_key_path) throws Exception {
        if (StringUtils.isNotBlank(prepayId)) {
            long timestamp = System.currentTimeMillis() / 1000;
            String nonceStr = generateNonceStr();
            //加載簽名
            String packageSign = sign(buildMessage(appId, timestamp, nonceStr, prepayId).getBytes(), private_key_path);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("appId", appId);
            jsonObject.put("prepayId", prepayId);
            jsonObject.put("timeStamp", timestamp);
            jsonObject.put("nonceStr", nonceStr);
            jsonObject.put("package", "Sign=WXPay");
            jsonObject.put("signType", "RSA");
            jsonObject.put("sign", packageSign);
            jsonObject.put("partnerId", mch_id);
            return jsonObject.toJSONString();
        }
        return "";
    }
    public static String sign(byte[] message, String private_key_path) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
        //簽名方式
        Signature sign = Signature.getInstance("SHA256withRSA");
        //私鑰
        sign.initSign(PemUtil
                .loadPrivateKey(new ClassPathResource(private_key_path).getInputStream()));
        sign.update(message);

        return Base64.getEncoder().encodeToString(sign.sign());
    }
	/**
     * 按照前端簽名文檔規范進行排序,\n是換行
     *
     * @param appId     appId
     * @param timestamp 時間
     * @param nonceStr  隨機字符串
     * @param prepay_id prepay_id
     * @return
     */
    public static String buildMessage(String appId, long timestamp, String nonceStr, String prepay_id) {
        return appId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + prepay_id + "\n";
    }
    protected static final SecureRandom RANDOM = new SecureRandom();
	//生成隨機字符串 微信底層的方法,直接copy出來了
    protected static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(RANDOM.nextInt("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".length()));
        }
        return new String(nonceChars);
    }

將WxAppPayTuneUp()返回的字符串,響應給前端即可

6.支付通知(異步通知,請參照官方文檔

    @PostMapping("/wxAppPayNotify")
    public JSONObject wxAppPayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException, GeneralSecurityException {
        //從請求頭獲取驗簽字段
        String signature = request.getHeader("Wechatpay-Signature");
        String serial = request.getHeader("Wechatpay-Serial");
        ServletInputStream inputStream = request.getInputStream();
        StringBuilder sb = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s;
        //讀取回調請求體
        while ((s = bufferedReader.readLine()) != null) {
            sb.append(s);
        }
        String s1 = sb.toString();
        //按照文檔要求拼接驗簽串
        String verifySignature = request.getHeader("Wechatpay-Timestamp") + "\n"
                + request.getHeader("Wechatpay-Nonce") + "\n" + s1 + "\n";

        //使用官方驗簽工具進行驗簽
        boolean verify1 = WxPayHttpClientFactory.verifier.verify(serial, verifySignature.getBytes(), signature);
        //判斷驗簽的結果
        if (!verify1) {
            //驗簽失敗,應答接口
            //設置狀態碼
            response.setStatus(500);
            JSONObject jsonResponse = new JSONObject();
            jsonResponse.put("code", "FAIL");
            jsonResponse.put("message", "失敗");
            return jsonResponse;
        }

        JSONObject parseObject = JSONObject.parseObject(s1);
        if ("TRANSACTION.SUCCESS".equals(parseObject.getString("event_type"))
                && "encrypt-resource".equals(parseObject.getString("resource_type"))) {
            //通知的類型,支付成功通知的類型為TRANSACTION.SUCCESS
            //通知的資源數據類型,支付成功通知為encrypt-resource
            JSONObject resourceJson = JSONObject.parseObject(parseObject.getString("resource"));
            String associated_data = resourceJson.getString("associated_data");
            String nonce = resourceJson.getString("nonce");
            String ciphertext = resourceJson.getString("ciphertext");

            //解密,如果這里報錯,就一定是APIv3密鑰錯誤
            AesUtil aesUtil = new AesUtil(api_v3.getBytes());
            String aes = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
		   System.out.println("解密后=" + aes);
            //dosomething 處理業務
            }
        }

        JSONObject jsonResponse = new JSONObject();
        jsonResponse.put("code", "SUCCESS");
        jsonResponse.put("message", "成功");
        //設置狀態碼
        response.setStatus(200);
        return jsonResponse;
    }

以上是一套APP下單的流程【APP下單】->【APP調起支付】->【微信支付異步通知】


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM