This article is the eighth in the series of “Brief Analysis of wechat Payment”. It mainly explains how merchants deal with wechat refund application, refund callback and refund interface query. Some pits will be emphasized.


Analysis of wechat payment series has been updated seven yo ~, have not seen the friends can have a look at oh.

Brief analysis of wechat Pay: query orders and close orders

Brief analysis of wechat Pay: Notification of payment results

Brief analysis of wechat Payment: unified single order interface

In actual scene, to apply for a refund and refund callback interface is relatively commonly used to WeChat payment interface, here we can speak the refund of the original road return way, also have a plenty of using direct cash payment for the user to change, a red envelope to a refund, this kind of situation will appear in the refund of the service, not all a refund, Some will appear in the use of wechat vouchers – single coupons, because single coupons can not be partially refunded, so can only take the way of enterprise payment users, we mainly talk about the original way to return the refund.

PS: The original way back means that the payment is deducted from the associated payment form when you pay, and wechat will record the relevant data, which can be displayed in the client notification.

1. Apply for refund interface

The following is the official wechat refund application document:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
Copy the code

1.1. Application Scenarios

If the buyer or seller needs a refund within a period of time after the transaction occurs, the seller can refund the payment to the buyer through the refund interface. Wechat Pay will refund the payment to the buyer’s account according to the original way after receiving the refund request and verifying the success.

2. Wechat Pay supports multiple refunds for a single transaction. For multiple refunds, you need to submit the merchant order number of the original payment order and set different refund tracking numbers. The total amount of refund cannot exceed the order amount. 3. Request frequency limit: 150qps, that is, the number of normal refund requests shall not exceed 150 times per second. 6Qps, that is, abnormal or wrong refund requests shall not exceed 6 times per second. 4. Partial refund times for each payment order shall not exceed 50 timesCopy the code

PS: The above restrictions will not occur under normal circumstances, but we must also write them into the system exception scenario processing. The request frequency can be handled by queuing or increasing delay, and the partial refund should not exceed the limit of wechat.

1.2. Interface links

https://api.mch.weixin.qq.com/secapi/pay/refund
Copy the code

1.3. Whether a certificate is required

The request requires a two-way certificate.

PS: about wechat certificate, can be in [merchant platform – account center -API security] to download, this certificate a lot of payment interface need to use, please configure the certificate address as constant, the specific implementation can refer to the author github source code.

1.4. Invoke the interface

First look at the source code, as follows:

/** * [wechat refund interface] - Save the related record of the call * @param refundPayment payment record * @param tradePayment historical payment bill * @return map
 * @throws Exception e
 *
 * @author yclimb
 * @date 2018/6/21
 */
public Map<String,String> saveWxPayRefund(Payment refundPayment, Payment tradePayment) throws Exception {
    if (refundPayment == null || tradePayment == null) {
        returnnull; } // transaction_id = null; // Transaction_id = null; / / merchant order number, merchants system internal order number, request within 32 characters, only Numbers, upper and lower case letters _ - | * @, and only under the same merchant number. String out_trade_no = tradePayment.getFlowNumer(); / / merchant refund number, merchants system within the refund number, merchants within the system only, only Numbers, upper and lower case letters _ - | * @, same refund order request only a back many times. String out_refund_no = refundPayment.getFlowNumer(); // Total order amount, passed in the unit: yuan String total_fee = string.valueof (tradePayment.getAmount()); Refund_fee = string.valueof (refundPayment.getAmount())); String refund_desc = refundpayment.getBody (); String refund_desc = refundpayment.getBody (); / / object WeChat pay WXPay WXPay = new WXPay (WXPayConfigImpl. GetInstance ()); // wechat refund interface Map<String, String> resultMap = wxpay. refund(refundUrl, null, out_trade_no, out_refund_no, total_fee, refund_fee, refund_desc); logger.info("saveWxPayRefund:resultMap:"+ resultMap.toString()); // Record the payment flow // order failure, processingif(WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RETURN_CODE)) || WXPayConstants. FAIL. Equals (resultMap. Get (WXPayConstants. RESULT_CODE))) {/ / processing results back, Resultmap. put(WXPayConstants.RESULT_CODE, wxpayconstants.fail); resultMap.put(WXPayConstants.ERR_CODE_DES, resultMap.get(WXPayConstants.RETURN_MSG));return resultMap;
    }

    return resultMap;
}
Copy the code

The above is the sample code for the SDK refund call. There are a few parameters to note:

The field name The variable name mandatory type describe
Wechat order Number transaction_id is String(32) The order number generated by wechat is returned in the payment notice
Merchant Order Number out_trade_no is String(32) The internal order number of the merchant system contains only 32 characters, digits, uppercase and lowercase letters, and underscores (_-)
Merchant refund receipt No out_refund_no is String(64) The refund receipt number is unique in the merchant system and can contain only digits, uppercase and lowercase letters _-
The refund amount refund_fee is Int Total amount of refund, total amount of order, in minutes, can only be an integer
Refund result notification URL notify_url no String(256) Callback address for receiving the refund result notification of wechat Pay asynchronously. The notification URL must be accessible to the external network without parameters. If notifY_URL is passed in the parameters, the callback address configured on the merchant platform will not take effect.

PS: You are advised to fill in all the preceding parameters. The notify_URL parameter can be set as an environment constant, which invokes callback addresses that are not called according to different environment configurations.

Here is the actual sdkwxpay.refund call code:

/ * * * : < br > * application refund scene: when trading after a period of time, because of the reason of the buyer or seller need a refund, the seller can return payments to the buyer, refund interface * WeChat payment after will receive a refund request and verify success, a refund in accordance with the rules to the payments according to the way back to the buyer's account. * Interface document address: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4 * * @ param notify_url callback address * @ param transaction_id WeChat generated by the order number, in the payment notice have returned * @ param out_trade_no merchants system internal order number, request within 32 characters, only Numbers, upper and lower case letters _ - | * @, and only under the same merchant number. * @ param out_refund_no merchants system within the refund number, merchants within the system only, only Numbers, upper and lower case letters, _ - | * @ the same refund order request only a back many times. * @param refund_fee Total amount of the order, the unit of the passed parameter is: yuan * @param refund_fee Total amount of the refund, the unit of the passed parameter is: Yuan * @param refund_desc Refund reason. If received by the merchant, the refund reason * @ will be reflected in the refund message sent to the userreturnAPI return data * @throws Exception e */ public Map<String, String> refund(String notify_URL, String transaction_id, String out_trade_no, String out_refund_no, String total_fee, String refund_fee, String refund_desc) throws Exception {/** Construct request parameter data **/ Map<String, String> data = new HashMap<>(); // variable name field name mandatory type example value description // wechat order number optional String(32) 1.21775E+27 order number generated by wechat, returned in the payment notificationif(transaction_id ! = null) { data.put("transaction_id", transaction_id); } / / merchant order number String (32) 1.21775 e+27 merchants system internal order number, request within 32 characters, only Numbers, upper and lower case letters _ - | * @, and only under the same merchant number. data.put("out_trade_no", out_trade_no); / / merchant refund number is a String (64) 1.21775 e+27 merchants system within the refund number, merchants within the system only, only Numbers, upper and lower case letters _ - | * @, same refund order request only a back many times. data.put("out_refund_no", out_refund_no); // Order amount is Int 100 total order amount, in minutes, can only be an integer, see payment amount data.put("total_fee", String.valueOf(new BigDecimal(total_fee).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); *100 data.put() *100 data.put() *100 data.put() *100 data.put() *100 data.put()"refund_fee", String.valueOf(new BigDecimal(refund_fee).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); // Reason for refund No String(80) The product has been sold out. If the merchant incoming the product, the reason for refund will be reflected in the next refund message sent to the user. Data.put ("refund_desc", refund_desc); // Currency Type No String(8) CNY currency type, which conforms to ISO 4217 standard three-letter code. The default value is CNY. For lists of other values, see currency type data.put("refund_fee_type", WXPayConstants.FEE_TYPE_CNY); Results / / refund notice whether url String (256) asynchronous receive https://weixin.qq.com/notify/ WeChat pay refund result notification callback address, notify the url must be accessible url for the network, not allowed to take parameters, if the parameter in the notify_url, The callback address configured on the merchant platform does not take effect. data.put("notify_url", notify_url); String(30) REFUND_SOURCE_RECHARGE_FUNDS Only applies to merchants with old funds flow; REFUND_SOURCE_UNSETTLED_FUNDS: REFUND_SOURCE_UNSETTLED_FUNDS (default to REFUND_SOURCE_UNSETTLED_FUNDS); REFUND_SOURCE_RECHARGE_FUNDS-- Available balance refunds // data.put("refund_account", null); /** The following five parameters are automatically assigned to the this.fillRequestData method **/ /*// Apr. ID AppID is String(32) wxD678EFH567HG6787 Apr. ID data.put("appid", WXPayConstants.APP_ID); // Merchant id McH_id is String(32) 1230000109 Merchant id assigned by wechat pay data.put("mch_id", WXPayConstants.MCH_ID); / / nonce_str random String is a String (32) 5 k8264iltkch16cq2502si8znmtm67vs random String, the length of the requirements within 32 bits. Recommended random number generation algorithm data.put("nonce_str", nonce_str); // Signature Type Sign_type No String(32) MD5 signature type. The default value is MD5. Hmac-sha256 and MD5 are supported. data.put("sign_type", WXPayConstants.MD5); / / signature sign is a String (32) C380BEC2BFD727A4B6845133519F3AD6 the signature of the calculated value by signature algorithm, see the signature generation algorithm for the data. The put ("sign", sign); */ // Map<String, String> resultMap = this.refund(data); WXPayUtil.getLogger().info("wxPay.refund:" + resultMap);

    return resultMap;
}
Copy the code

The above has been detailed about the specific field meaning, students who do not understand can view the official wechat document, the specific source can view the author’s Github.

There is a point that needs to be paid attention to here. After we call the refund, some exception handling situations will be returned. A series of error codes are included in the official documents, which can be processed in the system, but we won’t go into details here.

2. Refund callback interface

The following is the official wechat refund notification document:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10
Copy the code

2.1. Application Scenarios

When there is a result of the refund applied by the merchant, wechat will send the relevant result to the merchant, and the merchant needs to receive and process it, and return and reply. In the background notification interaction, if wechat receives the merchant’s reply is not successful or times out, wechat will consider the notification as a failure, and 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 30/180/1800/1800/1800/1800/3600/15/15, unit: second)

Note: The same notification may be sent to merchant systems more than once. The merchant system must be able to handle duplicate notifications correctly. When receiving a notification, you are advised to check the status of the corresponding service data and check whether the notification has been processed. If the notification has not been processed, you are advised to process the notification. If the notification has been processed, the result is displayed successfully. Prior to state checking and processing of business data, use data locks for concurrency control to avoid data clutter caused by function reentry.

Special note: The refund result is encrypted for important data, and the merchant needs to decrypt the result with the merchant secret key to obtain the content of the result notice

2.2. Interface links

Upload the parameter “notify_URL” in the refund application interface to enable this function. If the link cannot be accessed, the merchant cannot receive the wechat notification. The notification URL must be a directly accessible URL and cannot carry parameters.

Example: notify_url: "https://pay.weixin.qq.com/wxpay/pay.action"Copy the code

2.3. Decryption method

Decryption steps are as follows: (1) Base64 decoding of encrypted string A to obtain encrypted string B; (2) MD5 decoding of merchant key to obtain 32-bit lowercase key* (key setting path: (3) Decrypt aes-256-ECB (PKCS7Padding) with key*Copy the code

PS: Pay special attention to the fact that if we want to decrypt wechat AES, the encryption and decryption in the runtime environment package released by Java has certain restrictions due to the import control restrictions of GJ. By default, AES encryption and decryption of 256-bit key is not allowed. The solution is to modify the policy file. We need to download the unrestricted permission policy file from the official website.

Jdk8 jce download address: https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

Jar and us_export_policy. jar files replace the original files under %JRE_HOME%\lib\security and %JDK_HOME%\jre\lib\security, making sure that the original files are backed up first.

If jdk8, may encounter security directory has a policy folder, with the author’s computer, for example, the JDK path is/opt/jdk1.8.0 _152 / jre/lib/security/policy, has two child folders under this directory limited, limited, Jar and us_export_policy. jar files in the limited folder need to be replaced. Then restart the project.

2.4. Call the interface

Because the refund callback interface is used to passively receive wechat messages, it is the same as the payment callback interface, and also uses the stream method, the format is XML. Let’s look at the code below:

/** * Notification of refund result * <p> * Upload parameter "notify_URL" in the refund application interface to enable this function * If the link cannot be accessed, the merchant will not receive wechat notification. * The notification URL must be a directly accessible URL with no parameters. Example: notify_url: "https://pay.weixin.qq.com/wxpay/pay.action" * < p > * when merchants to apply for a refund with the results, WeChat take relevant results sent to the merchants, business needs to receive treatment, and returns a response. * When interacting with background notifications, if wechchat receives a response from merchants that is not successful or times out, wechchat will consider the notification as a failure. Wechchat will periodically re-initiate the notification through certain strategies to improve the success rate of the notification as much as possible, but wechchat does not guarantee the final success of the notification. * (notification frequency for 30/180/1800/1800/1800/1800/3600/15/15, unit: second) * note: the same notification may be sent to the merchant system for many times. The merchant system must be able to handle duplicate notifications correctly. * When receiving a notification, you are advised to check the status of the corresponding service data and determine whether the notification has been processed. If the notification has not been processed, you are advised to process it. If the notification has been processed, the result is returned successfully. Prior to state checking and processing of business data, use data locks for concurrency control to avoid data clutter caused by function reentry. * Special note: The refund result is encrypted for important data, and the merchant needs to decrypt the result with the merchant secret key to obtain the content of the result notification * @param Request req * @Param response resp * @return res xml
 *
 * @author yclimb
 * @date 2018/6/21
 */
@ApiOperation(value = "Pay WeChat | WeChat refund callback interface", httpMethod = "POST", notes = "This link is set through the notify_URL parameter submitted in the [wechat Refund API]. If notify_URL is passed in the parameter, the callback address configured on the merchant platform will not take effect.")
@RequestMapping("/refund")
public void refund(HttpServletRequest request, HttpServletResponse response) {

    String resXml = "";
    InputStream inStream;
    try {

        inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) ! = -1) { outSteam.write(buffer, 0, len); } WXPayUtil.getLogger().info("Refund: wechat refund ----start----"); String result = new String(outsteam.tobytearray (),"utf-8");
        WXPayUtil.getLogger().info("Refund: wechat refund ----result----="+ result); // Close the stream outsteam.close ();inStream.close(); // Convert XML to map map <String, String> map = wxpayUtil. xmlToMap(result);if (WXPayConstants.SUCCESS.equalsIgnoreCase(map.get(WXPayConstants.RETURN_CODE))) {

            WXPayUtil.getLogger().info("Refund: wechat refund ---- returned successfully"); String req_info = map.get(String req_info = map.get(String req_info = map.get("req_info"); /** * decryption mode * Decryption steps are as follows: * (1) Base64 decoding of encryption string A to obtain encryption string B * (2) MD5 decoding of merchant key to obtain 32-bit lowercase key* (key setting path: (3) Use key* to decrypt aes-256-ECB (PKCS7Padding) */ String resultStr = AESUtil.decryptData(req_info); // WXPayUtil.getLogger().info("Refund: decrypted string :"+ resultStr); Map<String, String> aesMap = WXPayUtil.xmlToMap(resultStr); /* // the merchant refund number is String(64) 1.21775E+27 merchant refund number String out_refund_no = aesmap.get ("out_refund_no"); // The refund status is String(16) SUCCESS SUCCESS- Refund successful, CHANGE- Refund exception, REFUNDCLOSE - Refund closed String refund_status = aesmap.get ("refund_status"); // merchant order number is String(32) 1.21775E+27 merchant order number String out_trade_no = aesmap.get ("out_trade_no"); /*// add transaction_id = null; /*/ add transaction_id = null; // wechat refund id is String(32) 1.21775E+27 String refund_id = null; // order amount is Int 100 order amount, in minutes, can only be an integer, see payment amount String total_fee = null; // Order amount to be settled no Int 100 This field is returned if the order uses a non-rechargeable coupon. Order amount payable = Order Amount - non-top-up voucher amount, order amount payable <= Order amount. String settlement_total_fee = null; String refund_fee = null; refund_fee = null; Int $settlement_refund_fee = null; Int $settlement_refund_fee = null; Int $settlement_refund_fee = null; */ // Whether the refund is successfulif(! WXPayConstants.SUCCESS.equals(refund_status)) { resXml = resFailXml; }else{// Notify wechat. Asynchronous confirmation succeeded. Mandatory. Otherwise the background will be notified all the time. The transaction is considered a failure after eight. isSuccess =true; } // Check the payment record out_refund_no // check the payment record modification & record payment logif(payment ! = null) { WXPayUtil.getLogger().error("Refund: wechat pay callback: change the payment slip");
            } else {
                WXPayUtil.getLogger().error("Refund: wechat pay callback: could not find the corresponding payment slip"); }}else {
            WXPayUtil.getLogger().error("Refund: failed to pay, error message:" + map.get(WXPayConstants.RETURN_MSG));
            resXml = resFailXml;
        }

    } catch (Exception e) {
        WXPayUtil.getLogger().error("Refund: wechat refund callback is abnormal:", e); } finally {try {BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); } catch (IOException e) { WXPayUtil.getLogger().error("Refund: wechat refund callback is abnormal :out:", e); }}}Copy the code

This code explains in detail how to receive and decode wechat callback data. See the source code for details on aesutil.decryptData (req_info). There is an address at the end of this text, so I won’t go into details here.

Please refer to the official wechat document for the specific parameters of refund receipt. It should be noted that the merchant’s refund receipt number and wechat refund receipt number are the necessary vouchers for modifying and recording the refund.

3. Check for a refund

The following is the official enquiry refund document of wechat:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
Copy the code

3.1. Application Scenarios

After a refund application is submitted, you can invoke this interface to query the refund status. There is a certain delay in refund. The refund paid by small change will arrive in the account within 20 minutes, and the refund paid by bank card will be checked again after 3 working days.

Note: If there are more than 20 partial refunds for a single payment order, please use the refund tracking number to check

3.2. Interface links

https://api.mch.weixin.qq.com/pay/refundquery
Copy the code

3.3. Whether a certificate is required

Don’t need

3.4. Call the interface

Note: When an order has more than 10 partial refunds, the first 10 refunds and total_refund_count are returned by default when the merchant queries the refund using wechat order number or merchant order number refund query API. When merchants need to query more than 10 refund orders under the same order, they can input the order number and offset to query. Wechat Pay will return the offset and the following 10 refund orders, and so on. If the offset value from the merchant exceeds total_refund_count, the system returns PARAM_ERROR.

For example:

There are 36 refund orders under one order. When the merchant wants to query the 25th order, he/she can input the order number and offset=24. The wechat Payment platform will return the information of the 25th to 35th refund order, or the merchant can directly input the refund order number to query the refundCopy the code

Here is how to call:

private void doRefundQuery() {// Select one of the four, the priority of wechat order number query is:  refund_id > out_refund_no > transaction_id > out_trade_no HashMap<String, String> data = new HashMap<String, String>();  // Store order number data.put("out_trade_no", out_trade_no); // wechat order number data.put("transaction_id", out_trade_no); // The merchant refund number data.put("out_refund_no", out_trade_no); // wechat refund number data.put("refund_id", out_trade_no); try { Map<String, String> r = wxpay.refundQuery(data); System.out.println(r); } catch (Exception e) { e.printStackTrace(); }}Copy the code

PS: The priority of wechat order number query is refund_id > out_refund_no > transaction_id > out_trade_no

Note that when querying a refund, pay attention to the returned error code. If any error occurs, synchronize the refund data in the merchant system in a timely manner.

conclusion

Above for refund application, refund callback interface, query refund related explanation and source code, special need to pay attention to is the decryption of the receipt of refund and replace the security file, partners must pay attention to oh, the specific source can see the author’s Github, in the face of each method has a detailed note.

Notice: Next article download statement and fund statement, stay tuned!!

If you want to see the friend of the source in advance, can first take a look at my lot, address is as follows: https://github.com/YClimb/wxpay-sdk/blob/master/README.md

Add the author’s personal wechat account, and the author’s wechat account is as follows: Yclimb, indicating wechat Payment can be included in the wechat Payment discussion group with your friends. Be sure to indicate wechat payment

To this article is over, pay attention to the public account to view more push!!