JAVA之微信小程式支付退款(PKCS12證書設定與SSL請求封裝)
阿新 • • 發佈:2018-12-26
問題背景
話說有小程式支付就有小程式退款,退款和支付是對應的,不能憑空退。
解決方案
解決方案有點長,我們分兩個部分,一個是業務引數拼接與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>