WechatPay-API-v3接口規則
目錄
1、官方文檔
https://wechatpay-api.gitbook.io/wechatpay-api-v3/
2、下面是Java 接入過程:
第一步: 閱讀微信支付分給的接口規則 說明: https://wechatpay-api.gitbook.io/wechatpay-api-v3/
第二步: 微信支付API v3要用第三方CA的證書 所以涉及到 API證書升級
新接入商戶請參考什麼是API證書?如何獲取API證書?。
已經接入並使用微信支付頒發證書的商戶請參考微信支付API證書升級指引(技術人員)。
API v3已不支持使用微信支付頒發的證書。
商戶升級API證書時,需要完成三個步驟:
①:商戶號的超級管理員到商戶平臺升級證書,獲取到權威CA頒發的API證書。 (查看指引)
②:超級管理員將權威CA頒發的API證書(共包含三個文件: 證書pkcs12格式、證書pem格式、證書密鑰pem格式)轉交給技術人員。
③:技術人員用新證書文件替換服務器上原微信支付頒發的API證書,無需對現有系統進行代碼修改。
(注意)這裏升級API證書不影響原有的 API 密鑰 代碼不需要做改動直接替換 apiclient_cert.p12文件即可
第三步: 拿到API證書和密鑰文件.
第四步: 引入微信支付API v3的Apache HttpClient裝飾器: GitHub 地址
注意: 微信支付API v3的Apache HttpClient擴展,實現了請求籤名的生成和應答簽名的驗證。如不想使用次封裝客戶端 可自己實現 簽名和應答解密過程.
相關maven依賴
<dependency>
<groupId>com.xiaomi.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.1.6</version>
</dependency>
第五步: 請求微信支付分API前準備
//微信支付商戶開通後 微信會提供appid
public String appId;
//微信支付商戶開通後 微信會提供appSecret
public String appSecret;
//商戶號
public String mchId;
//32位的api密鑰,微信商戶平臺-賬戶設置-安全設置-api安全 密鑰 用於拉起支付簽名
public String partnerkey;
//openId 是微信用戶針對公衆號的標識,授權的部分這裏不解釋
public String openId;
//微信支付成功後異步通知地址 必須要求80端口並且地址不能帶參數
public String notifyUrl;
//微信支付成功後同步通知地址 必須要求80端口並且地址不能帶參數
public String returnUrl;
//證書apiclient_cert.p12文件位置 可加載
public String certPath;
//微信支付分 分配的服務 ID
public String serviceId;
//v3接口 CA證書 apiclient_key.pem私鑰內容
public String privateKey;
//v3接口 CA證書 apiclient_cert.pem證書內容
public String certificate;
// APIv3密鑰 32 位
public String AES_KEY = "xxx";
//商戶證書序列號 CA證書 可查看微信商戶平臺-賬戶設置-安全設置-api安全密鑰
public String MC_HSERIAL_NO = "xxxxx";
3、敏感信息加解密
https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/min-gan-xin-xi-jia-mi
使用AES-256-GCM,對回調中的關鍵信息進行加密保護
建議從Verifier中獲得微信支付平臺證書,或使用預先下載到本地的平臺證書文件中
X509Certificate wechatpayCertificate = verifier.getValidCertificate();
privateKey 爲 v3接口 CA證書 apiclient_key.pem私鑰內容
將String類型的privateKey 轉化爲PrivateKey類型
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
4、demo參考
構建httpClient,需要設置微信支付平臺證書。
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withWechatpay(wechatpayCertificates) // 加載證書
.build();
在第一次下載平臺證書時,按照下述方法臨時"跳過”應答簽名的驗證
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(response -> true) // NOTE: 設置一個空的應答簽名驗證器,**不要**用在業務請求
.build();
get請求
URIBuilder uriBuilder = new URIBuilder(USER_SERVICE_STATE_URL);
uriBuilder.setParameter("service_id", yourServiceIdxxx);
uriBuilder.setParameter("appid", yourAppIdxxx);
uriBuilder.setParameter("openid", userOpenIdxxx);
CloseableHttpResponse response=null;
try {
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
// NOTE: 建議指定charset=utf-8。低於4.4.6版本的HttpCore,不能正確的設置字符集,可能導致簽名錯誤
response = getHttpDefaultClient().execute(httpGet);
if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
String result = EntityUtils.toString(response.getEntity());// 返回json格式:
return JSONObject.parseObject(result);
}else {
String result = EntityUtils.toString(response.getEntity());// 返回json格式:
log.info("微信支付V3 url={} result={} responseEntity={}",uriBuilder.build(), result,JSON.toJSONString(response.getEntity()));
}
} catch (Exception e) {
log.error("微信支付V3 請求url={}異常 ",uriBuilder.build());
}finally {
if(null!=response){
response.close();
}
}
post請求
HttpPost httpPost = new HttpPost(PAYSCORE_PAYAFTER_ORDERS_URL);
StringEntity reqEntity = new StringEntity(JSONObject.toJSONString(payAfterOrdersModel), ContentType.create("application/json", "utf-8"));
httpPost.setEntity(reqEntity);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-Type", "application/json");
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
String result = EntityUtils.toString(response.getEntity());// 返回json格式:
log.info("微信支付V3 url={} result={} ",url,result);
return JSONObject.parseObject(result);
}else {
String result = EntityUtils.toString(response.getEntity());// 返回json格式:
log.info("微信支付V3 url={} result={} response.getEntity()={}",url,result,JSON.toJSONString(response.getEntity()));
}
} catch (Exception e) {
log.error("微信支付V3 請求url={} 參數={} 異常 e={}",url, JSON.toJSONString(json),e.getMessage());
}finally {
response.close();
}
官方DEMO參考: https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient/blob/master/src/test/java/com/wechat/pay/contrib/apache/httpclient/HttpClientBuilderTest.java
5、回調通知解密算法(AEAD_AES_256_GCM)
我們已一個實際接口爲例,進行講解:
下面我們對resource部分進行解密,解密算法如下:
package com.wsw.sdk.utils;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class WxAPIV3AesUtil {
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
private final byte[] aesKey;
public WxAPIV3AesUtil(byte[] key) {
if (key.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("無效的ApiV3Key,長度必須爲32個字節");
}
this.aesKey = key;
}
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(aesKey, "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)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}
調用示例:
//解密回調信息
byte[] key = SystemConst.WX_KEY.getBytes("UTF-8");
WxAPIV3AesUtil aesUtil = new WxAPIV3AesUtil(key);
String decryptToString = aesUtil.decryptToString(assc.getBytes("UTF-8"),noce.getBytes("UTF-8"),cip);