1. 程式人生 > >JAVA之微信小程式支付退款(PKCS12證書設定與SSL請求封裝)

JAVA之微信小程式支付退款(PKCS12證書設定與SSL請求封裝)

問題背景

話說有小程式支付就有小程式退款,退款和支付是對應的,不能憑空退。

解決方案

解決方案有點長,我們分兩個部分,一個是業務引數拼接與Sign簽名,一個是https請求/ssl請求與pkcs12證書,用到的包org.apache.httpcomponents/httpclient。

引數拼接

以下是官方規定的欄位,有些可以不需要,根據業務情況來即可。
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4

欄位名 變數名 必填 型別 示例值 描述
小程式ID appid String(32) wx8888888888888888 微信分配的小程式ID
商戶號 mch_id String(32) 1900000109 微信支付分配的商戶號
隨機字串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 隨機字串,不長於32位。推薦隨機數生成演算法
簽名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 簽名,詳見簽名生成演算法
微信訂單號 transaction_id 二選一 String(32) 1217752501201407033233368018 微信生成的訂單號,在支付通知中有返回
商戶訂單號 out_trade_no String(32) 1217752501201407033233368018 商戶系統內部訂單號,要求32個字元內,只能是數字、大小寫字母_-|*@ ,且在同一個商戶號下唯一。
商戶退款單號 out_refund_no String(64) 1217752501201407033233368018 商戶系統內部的退款單號,商戶系統內部唯一,只能是數字、大小寫字母_-|*@ ,同一退款單號多次請求只退一筆。
訂單金額 total_fee Int 100 訂單總金額,單位為分,只能為整數,詳見支付金額
退款金額 refund_fee Int 100 退款總金額,訂單總金額,單位為分,只能為整數,詳見支付金額

退款請求報文

以下是真實的業務場景所需要的引數,資料做了處理,可供參考。
=======================退款XML資料:

<xml>
    <appid>wxe09a8f4******</appid>
    <mch_id>150074*****</mch_id>
    <nonce_str>aqw596hsfxs9f0kposs64pzw8xiwd692</nonce_str>
    <out_trade_no>20181210024229*****</out_trade_no>
    <out_refund_no>2018121002422*****</out_refund_no>
    <total_fee>1</total_fee>
    <refund_fee>1</refund_fee>
    <refund_desc>使用者退票f906d8ae70434430ace5671651bde693</refund_desc>
    <sign>6C2D267A54932D941C4F838D12D0C916</sign>
</xml>

PKCS12證書與SSl請求封裝

用到的maven庫是apache的httpclient,裡面包含大量的SSL請求相關,引入即可。

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>

外部Controller或者ServiceImpl呼叫方法

String result = "";
 // 呼叫退款介面,並接受返回的結果
 try{
     result = PayUtil.doRefund(mch_id,refund_url,xml);
     log.info("=======================退款RESPONSE資料:" + result);
 }catch (Exception e){
     e.printStackTrace();
 }

核心業務請求,大部分基於httpclient,需要手工設定filepath,也可以自己修改成一個變數傳進來。

  • mchId=商戶id用於解碼祕鑰
  • refund_url=請求的url,官方是https://api.mch.weixin.qq.com/secapi/pay/refund基本不會變
  • 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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
/**  
 * 微信支付工具類
 * @Author blog.csdn.com/moshowgame
 */  
public class PayUtil {
    public static String doRefund(String mchId,String url, String data) throws Exception{
        /**
         * 注意PKCS12證書 是從微信商戶平臺-》賬戶設定-》 API安全 中下載的
         */

        KeyStore keyStore  = KeyStore.getInstance("PKCS12");
        //P12檔案目錄 證書路徑,這裡需要你自己修改,linux下還是windows下的根路徑
        String filepath = "D:\\";
        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());
        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();
        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();
            }
        } finally {
            httpclient.close();
        }
    }

退款返回

看到如果不是顯示什麼end file of server或者其他FAIL如簽名錯誤的話,就證明成功了,剩下的可能是這些例如"基本賬戶餘額不足,請充值後重新發起"的,賬戶裡存些錢進去就可以退了,核心的業務邏輯已經搞定。
=======================退款RESPONSE資料:

<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wxe09a8f4******]]></appid>
<mch_id><![CDATA[15007******]]></mch_id>
<nonce_str><![CDATA[Lp9JL9qF1tvSxXBb]]></nonce_str>
<sign><![CDATA[B8075C857C9760023CB5A61D49F3138E]]></sign>
<result_code><![CDATA[FAIL]]></result_code>
<err_code><![CDATA[NOTENOUGH]]></err_code>
<err_code_des><![CDATA[基本賬戶餘額不足,請充值後重新發起]]></err_code_des>
</xml>