Hello, I'm ling of Mengxin studio. Recently, in the process of making relevant software, I need to use the complaint risk compliance of wechat merchant ID. I stepped on many holes for the first time, and then made the following packaging for ease of use. In fact, it's not difficult, It's mainly because I didn't read the guidance provided by wechat carefully (it continues to be simplified based on the official wechatpay-apache-httpclient-0.2.2.jar). Let's talk about how to write it
First prepare the following relevant materials:
1. Merchant number
2. The merchant ID ApiV3 secret key needs to be set in the background of wechat merchant ID
3. The private key of the merchant ID certificate. There will be the apiclient when downloading the merchant ID certificate_ key. PEM file is it
4. Merchant ID certificate serial number, which can be viewed in the wechat merchant ID certificate management background
After you have prepared the above information, you can start writing
Step 1: load the certificate and get the httpCilent object:
public void setup() { if (!doCheckParam()) { isStep = false; return; } try { privateKey = new String(Files.readAllBytes(Paths.get(privateKeyFilePath)), "utf-8"); // Load merchant private key (privateKey: private key string) merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8"))); // Load platform certificate (mchId: merchant number, mchSerialNo: merchant certificate serial number, apiV3Key: V3 key) AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8")); // Initialize httpClient httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey) .withValidator(new WechatPay2Validator(verifier)).build(); isStep = true; } catch (Exception e) { // TODO: handle exception errorHint = errorHint.toString(); isStep = false; } }
Step 2: initiate and create a complaint callback notice. Other operations are similar
/** * Create complaint callback notification * * @param url * token url * @return * @throws Exception */ public String CreateComplaintsNotify(String url) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications"); JSONObject dataJSON = new JSONObject(); dataJSON.put("url", url); StringEntity entity = new StringEntity(dataJSON.toString()); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpPost); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; }
It should be noted here that the API package (wechatpay-apache-httpclient-0.2.2.jar) provided by wechat has its own Authorization Header when initiating the request, so there is no need to add this Header, otherwise an error will be reported. The landlord has been trapped here for a long time and found it only after turning over the bad document
And if there is java security. Invalidkeyexception: if the legal key size wechatpayhttpclientbuild is wrong, please pay attention to the java version. If it is below java 8u 162, please change the java version. It must be above java 8u 162. The early java operation limits the length of the secret key supported by JCE, that is, 256 bit AES is not supported by default. The building owner here has been cheated for a long time, I tried many ways to find it. In exchange, the java version is low
Here are all code examples:
package com.mx.util; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import org.apache.commons.codec.digest.DigestUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; import com.wechat.pay.contrib.apache.httpclient.WechatPayUploadHttpPost; import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier; import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; 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.util.PemUtil; import net.sf.json.JSONArray; import net.sf.json.JSONObject; public class WeChatAPIV3 { CloseableHttpClient httpClient; String mchId = "Merchant number"; String mchSerialNo = "Certificate serial number"; String apiV3Key = "APIV3 Secret key"; String privateKeyFilePath = "Certificate private key path"; String privateKey = ""; PrivateKey merchantPrivateKey; String errorHint = ""; public String getMchId() { return mchId; } public void setMchId(String mchId) { this.mchId = mchId; } public String getMchSerialNo() { return mchSerialNo; } public void setMchSerialNo(String mchSerialNo) { this.mchSerialNo = mchSerialNo; } public String getApiV3Key() { return apiV3Key; } public void setApiV3Key(String apiV3Key) { this.apiV3Key = apiV3Key; } public String getPrivateKeyFilePath() { return privateKeyFilePath; } public void setPrivateKeyFilePath(String privateKeyFilePath) { this.privateKeyFilePath = privateKeyFilePath; } private boolean isStep = false; public WeChatAPIV3() { } public WeChatAPIV3(String mchId, String mchSerialNo, String apiV3Key, String privateKeyFilePath) { this.mchId = mchId; this.mchSerialNo = mchSerialNo; this.apiV3Key = apiV3Key; this.privateKeyFilePath = privateKeyFilePath; } public String rsaDecryptOAEP(String ciphertext) throws BadPaddingException, IOException { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } if (merchantPrivateKey == null) { errorHint = "The private key was not loaded successfully"; return null; } try { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); cipher.init(Cipher.DECRYPT_MODE, merchantPrivateKey); byte[] data = Base64.getDecoder().decode(ciphertext); return new String(cipher.doFinal(data), "utf-8"); } catch (NoSuchPaddingException | NoSuchAlgorithmException e) { throw new RuntimeException("current Java Environment not supported RSA v1.5/OAEP", e); } catch (InvalidKeyException e) { throw new IllegalArgumentException("Invalid private key", e); } catch (BadPaddingException | IllegalBlockSizeException e) { throw new BadPaddingException("Decryption failed"); } } public boolean doCheckParam() { return doCheckValue(mchId, mchSerialNo, apiV3Key, privateKeyFilePath); } public boolean doCheckValue(String... item) { for (String each : item) { if (each == null || each.length() == 0) { errorHint = "Missing required parameters"; return false; } } return true; } public void setup() { if (!doCheckParam()) { isStep = false; return; } try { privateKey = new String(Files.readAllBytes(Paths.get(privateKeyFilePath)), "utf-8"); // Load merchant private key (privateKey: private key string) merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8"))); // Load platform certificate (mchId: merchant number, mchSerialNo: merchant certificate serial number, apiV3Key: V3 key) AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8")); // Initialize httpClient httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey) .withValidator(new WechatPay2Validator(verifier)).build(); isStep = true; } catch (Exception e) { // TODO: handle exception errorHint = errorHint.toString(); isStep = false; } } /** * Query complaint details * * @param complaint_id * Complaint No * @return * @throws Exception */ public String GetComplaintsInfo(String complaint_id) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpGet httpGet = new HttpGet( "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/" + complaint_id); httpGet.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpGet); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; } /** * Query negotiation history * * @param complaint_id * Complaint No * @param offset * Start position * @param limit * Number of returned data * @return * @throws Exception */ public String GetComplaintsHis(String complaint_id, int offset, int limit) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/" + complaint_id + "/negotiation-historys?limit=" + limit + "&offset=" + offset); httpGet.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpGet); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; } /** * Query complaint list * * @param offset * Start position * @param limit * Number of returned data * @param begin_date * Start date yyyy MM DD * @param end_date * The end date yyyy MM DD has a maximum time difference of one month * @return Query results * @throws Exception */ public String GetComplaintsList(int offset, int limit, String begin_date, String end_date) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpGet httpGet = new HttpGet( "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2?limit=" + limit + "&offset=" + offset + "&begin_date=" + begin_date + "&end_date=" + end_date + "&complainted_mchid=" + mchId); httpGet.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpGet); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; } public void after() throws IOException { httpClient.close(); } /** * Create complaint callback notification * * @param url * token url * @return * @throws Exception */ public String CreateComplaintsNotify(String url) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications"); JSONObject dataJSON = new JSONObject(); dataJSON.put("url", url); StringEntity entity = new StringEntity(dataJSON.toString()); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpPost); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; } /** * Update complaint callback notification * * @param url * callback notification * @return * @throws Exception */ public String UpdateComplaintsNotify(String url) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpPut httpPut = new HttpPut("https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications"); JSONObject dataJSON = new JSONObject(); dataJSON.put("url", url); StringEntity entity = new StringEntity(dataJSON.toString()); entity.setContentType("application/json"); httpPut.setEntity(entity); httpPut.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpPut); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; } /** * Delete complaint callback notification address * * @return * @throws Exception */ public String DelComplaintsNotify() throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpDelete httpDel = new HttpDelete( "https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications"); httpDel.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpDel); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; } /** * Submit reply * * @param complaint_id * Complaint No * @param response_content * Reply content * @param response_images * Reply picture * @return * @throws Exception */ public String ReplyInfo(String complaint_id, String response_content, String response_images) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpPost httpPost = new HttpPost( "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/" + complaint_id + "/response"); // Request body parameter JSONObject dataJSON = new JSONObject(); dataJSON.put("complainted_mchid", mchId); dataJSON.put("response_content", response_content); String[] imgs = response_images.split(","); JSONArray array = new JSONArray(); for (String img : imgs) { array.add(img); } StringEntity entity = new StringEntity(dataJSON.toString()); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpPost); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; } /** * Feedback processing completed * * @param complaint_id * Complaint No * @return * @throws Exception */ public String CompleteComplaints(String complaint_id) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; // Request URL HttpPost httpPost = new HttpPost( "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/" + complaint_id + "/complete"); JSONObject dataJSON = new JSONObject(); dataJSON.put("complainted_mchid", mchId); StringEntity entity = new StringEntity(dataJSON.toString()); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json"); // Complete signing and execute request CloseableHttpResponse response = httpClient.execute(httpPost); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } return result; } /** * Upload pictures * * @param filePath * Picture path * @return * @throws Exception */ public String uploadImg(String filePath) throws Exception { if (!isStep) { errorHint = "Not successfully enabled Step"; return null; } String result = null; URI uri = new URI("https://api.mch.weixin.qq.com/v3/merchant-service/images/upload"); File file = new File(filePath); try (FileInputStream ins1 = new FileInputStream(file)) { String sha256 = DigestUtils.sha256Hex(ins1); try (InputStream ins2 = new FileInputStream(file)) { HttpPost request = new WechatPayUploadHttpPost.Builder(uri).withImage(file.getName(), sha256, ins2) .build(); CloseableHttpResponse response = httpClient.execute(request); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Processing succeeded result = EntityUtils.toString(response.getEntity()); } else if (statusCode == 204) { // Processing succeeded, no Body returned result = "{'code':204}"; } else { result = EntityUtils.toString(response.getEntity()); } } finally { response.close(); } } } return result; } }
There is also a decrypted encapsulation class:
package com.mx.util; 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 AesUtil { static final int KEY_LENGTH_BYTE = 32; static final int TAG_LENGTH_BIT = 128; private final byte[] aesKey; /** * Create decryption class * * @param key */ public AesUtil(byte[] key) { if (key.length != KEY_LENGTH_BYTE) { throw new IllegalArgumentException("invalid ApiV3Key,The length must be 32 bytes"); } this.aesKey = key; } /** * Decrypt data * * @param associatedData * Additional packets * @param nonce * Random string initialization vector used for encryption * @param ciphertext * Base64 Coded ciphertext * @return * @throws GeneralSecurityException * @throws IOException */ 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); } } }
The above is the encapsulated API of the landlord, which is easy to use. Here is how to call it:
public static void main(String[] args) { WeChatAPIV3 apiv3 = new WeChatAPIV3(); try { apiv3.setMchId("Merchant number"); apiv3.setMchSerialNo("Certificate serial number"); apiv3.setApiV3Key("APIV3 Secret key"); apiv3.setPrivateKeyFilePath("Certificate private key path"); apiv3.setup(); // Query complaint list // System. out. Print (apiv3. Getcomplaintslist) (how many pieces of data are returned from the first few, // "Start date format: yyyy MM DD", "end date format: yyyy MM DD"); // Query complaint details // System.out.print(apiv3.GetComplaintsInfo("complaint number"); // Query complaint history // System. out. Print (apiv3. Getcomplaints his ("complaint number"), starting from the first few, // How many pieces of data are returned); // Create complaint callback notification // System.out.print(apiv3.CreateComplaintsNotify("notification address"); // Update complaint callback notification // System.out.print(apiv3.UpdateComplaintsNotify("notification address"); // Decryption phone // System.out.print(apiv3.rsaDecryptOAEP( // "Ciphertext"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
You just fill in the corresponding parameters, then call the above method, is it a lot simpler? Wow, you don't know. I suppressed it for a long time. - = - - the main reason is that the landlord himself didn't read the API document seriously.
github portal: https://github.com/wintton/MxWxPayUtil.git
Path: Src / COM / MX / util / wechatapiv3 java