Writing in the front
Now wechat Pay is everywhere, as a back-end developer, with me to see how wechat pay in the end how to apply their own projects
If you are not the WeChat third-party service ecosystem, please first read the WeChat with ali cloud some concepts of the third party process comb, believe you will after reading of WeChat third party service had certain understanding, can be prepared in accordance with the requirements under the opened the essential condition of WeChat pay, and opened APIv3 certificate, get some essential parameters.
If you are not familiar with the basics of cryptography, it is best to read the basics of cryptography that you have to know during development. Based on this common sense, understanding this article will be twice as effective.
Familiar with official documents
If you have tried to browse the official documentation /SDK of wechat Pay before, you may be as confused as I am. So I’m going to walk you through the documentation.
Native Payments, for example, starts with development guidelines. Here, wechat pay has summarized the realization of wechat pay process.
Firstly, the wechat payment interface is based on APIv3 (post an official picture to illustrate).
In a word, APIv3 uses JSON as the data interaction format, the interface follows the REST style, and uses the efficient and secure SHA256-RSA signature algorithm to ensure data integrity and requestor identity. The HTTPS client certificate is not required. The public key and signature content are exchanged by the certificate serial number. Aes-256-gcm symmetric encryption is used to ensure data privacy.
As a developer, there are two things that you need to keep safe and important
- Merchant API Certificate
The merchant certificate encapsulates the public key and signature content for sending requests and verifying signatures
- Merchant API private key
When a merchant applies for a merchant API certificate, the merchant private key is generated and stored in the apiclient_key.pem file in the local certificate folder. The private key is used to obtain the AES encryption password and decrypt the encryption content
Secondly, three SDK versions of JAVA,PHP and GO are provided in the preparation for development, which encapsulates the functions of signature generation, signature verification, encryption and decryption of sensitive information, media file uploading and so on, which is convenient for us to use directly without handwritten operations. If you are not comfortable with the official SDK, you can implement it yourself. The official implementation idea is also given:
There are also quick tests:
Then, the implementation logic of business flow chart and corresponding functions (order, check order, close order, callback payment) is provided in fast access
Ignoring some of the details of the official schematic, I drew a sequence diagram of the flow that is easier to understand:
Here’s what we need to do: learn how to use the SDK to encapsulate a series of security processes, that is, build an automated HttpClient, and then implement each API request!
Understand SDK documentation
To prepare
This section uses Java as an example to analyze the README document of the official SDK
The document address here suggests that you download the source code to the local, more convenient to read the source code, to understand the implementation details
I wrote this article when
mysql> select now(a); +---------------------+
| now() |
+---------------------+14:39:46 | 2022-03-11 | +---------------------+
1 row in set (0.03 sec)
Copy the code
Use the latest version of SDK wechatPay-Apache-HttpClient 0.4.2
Based on JDK1.8+ Maven dependency for
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.2</version>
</dependency>
Copy the code
start
WechatPayHttpClient WechatPayHttpClient WechatPayHttpClient WechatPayHttpClient WechatPayHttpClient WechatPayHttpClient WechatPayHttpClient WechatPayHttpClient WechatPayHttpClient
The next step is to use the HttpClient to encapsulate a simple instance of the request header and request body
Fill in the pit
In the preceding method, you need to manually download and update the certificate. A workaround is given at the back of the README
Different from the above, the certificate Manager is used to implement centralized management of verification and certificate update
The callback scheme
Here is how to use the SDK to verify the callback signature and decrypt the returned data. A little analysis shows that the SDK provides NotificationRequest and NotificationHandler to do this.
Now feel meng force do not be nervous, the following combination of specific examples to explain
Began to work
Prepare again
I believe that after reading the above general analysis of wechat documents and SDK documents, I have a good idea about how the process is. Let’s get to work.
Development guide provides a general solution (how to load the merchants private key and certificate of loading platform, initialize the httpClient), except that the AutoUpdateCertificatesVerifier in the latest SDK has been abandoned
However, the developers offer a better solution:
This tool integrates the functions of obtaining the certificate, downloading the certificate, updating the certificate regularly and obtaining the checker
Look at the structure of this class:
Obviously, this is the design of the singleton pattern. The getInstance method gets a unique instance that you can put in the certificate, stop downloading updates, and get the checker.
Register global beans for business use
After the above analysis, it is not difficult to conclude that. Get the certificate Manager instance, place the certificate, enable automatic download and update, and remove the validator. Register the global Bean in the SpringBoot service and be ready to go!
Of course, it is best to configure the required parameters in YML and read them into WxPayConfig:
wxpay:
# # merchants
mch-id: 1* * * * * * 42
# API certificate serial number
mch-serial-no: Your API certificate serial number
# Merchant private key file
private-key-path: C:\Users\cheung0\Desktop\apiclient_key.pem
# APIv3 key
api-v3-key: w*************x
# APPID
appid: wx3*********46
# wechat server address
domain: https://api.mch.weixin.qq.com
Accept result notification address
notify-domain: http://maiqu.sh1.k9s.run:2271
Copy the code
Notify-domain is the address requested by the wechat server when the notification is called back. To perform a local test, use the Intranet penetration tool to open a tunnel. SuiDao is recommended for me
Then configure to
@Data
@Slf4j
@Component
@ConfigurationProperties(prefix = "wxpay")
public class WxPayConfig {
/ / merchants
private String mchId;
// API certificate serial number
private String mchSerialNo;
// Private key address
private String privateKeyPath;
/ / APIv3 keys
private String apiV3Key;
// APPID
private String appid;
// wechat server address
private String domain;
// Address for receiving result notification
private String notifyDomain;
}
Copy the code
It is recommended to add POM dependencies for better dependencies between configurations and entities:
<! -- Configure mapping -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Copy the code
Now, you can write your own test to see if it works
Next, register the Bean
Since the private key needs to be loaded in multiple places, a Bean that returns the contents of the private key is registered
/** * Load merchant private key <br> *@return PrivateKey
*/
@Bean
public PrivateKey getPrivateKey(a) throws IOException {
// load the merchant privateKey (privateKey: privateKey string)
log.info("Start loading private key, read contents...");
String content = new String(Files.readAllBytes(Paths.get(privateKeyPath)),StandardCharsets.UTF_8 );
return PemUtil.loadPrivateKey(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
}
Copy the code
PemUtil is a utility class provided by the SDK that helps us read the private key:
public static PrivateKey loadPrivateKey(String privateKey) {
privateKey = privateKey.replace("-----BEGIN PRIVATE KEY-----"."").replace("-----END PRIVATE KEY-----"."").replaceAll("\\s+"."");
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException var2) {
throw new RuntimeException("Current Java environment does not support RSA", var2);
} catch (InvalidKeySpecException var3) {
throw new RuntimeException("Invalid key format"); }}Copy the code
Obtain a check device
/** * Get the platform certificate manager, update the certificate periodically (default value is UPDATE_INTERVAL_MINUTE) * <br> * Return the validator instance, register it as a bean, and use ** in real business@return* /
@Bean
public Verifier getVerifier(PrivateKey merchantPrivateKey) throws IOException, NotFoundException {
log.info("Load certificate Manager instance");
// Get the certificate manager singleton instance
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// Add merchant information to the certificate manager that requires the platform certificate to be automatically updated
log.info("Add merchant information to certificate Manager and enable automatic update");
try {
// The underlying implementation of this method is synchronous thread update certificate
// see the beginScheduleUpdate() method
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
} catch (GeneralSecurityException | HttpCodeException e) {
e.printStackTrace();
}
log.info("Get the checker from the Certificate Manager");
return certificatesManager.getVerifier(mchId);
}
Copy the code
At this point, obtaining attestation device also opened regularly download the update certificate, in certificatesManager. PutMerchant method:
public synchronized void putMerchant(String merchantId, Credentials credentials, byte[] apiV3Key) throws IOException, GeneralSecurityException, HttpCodeException {
if(merchantId ! =null && !merchantId.isEmpty()) {
if (credentials == null) {
throw new IllegalArgumentException("Credentials for empty");
} else if (apiV3Key.length == 0) {
throw new IllegalArgumentException("ApiV3Key is empty");
} else {
if (this.certificates.get(merchantId) == null) {
this.certificates.put(merchantId, new ConcurrentHashMap());
}
this.initCertificates(merchantId, credentials, apiV3Key);
this.credentialsMap.put(merchantId, credentials);
this.apiV3Keys.put(merchantId, apiV3Key);
if (this.executor == null) {
this.beginScheduleUpdate(); }}}else {
throw new IllegalArgumentException("MerchantId is empty"); }}Copy the code
private void beginScheduleUpdate(a) {
this.executor = new SafeSingleScheduleExecutor();
Runnable runnable = () -> {
try {
Thread.currentThread().setName("scheduled_update_cert_thread");
log.info("Begin update Certificates.Date:{}", Instant.now());
this.updateCertificates();
log.info("Finish update Certificates.Date:{}", Instant.now());
} catch (Throwable var2) {
log.error("Update Certificates failed", var2); }};this.executor.scheduleAtFixedRate(runnable, 0L.1440L, TimeUnit.MINUTES);
}
Copy the code
When the SpringBoot service starts, a thread named “scheduled_update_cert_thread” is created in the thread pool to periodically download and update the certificate
Access to HttpClient
/ * * * through WechatPayHttpClientBuilder tectonic HttpClient * *@param verifier
* @return* /
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier,PrivateKey merchantPrivateKey) throws IOException {
log.info("Tectonic httpClient");
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
/ / by HttpClient WechatPayHttpClientBuilder structure, automatically processing signatures and attestation, certificates and updated automatically
CloseableHttpClient httpClient = builder.build();
log.info("HttpClient construction succeeded");
return httpClient;
}
Copy the code
Gets the payment callback request handler
/** * Build wechat pay callback request handler **@param verifier
* @return NotificationHandler
*/
@Bean
public NotificationHandler notificationHandler(Verifier verifier) {
return new NotificationHandler(verifier,apiV3Key.getBytes(StandardCharsets.UTF_8));
}
Copy the code
Start the service for testing
The 2022-03-11 15:54:27. 4944-683 the INFO/restartedMain T.M.M etrichall. Wxpay. Config. WxPayConfig: start loading the private key, read the content... The 2022-03-11 15:54:27. 4944-697 the INFO/restartedMain T.M.M etrichall. Wxpay. Config. WxPayConfig: Load the certificate manager instance 15:54:27 2022-03-11. 4944-698 the INFO/restartedMain T.M.M etrichall. Wxpay. Config. WxPayConfig: Increase business information to the certificate manager, and open automatically updates the 2022-03-11 15:54:28. 4944-363 the INFO/restartedMain T.M.M etrichall. Wxpay. Config. WxPayConfig: From obtaining the attestation certificate manager 15:54:28 2022-03-11. 4944-365 the INFO/restartedMain T.M.M etrichall. Wxpay. Config. WxPayConfig: Tectonic httpClient 15:54:28 2022-03-11. 4944-364 the INFO/ate_cert_thread C.W.P.C.A.H.C ert. CertificatesManager: Begin Update Certificates.Date: 2022-03-11T07:54:28.364z 2022-03-11 15:54:28.367 INFO 4944 -- [restartedMain] t.m.metrichall.wxpay.config.WxPayConfig : Tectonic httpClient 15:54:28 2022-03-11 success. 4944-626 the INFO/ate_cert_thread C.W.P.C.A.H.C ert. CertificatesManager: Finish the update Certificates. Date: 2022-03-11 T07:54:28. 626 zCopy the code
As you can see, our registered Bean instance has started, the download update certificate thread has started, and everything is ready to go
Centrally encapsulate some enumerated classes
@AllArgsConstructor
@Getter
public enum PayType {
/** * wechat */
WXPAY("WeChat"),
/** ** alipay */
ALIPAY("Alipay");
/** * type */
private final String type;
}
Copy the code
@AllArgsConstructor
@Getter
public enum OrderStatus {
/** ** not paid */
NOTPAY("Unpaid"),
/** * Paid successfully */
SUCCESS("Payment successful"),
/** * closed */
CLOSED("Timeout is closed"),
/** ** cancelled */
CANCEL("User cancelled"),
/** ** The refund is */
REFUND_PROCESSING("Refund in progress"),
/** * has been refunded */
REFUND_SUCCESS("Refunded"),
/** * The refund is abnormal */
REFUND_ABNORMAL("Refund exception");
/** * type */
private final String type;
}
Copy the code
@AllArgsConstructor
@Getter
public enum WxApiType {
/**
* Native下单
*/
NATIVE_PAY("/v3/pay/transactions/native"),
/** ** order */
ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),
/** * close the order */
CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),
/** * Apply for a refund */
DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),
/** * query single refund */
DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),
/** * request transaction bill */
TRADE_BILLS("/v3/bill/tradebill"),
/** ** Apply for fund bill */
FUND_FLOW_BILLS("/v3/bill/fundflowbill");
/** * type */
private final String type;
}
Copy the code
@AllArgsConstructor
@Getter
public enum WxNotifyType {
/**
* 支付通知
*/
NATIVE_NOTIFY("/api/wx-pay/native/notify"),
/** * Refund result notification */
REFUND_NOTIFY("/api/wx-pay/refunds/notify");
/** * type */
private final String type;
}
Copy the code
@AllArgsConstructor
@Getter
public enum WxRefundStatus {
/** * Successful */
SUCCESS("SUCCESS"),
/**
* 退款关闭
*/
CLOSED("CLOSED"),
/** ** Refund processing */
PROCESSING("PROCESSING"),
/** * The refund is abnormal */
ABNORMAL("ABNORMAL");
/** * type */
private final String type;
}
Copy the code
@AllArgsConstructor
@Getter
public enum WxTradeState {
/** * Paid successfully */
SUCCESS("SUCCESS"),
/** ** not paid */
NOTPAY("NOTPAY"),
/** * closed */
CLOSED("CLOSED"),
/** * transfer to refund */
REFUND("REFUND");
/** * type */
private final String type;
}
Copy the code
Values in enumerated classes are often used in business and are more elegantly encapsulated as enumerated classes
Encapsulating response message
@Data
@Accessors(chain = true)
public class R {
private Integer code; / / the response code
private String message; // Response message
private Map<String, Object> data = new HashMap<>();
public static R ok(a){
R r = new R();
r.setCode(0);
r.setMessage("Success");
return r;
}
public static R error(a){
R r = new R();
r.setCode(-1);
r.setMessage("Failure");
return r;
}
public R data(String key, Object value){
this.data.put(key, value);
return this; }}Copy the code
The @accessors (chain = true) annotation makes the class method chain-able:
return R.ok().setMessage("Order successful!")
Copy the code
Native place the order
The Native order API dictionary tells us the necessary parameters and provides a sample request:
{
"mchid": "1900006XXX"."out_trade_no": "native12177525012014070332333"."appid": "wxdace645e0bc2cXXX"."description": "Image Shop - Shenzhen Tengda -QQ dolls"."notify_url": "https://weixin.qq.com/"."amount": {
"total": 1."currency": "CNY"}}Copy the code
Return example:
{
"code_url": "weixin://wxpay/bizpayurl? pr=p4lpSuKzz"
}
Copy the code
If successful, you’ll get a link to the QR code
As an example, wrap our own request body:
/** * Create order, call Native payment interface **@param productId
* @returnCode_url and Order number *@throws Exception
*/
@Transactional(rollbackFor = Exception.class)
@Override
public Map<String, Object> nativePay(Long productId) throws Exception {
log.info("Generate order");
// Generate order...
// Check whether the link to the qr code already exists. Direct retun: Go down...
log.info("Call unified Order API");
// call the unified order API
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
httpPost.addHeader("Accept"."application/json");
httpPost.addHeader("Content-type"."application/json; charset=utf-8");
// Request the body argument
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("mchid", wxPayConfig.getMchId());
paramsMap.put("out_trade_no", orderInfo.getOrderNo());
paramsMap.put("appid", wxPayConfig.getAppid());
paramsMap.put("description", orderInfo.getTitle());
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
/ / assembly amount
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("total", orderInfo.getTotalFee());
amountMap.put("currency"."CNY");
paramsMap.put("amount", amountMap);
// Convert the parameter to a JSON string
String jsonParams = JSON.toJSONString(paramsMap);
log.info("Request parameters ===> {}" + jsonParams);
// Configure the request body
httpPost.setEntity(new StringEntity(jsonParams, "UTF-8"));
// Complete the signing and execute the request
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
String bodyAsString = EntityUtils.toString(response.getEntity());/ / response body
int statusCode = response.getStatusLine().getStatusCode();// Response status code
if (statusCode == 200) { // The processing succeeded
log.info("Success, return result =" + bodyAsString);
} else if (statusCode == 204) { // Processing is successful, no Body is returned
log.info("Success");
} else {
log.info("Native order failed, response code =" + statusCode + ", return result =" + bodyAsString);
throw new IOException("request failed");
}
// Response result
Map<String, String> resultMap = JSON.parseObject(bodyAsString, HashMap.class);
/ / qr code
codeUrl = resultMap.get("code_url");
// Save the link to the qr code...
// Return the qr code
Map<String, Object> map = new HashMap<>();
map.put("codeUrl", codeUrl);
map.put("orderNo", orderInfo.getOrderNo());
returnmap; }}Copy the code
The omitted part is personalized development, using Mybatis/MybatisPlus/JPA and other tools to add, delete, change and check the database, the same in the following similar places
API request take a look:
Returns a JSON:
{
"code": 0."message": "Success"."data": {
"codeUrl": "weixin://wxpay/bizpayurl? pr=HVPisQfzz"."orderNo": "ORDER_20220311155916957"}}Copy the code
CodeUrl is the link of our TWO-DIMENSIONAL code, the back end can use Zxing tool to parse into two-dimensional code image binary stream back to the front end. I leave it to the front end students to do their own optimization.
Test the codeUrl here using QRcode
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Pay for the test</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
</head>
<body>
<button onclick="displayDate()">Click on the pay</button>
<div id="myQrcode"></div>
<script>
function displayDate() {
jQuery('#myQrcode').qrcode({
text: 'weixin://wxpay/bizpayurl? pr=HVPisQfzz'
});
}
</script>
</body>
</html>
Copy the code
Cancel the order
The cancel ORDER API dictionary tells us the parameters we need:
{
"mchid": "1230000109"
}
Copy the code
The merchant number is placed in the request body, and the order number is spliced in the URL
/ close single interface call * * * * < p > * API dictionary: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml * *@param orderNo
*/
private HashMap<String, String> closeOrder(String orderNo) throws Exception {
log.info("Close single interface call, order number ===> {}", orderNo);
// Create a remote request object
String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url);
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Accept"."application/json");
httpPost.addHeader("Content-type"."application/json; charset=utf-8");
// Request the body argument
Map<String, String> paramsMap = new HashMap<>();
paramsMap.put("mchid", wxPayConfig.getMchId());
String jsonParams = JSON.toJSONString(paramsMap);
log.info("Request parameters ===> {}", jsonParams);
StringEntity entity = new StringEntity(jsonParams, "UTF-8");
entity.setContentType("application/json");
// Set the request parameters to the request object
httpPost.setEntity(entity);
// Complete the signing and execute the request
CloseableHttpResponse response = httpClient.execute(httpPost);
HashMap<String, String> res = new HashMap<>();
try {
if (response.getStatusLine().getStatusCode() == 200 || response.getStatusLine().getStatusCode() == 204 ) {
res.put("code"."SUCCESS");
res.put("message"."This order has been successfully closed.");
return res;
}
String bodyAsString = EntityUtils.toString(response.getEntity());
res = JSON.parseObject(bodyAsString,HashMap.class);
return res;
} catch (IOException | ParseException e) {
res.put("code"."ERROR");
if(e.toString() ! =null && !e.toString().equals("")) {
res.put("message", e.toString());
} else {
res.put("message"."Unknown error occurred");
}
returnres; }}Copy the code
Print the return body to get specific information. The API dictionary also explains this.
If successful, the return body is empty and the status code is 200 or 204. If it fails, for example:
Query order
Query the order API dictionary
/** * Order details can be queried through "wechat Pay order number query" and "merchant order number query" two ways * <p> * here through the latter query * <p> * API dictionary: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml * *@param orderNo
* @return
* @throws Exception
*/
@Override
public String queryOrder(String orderNo) throws Exception {
log.info("Lookup interface call ===> {}", orderNo);
// Concatenate the request's third-party API
String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url).concat("? mchid=").concat(wxPayConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept"."application/json");
// Complete the signing and execute the request
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());/ / response body
int statusCode = response.getStatusLine().getStatusCode();// Response status code
if (statusCode == 200) { // The processing succeeded
log.info("Success, return result =" + bodyAsString);
} else if (statusCode == 204) { // Processing is successful, no Body is returned
log.info("Success");
} else {
log.info(Lookup interface call, response code = + statusCode + ", return result =" + bodyAsString);
throw new IOException("request failed");
}
return bodyAsString;
} finally{ response.close(); }}Copy the code
The API test:
Returns a JSON:
{
"code": 0."message": "Query successful"."data": {
"result": {
"mchid": "162 * * * * 542"."out_trade_no": "ORDER_20220311155916957"."trade_state": "CLOSED"."promotion_detail": []."appid": "wx32d4*******79746"."trade_state_desc": "Order closed"."attach": ""."payer": {}}}}Copy the code
Pay the callback
When users place an order, the wechat server will request our server to inform us of the payment result. But it’s not safe because we can’t be sure where the request server is coming from. What if it’s a malicious request from a hacker? Therefore, wechat strongly suggests that we conduct signature verification to confirm whether we are requested by the wechat Pay server and whether the data is complete and not tampered with during the process
The Payment callback API dictionary explains in detail the contents of the Request and decrypted Resource, as well as how we should respond to the wechat server. If the response received by wechat does not meet the standards or exceeds the time limit, wechat will consider the notification as a failure. Wechat will periodically initiate the notification through certain strategies to improve the success rate of the notification as much as possible. However, wechat does not guarantee the final success of the notification. (notice frequency for 15 s/s / 30/10 m/s / 3 m 20 30 30 m/m/m/m / 60 m / 3 h / 3 h / 3 h / 6 h/h – a total of 24 h4m)
In the SDK documentation of the callback scheme I posted the SDK provided practices, here I do the same
/** * Payment notification <br> * wechat Pay will notify the merchant of the success of payment by the user through the payment notification interface. < BR > * Merchant shall return a reply Wechat periodically re-initiates notifications through certain policies. Encryption is not guaranteed to come from wechat. Wechat signs notifications sent to merchants and places the Signature value in the notification's HTTP header wechatPay-signature <br> * *@param request
* @param response
* @returnIn response to the map * /
@apiOperation (" Payment advice ")
@PostMapping("/native/notify")
public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
// Reply object
Map<String, String> map = new HashMap<>();
try {
// Process parameters
String serialNumber = request.getHeader("Wechatpay-Serial");
String nonce = request.getHeader("Wechatpay-Nonce");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String signature = request.getHeader("Wechatpay-Signature");// Request header wechatpay-signature
// Get the request body
String body = HttpUtils.readData(request);
// Construct the wechat request body
NotificationRequest wxRequest = new NotificationRequest.Builder().withSerialNumber(serialNumber)
.withNonce(nonce)
.withTimestamp(timestamp)
.withSignature(signature)
.withBody(body)
.build();
Notification notification = null;
try {
/ * * * use WeChat pays the callback request processor analytical structural WeChat request body * in the process will be carried out in signature verification, and decrypting encrypted signature * source: com. Wechat. Pay. Contrib.. Apache httpclient. Cert. Line 271 begins * Decrypt source code: Com. Wechat. Pay. Contrib. Apache. Httpclient. Notification 76 rows * com. Wechat. Pay. Contrib.. Apache httpclient. Notification 147 rows Use of a private key to obtain AesUtil * com. Wechat. Pay. Contrib.. Apache httpclient. Notification 147 lines using Aes symmetric * / decryption for the original
notification = notificationHandler.parse(wxRequest);
} catch (Exception e) {
log.error("Failed to notify visa inspection");
// Failed to reply
response.setStatus(500);
map.put("code"."ERROR");
map.put("message"."Failed to notify visa inspection");
return JSON.toJSONString(map);
}
// Get decrypted messages from the Notification and parse them into HashMap
String plainText = notification.getDecryptData();
log.info("Notification of successful visa inspection");
// Process orders
wxPayService.processOrder(plainText);
// Successful reply
response.setStatus(200);
map.put("code"."SUCCESS");
map.put("message"."Success");
return JSON.toJSONString(map);
} catch (Exception e) {
e.printStackTrace();
// Failed to reply
response.setStatus(500);
map.put("code"."ERROR");
map.put("message"."Failure");
returnJSON.toJSONString(map); }}Copy the code
Avoid pit: the serialNumber parameter value is not the one we configured in yml. Wechat will send a new certificate serialNumber to the request header. We have to concatenate the certificate serialNumber to exchange the certificate instance for public key verification
Debugging is as follows:
HttpUtils: HttpUtils: HttpUtils: HttpUtils: HttpUtils: HttpUtils: HttpUtils: HttpUtils: HttpUtils: HttpUtils:
public class HttpUtils {
/** * converts the notification argument to a string *@param request
* @return* /
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for(String line; (line = br.readLine()) ! =null;) {if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if(br ! =null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Copy the code
If you are curious about how to implement signature and decryption, you can check the source code in the SDK. I have also written the source code in the comments
A quick explanation:
public boolean verify(String serialNumber, byte[] message, String signature) {
if(! serialNumber.isEmpty() && message.length ! =0 && !signature.isEmpty()) {
BigInteger serialNumber16Radix = new BigInteger(serialNumber, 16);
ConcurrentHashMap<BigInteger, X509Certificate> merchantCertificates = (ConcurrentHashMap)CertificatesManager.this.certificates.get(this.merchantId);
X509Certificate certificate = (X509Certificate)merchantCertificates.get(serialNumber16Radix);
if (certificate == null) {
CertificatesManager.log.error("Merchant certificate empty, serialNumber:{}", serialNumber);
return false;
} else {
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(certificate);
sign.update(message);
return sign.verify(Base64.getDecoder().decode(signature));
} catch (NoSuchAlgorithmException var8) {
throw new RuntimeException("SHA256withRSA not supported in current Java environment", var8);
} catch (SignatureException var9) {
throw new RuntimeException("An error occurred during the signature verification process.", var9);
} catch (InvalidKeyException var10) {
throw new RuntimeException("Invalid certificate", var10); }}}else {
throw new IllegalArgumentException("SerialNumber or message or signature is empty"); }}Copy the code
Obtain the certificate in exchange for the public key signature
private void setDecryptData(Notification notification) throws ParseException {
Resource resource = notification.getResource();
String getAssociateddData = "";
if(resource.getAssociatedData() ! =null) {
getAssociateddData = resource.getAssociatedData();
}
byte[] associatedData = getAssociateddData.getBytes(StandardCharsets.UTF_8);
byte[] nonce = resource.getNonce().getBytes(StandardCharsets.UTF_8);
String ciphertext = resource.getCiphertext();
AesUtil aesUtil = new AesUtil(this.apiV3Key);
String decryptData;
try {
decryptData = aesUtil.decryptToString(associatedData, nonce, ciphertext);
} catch (GeneralSecurityException var10) {
throw new ParseException("AES decryption failed, resource:" + resource.toString(), var10);
}
notification.setDecryptData(decryptData);
}
Copy the code
Obtain the AES password with the private key to decrypt the contents of the CipherText
Test it:
The resource content is encrypted
The 2022-03-11 17:11:23. 1656-379 the INFO [nio - 8080 - exec - 1] T.M.M.W.S ervice. Impl. WxPayServiceImpl: Generate orders for the 2022-03-11 17:11:23. 1656-479 the INFO [nio - 8080 - exec - 1] T.M.M.W.S ervice. Impl. WxPayServiceImpl: Call unified order API 17:11:23 2022-03-11. 1656-523 the INFO [nio - 8080 - exec - 1] T.M.M.W.S ervice. Impl. WxPayServiceImpl: Request parameters ===> {}{"amount":{"total":1,"currency":"CNY"},"mchid":"1621810542","out_trade_no":"ORDER_20220311171123529","appid":"wx32d4d9 7357 b79746 ", "description" : "GBA game evaluation", "notify_url" : "http://maiqu.sh1.k9s.run:2271/api/wx-pay/native/notify"} 2022-03-11 17:11:23. 1656-926 the INFO [nio - 8080 - exec - 1] T.M.M.W.S ervice. Impl. WxPayServiceImpl: Success, Result = {" code_URL ":"weixin://wxpay/ bizpayURL? Pr =ICd695Azz"} 2022-03-11 17:11:31.783 ERROR 1656 -- [NIO-8080-exec-2] t.m.m.s.f.JWTAuthenticationTokenFilter : Token is empty 17:19:53 2022-03-11. 1656-337 WARN housekeeper] [l - 1 com. Zaxxer. Hikari. Pool. HikariPool: Hikaripool-1 - Thread starvation or clock leap detected (housekeeper delta= 8M29S873MS509 µ S200ns). 2022-03-11 17:19:53. 1656-338 the INFO [nio - 8080 - exec - 2] t.m. Aiquer metrichall. Wxpay. API. WxPayAPI: notify the attestation of successCopy the code
Declassified contents:
{
"mchid": "1621 * * * 42"."appid": "wx32d*****79746"."out_trade_no": "ORDER_20220311171123529"."transaction_id": "4200001348202203119819934409"."trade_type": "NATIVE"."trade_state": "SUCCESS"."trade_state_desc": "Payment successful"."bank_type": "OTHERS"."attach": ""."success_time": "2022-03-11T17:11:31+08:00"."payer": {
"openid": "o0F3X099H******Spqj5p8D-6TI"
},
"amount": {
"total": 1."payer_total": 1."currency": "CNY"."payer_currency": "CNY"}}Copy the code
conclusion
So I’m going to stop here
I did not provide the relevant database tables and entity classes. You can design according to the personalized business. As for how to use wechat payment SDK, this article has explained very clearly
There are apis for refunds, order timeouts, and downloading bills. How to use all the same, no more than assemble the request body, using the SDK provided HttpClient request, omit the tedious security verification process, get the return result… It is useless to say more than you think
Need complete instance source code can be private message me