Introduction: in the development of a project, my business module involves WeChat scan code payment and Alipay scan code payment. After consulting the existing documents, I found that most articles still stay in V2 payment, and some articles introduce V3 payment, but it can't go through. So I read the official documents and formed this concluding article, hoping to help readers.
Official development documents
Official SDK development kit
API dictionary
Wechat platform information configuration
I believe you must have registered and logged in at the moment Wechat open platform I won't repeat it here. The information required for development to be obtained in the platform is as follows:
MERCHANT_ID: merchant number
MERCHANT_SERIAL_NUMBER: serial number of merchant API certificate
MERCHANT_PRIVATEKEY: merchant API private key
AIV3KEY: API v3 key
APP_ID: APPID of applet
Note: if you want to obtain the above information, you must be the merchant's main account (even the administrator, some operations cannot be completed).
Let's get the above parameters one by one
Obtain merchant number
- Log in to wechat open platform
- If it is a primary account number, as shown in the figure, the position in the red box is the merchant number. If it is not the merchant's primary account, the displayed account is the personal account (as shown in the figure is the sub administrator account)
- If the login account is not the primary account, you need to click product center → AppID account management → associated AppID in turn to see the merchant number
Get certificate serial number
This step requires the merchant's primary account to complete.
- Click account center → API security in turn, and you will enter the API Security page. One certificate and two keys can be set on this page. The API certificate is generated according to the tools provided by the website (please refer to the instructions given in the website for specific download operations)
- Decompress the file generated by wechat tool to get the following information. Follow up operations can be carried out according to the certificate instructions txt (prompt: Windows users can directly double-click the apiclient_cert.p12 file to upload the certificate).
- After uploading the API certificate according to the website guidelines, the merchant's main account can directly click the management certificate to view the certificate serial number.
Get API private key
Apiclient in the second step of obtaining the certificate serial number_ key. The PEM file is the required merchant API private key.
Note: there is a small hole here. When pasting the copy key, in addition to keeping the newline of ----- BEGIN PRIVATE KEY ----- and ----- END PRIVATE KEY -----, the newline character generated during copying must be removed when pasting the intermediate content (it is recommended to remove the lower newline character from the newline tool on the Internet after copying the content)
Get APIv3 key
- Still on the API Security page, click settings directly to set the APIv3 key.
- APIv3 key is required to be 32 characters and supports numbers / uppercase and lowercase letters. It can be generated in the online tool for randomly generating strings
- Copy and paste the key generated in the previous step and complete the security authentication of mobile phone number verification code to complete the setting of APIv3 key
Get APPID
The official account and the AppID of the applet are identical, and refer to the guidelines as shown in the figure.
-
Sign in Wechat public platform , click settings → basic settings in turn, and the AppID (applet ID) can be seen at the account information at the bottom of the page
-
On the wechat public platform, click settings → basic settings to find the subject information
-
go back to Wechat open platform , click product center → AppID account management → associated AppID in turn, paste the AppID and subject information obtained in the previous step here and submit
-
Go back to wechat public platform for authorization
Open payment products
- Log in to the wechat open platform and come to the product center page.
- Enable the Native payment function in the payment product.
- Open the enterprise payment to change in the operation tool (the payment funds directly enter the wechat change, and the withdrawal method can open different products according to the user's needs).
Configuration in code
For v3 payment, everything is ready at this time. We only owe Dongfeng. Let's configure the tool classes and third-party requests in the code.
Introduce dependency
v3 payment is introduced here
<dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.3.0</version> </dependency>
Configure wechat tools
This tool class is for reference only, and the configuration information can also be placed in the yml file according to personal preferences (for ease of reading, it is placed in the class as a constant here).
Tool classes here have the following functions:
- Configure merchant information
- Tools and methods required for wechat interface request
- Wechat signature verification
public class WeChartUtil { public static final String MERCHANT_ID = "Merchant id"; public static final String MERCHANT_SERIAL_NUMBER = "Certificate serial number"; public static final String MERCHANT_PRIVATEKEY = "-----BEGIN PRIVATE KEY-----\n" + "Your key content (remember to wrap)" + "\n" + "-----END PRIVATE KEY-----"; public static final String AIV3KEY = "APIv3 secret key"; public static final String NOTIFY_URL = "token url "; public static final String APP_ID = "Applet AppID"; /** * @Author Spence_Dou * @Description Generate order number * @Date 11:35 2021/12/23 * @return void */ public static String orderNo(){ return UUID.randomUUID().toString() .replaceAll("-", "") .substring(0, 32); } /** * @Author Spence_Dou * @Description Generate order expiration time * @Date 11:41 2021/12/23 * @return java.time.LocalDateTime */ public static String timeExpire(){ //Expiration time: 5 minutes later long time = 5*60*1000; SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); Date now = new Date(); //Time after 30 minutes Date afterDate = new Date(now.getTime() + time); return simpleDateFormat.format(afterDate); } /** * @Author Spence_Dou * @Description Two decimal places shall be reserved for the conversion from minute to yuan * @Date 11:38 2021/12/23 * @Param num Conversion amount * @return java.lang.String */ public static BigDecimal transition(Integer num) { BigDecimal bigDecimal1 = new BigDecimal(num + ""); BigDecimal bigDecimal2 = new BigDecimal("100.00"); return bigDecimal1.divide(bigDecimal2).setScale(2); } /** * @Author Spence_Dou * @Description Wechat payment callback signature verification * @Date 16:30 2021/12/23 * @Param serial Request header serial number * @Param message Request message * @Param signature autograph * @return boolean */ public static boolean signVerify(String serial, String message, String signature) { try { PrivateKey key = PemUtil.loadPrivateKey(new ByteArrayInputStream(MERCHANT_PRIVATEKEY.getBytes(StandardCharsets.UTF_8))); ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier( new WechatPay2Credentials(MERCHANT_ID, new PrivateKeySigner(MERCHANT_SERIAL_NUMBER, key)), AIV3KEY.getBytes(StandardCharsets.UTF_8)); return verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature); } catch (Exception e) { e.printStackTrace(); } return false; } /** * @Author Spence_Dou * @Description Decrypt ciphertext * @Date 17:04 2021/12/23 * @Param body Request data * @return java.lang.String */ public static String decryptOrder(String body){ try { AesUtil util = new AesUtil(AIV3KEY.getBytes(StandardCharsets.UTF_8)); ObjectMapper objectMapper = new ObjectMapper(); JsonNode node = objectMapper.readTree(body); JsonNode resource = node.get("resource"); String ciphertext = resource.get("ciphertext").textValue(); String associatedData = resource.get("associated_data").textValue(); String nonce = resource.get("nonce").textValue(); return util.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext); } catch (IOException | GeneralSecurityException e) { e.printStackTrace(); } return null; } /** * @Author Spence_Dou * @Description Close order * @Date 17:34 2021/12/23 * @Param outTradeNo order number * @return void */ public static void closeOrder(String outTradeNo) { PrivateKey key = PemUtil.loadPrivateKey(new ByteArrayInputStream(MERCHANT_PRIVATEKEY.getBytes(StandardCharsets.UTF_8))); // Using a regularly updated signature verifier, you do not need to pass in a certificate ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier( new WechatPay2Credentials(MERCHANT_ID, new PrivateKeySigner(MERCHANT_SERIAL_NUMBER, key)), AIV3KEY.getBytes(StandardCharsets.UTF_8)); WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withMerchant(MERCHANT_ID, MERCHANT_SERIAL_NUMBER, key) .withValidator(new WechatPay2Validator(verifier)); CloseableHttpClient httpClient = builder.build(); HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+outTradeNo+"/close"); httpPost.addHeader("Accept", "application/json"); httpPost.addHeader("Content-type","application/json; charset=utf-8"); try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectMapper objectMapper = new ObjectMapper(); ObjectNode rootNode = objectMapper.createObjectNode(); rootNode.put("mchid",MERCHANT_ID); objectMapper.writeValue(bos, rootNode); httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8")); CloseableHttpResponse response = httpClient.execute(httpPost); String bodyAsString = EntityUtils.toString(response.getEntity()); System.out.println(bodyAsString); } catch (IOException e) { e.printStackTrace(); } } }
Payment request
- The request method refers to the request method in the SDK development kit provided at the beginning of the article
- Different products should refer to API dictionary Request different addresses. Take Native payment as an example
PrivateKey key = PemUtil.loadPrivateKey(new ByteArrayInputStream(WeChartUtil.MERCHANT_PRIVATEKEY.getBytes(StandardCharsets.UTF_8))); // Using a regularly updated signature verifier, you do not need to pass in a certificate ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier( new WechatPay2Credentials(WeChartUtil.MERCHANT_ID, new PrivateKeySigner(WeChartUtil.MERCHANT_SERIAL_NUMBER, key)), WeChartUtil.AIV3KEY.getBytes(StandardCharsets.UTF_8)); WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withMerchant(WeChartUtil.MERCHANT_ID, WeChartUtil.MERCHANT_SERIAL_NUMBER, key) .withValidator(new WechatPay2Validator(verifier)); // The HttpClient constructed by WechatPayHttpClientBuilder will automatically process the signature and signature verification, and automatically update the certificate CloseableHttpClient httpClient = builder.build(); HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native"); httpPost.addHeader("Accept", "application/json"); httpPost.addHeader("Content-type","application/json; charset=utf-8"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectMapper objectMapper = new ObjectMapper(); // Generate order number String outTradeNo = WeChartUtil.orderNo(); // Request parameters ObjectNode rootNode = objectMapper.createObjectNode(); rootNode.put("mchid",WeChartUtil.MERCHANT_ID) .put("appid", WeChartUtil.APP_ID) .put("notify_url", WeChartUtil.NOTIFY_URL) .put("out_trade_no", outTradeNo) .put("time_expire", WeChartUtil.timeExpire()) .put("description", description); // Order description (front-end acquisition) rootNode.putObject("amount") .put("total", 1); // The payment amount here is in cents try { objectMapper.writeValue(bos, rootNode); httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8")); CloseableHttpResponse response = httpClient.execute(httpPost); String bodyAsString = EntityUtils.toString(response.getEntity()); // Here is the data returned after requesting payment JSONObject details = JSONObject.parseObject(bodyAsString); // Payment QR code String qrCode= details.getString("code_url"); /** * Business operations such as order data persistence can be performed here * ... */ } catch (IOException e) { e.printStackTrace(); }
Callback interface
- Here is an obvious difference between v3 payment and v2 payment. The callback address of v2 payment is configured in the official website, while in v3 payment, the callback address is passed to wechat as a request parameter.
- This interface address corresponds to the request parameter notify in payment request_ URL, the specific functions will not be described again
public Map wxCallback(HttpServletRequest request) { Map result = new HashMap(); result.put("code", "FAIL"); try { StringBuilder signStr = new StringBuilder(); signStr.append(request.getHeader("Wechatpay-Timestamp")).append("\n"); signStr.append(request.getHeader("Wechatpay-Nonce")).append("\n"); BufferedReader br = request.getReader(); String str = null; StringBuilder builder = new StringBuilder(); while ((str = br.readLine()) != null){ builder.append(str); } signStr.append(builder.toString()).append("\n"); // Verify signature if (!WeChartUtil.signVerify(request.getHeader("Wechatpay-Serial"), signStr.toString(), request.getHeader("Wechatpay-Signature"))){ result.put("message", "sign error"); return result; } // Decrypt ciphertext String decryptOrder = WeChartUtil.decryptOrder(builder.toString()); // Validate order JSONObject details = JSONObject.parseObject(decryptOrder); // Get order status String ciphertext = details.getString("trade_state"); if ("SUCCESS".equals(ciphertext)){ /** * Business operations such as order status data persistence can be performed here * ... */ result.put("code", "SUCCESS"); result.put("message", "success"); // Close order WeChartUtil.closeOrder(details.getString("out_trade_no")); } } catch (IOException e) { e.printStackTrace(); //Force manual transaction rollback TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return result; }