Introduction to Native payment
Reference documents: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_0.shtml
Native payment refers to the mode in which the merchant system generates a payment QR code according to the wechat payment agreement, and the user uses wechat to "scan" to complete the payment.
Application scenario
Native payment is applicable to PC website, physical store single product or order, media advertising payment and other scenarios
The user scans the QR code displayed by the merchant in various scenarios for payment. The specific operation process is as follows:
Preparation before access
Reference documents: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_1.shtml#top
Select access mode
- Direct Mode
- Service provider model
Parameter application
Note: appid and mchid are required.
Configure API key
API v3 key is mainly used for platform certificate decryption and callback information decryption. Please refer to the interface rules document for specific usage Decryption of certificate and callback message Chapter.
Note: we set the API v3 key here, not the API v2 key.
Download and configure merchant certificate
In the "certificate tool" - "generate certificate" link, the certificate application process has been completed. Click "View Certificate folder" to view the generated certificate file.
Congratulations, everything is ready now!
About API v3
In order to give merchants a simple, consistent and easy-to-use development experience on the premise of ensuring payment security, we launched a new wechat payment API v3.
Compared with the previous wechat payment API, the main differences are:
- Follow a unified REST design style
- Use JSON as the format of data interaction instead of XML
- The digital signature algorithm of SHA256-RSA based on asymmetric key is used, and MD5 or HMAC-SHA256 is no longer used
- HTTPS client certificate is no longer required (only certificate serial number is required)
- AES-256-GCM is used to encrypt and protect the key information in the callback
data format
All API requests must use HTTPS.
Wechat payment API v3 usage JSON As the data exchange format of the message body, except for the image upload API. HTTP header must be set for the request:
Content-Type: application/json Accept: application/json
error message
Wechat payment API v3 uses HTTP status code to represent the result of request processing.
- If the request is processed successfully, the message body will return 200 if there is a response, and 204 if there is no response.
- If the pending request has been successfully accepted, 202 will be returned.
- When the request processing fails, if the necessary input parameters are missing and the balance is insufficient during payment, the error code within 4xx range will be returned.
- The service system error on wechat payment side occurs during request processing, and the status code of 500 / 501 / 503 will be returned. This is rare.
Wechat payment v3 actual combat
Environmental Science:
- SpringBoot 2.3.2.RELEASE
- weixin-java-pay 4.2.0
- lombok 1.18.22
- hutool-all 5.7.21
prepare
The following materials need to be prepared in advance
Add dependency
Reference address: https://gitee.com/binary/weixin-java-tools
<!--weixin pay --> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-pay</artifactId> <version>4.2.0</version> </dependency>
other
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.21</version> </dependency>
configuration file
Modify application YML file
wx: pay: appId: Please fill in your appId mchId: Please fill in your mchId apiV3Key: Please fill in your apiV3Key privateCertPath: Please fill in your apiclient_cert.pem File (absolute path or relative path) privateKeyPath: Please fill in your apiclient_key.pem File (absolute path or relative path) notifyUrl: Your callback address must be filled in successfully https (and accessible from the public network)
Configure payment environment
Add two configuration files to complete the initialization of payment environment
WxPayPropertiesV3
Read the contents of the configuration file
package com.ray.weixin.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * wxpay pay properties. * * @author Binary Wang */ @Data @ConfigurationProperties(prefix = "wx.pay") public class WxPayPropertiesV3 { /** * Set up appid for WeChat official account or small program. */ private String appId; /** * Wechat payment merchant No */ private String mchId; /** * apiV3 Secret key value */ private String apiV3Key; /** * apiclient_cert.pem The absolute path of the certificate file or the classpath starting with classpath: */ private String privateCertPath; /** * apiclient_key.pem The absolute path of the certificate file or the classpath starting with classpath: */ private String privateKeyPath; /** * The link is set through the request parameter "notify_url" in the basic order interface, which must be an https address. * Please ensure that the callback URL is externally accessible and cannot carry suffix parameters, otherwise the merchant may not receive the callback notification information of wechat. * Example callback URL:“ https://pay.weixin.qq.com/wxpay/pay.action ” */ private String notifyUrl; }
WxPayConfiguration
Complete payment environment initialization
package com.ray.weixin.config; import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.service.WxPayService; import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Binary Wang */ @Configuration @ConditionalOnClass(WxPayService.class) @EnableConfigurationProperties(WxPayPropertiesV3.class) @AllArgsConstructor public class WxPayConfiguration { private WxPayPropertiesV3 properties; @Bean @ConditionalOnMissingBean public WxPayService wxService() { WxPayConfig payConfig = new WxPayConfig(); payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId())); payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId())); payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiV3Key())); payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath())); payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath())); payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl())); // You can specify whether to use a sandbox environment payConfig.setUseSandboxEnv(false); // The wechat payment interface request implementation class is implemented by Apache HttpClient by default WxPayService wxPayService = new WxPayServiceImpl(); wxPayService.setConfig(payConfig); return wxPayService; } }
WxPayServiceImpl is the wechat payment interface request implementation class, which is implemented by Apache HttpClient by default.
Tool class
Wechat order tools
package com.ray.weixin.utils; import lombok.extern.slf4j.Slf4j; import java.text.SimpleDateFormat; import java.util.Date; /** * @ClassName: WxOrderUtils * @Description: Wechat order tools * @Author: Ray * @Date: 2022-02-16 9:28 */ @Slf4j public class WxOrderUtils { public static final String ORDER_PAY_PREFIX = "ray_pay_"; public static final String ORDER_REFUND_PREFIX = "ray_refund_"; public static final String FORMAT = "yyyyMMddHHmmssSSS"; /** * Generate merchant order number * * @return */ public static String genOutTradeNo() { String order = ORDER_PAY_PREFIX + new SimpleDateFormat(FORMAT).format(new Date()); log.debug("Generate merchant order number: [{}]", order); return order; } /** * Generate refund order number * * @return */ public static String genRefundOrder() { String order = ORDER_REFUND_PREFIX + new SimpleDateFormat(FORMAT).format(new Date()); log.debug("Generate refund order number: [{}]", order); return order; } }
Request header tool class
package com.ray.weixin.utils; import com.github.binarywang.wxpay.bean.notify.SignatureHeader; import lombok.extern.slf4j.Slf4j; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @ClassName: HtmlUtils * @Description: Request header tool class * @Author: Ray * @Date: 2022-02-10 8:48 */ @Slf4j public class HtmlUtils { /** * Get request header information * * @return */ public static String fetchRequest2Str(HttpServletRequest request) { String reqStr = null; BufferedReader streamReader = null; try { streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8")); StringBuilder responseStrBuilder = new StringBuilder(); String inputStr; while ((inputStr = streamReader.readLine()) != null) { responseStrBuilder.append(inputStr); } reqStr = responseStrBuilder.toString(); log.info("Request Received is \n" + reqStr); } catch (Exception e) { e.printStackTrace(); } finally { try { if (streamReader != null) { streamReader.close(); } } catch (IOException e) { e.printStackTrace(); } } return reqStr; } /** * Get request header signature * * @return */ public static SignatureHeader fetchRequest2SignatureHeader(HttpServletRequest request) { SignatureHeader signatureHeader = new SignatureHeader(); signatureHeader.setSignature(request.getHeader("Wechatpay-Signature")); signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce")); signatureHeader.setSerial(request.getHeader("Wechatpay-Serial")); signatureHeader.setTimeStamp(request.getHeader("Wechatpay-TimeStamp")); return signatureHeader; } }
Processing notification response class
package com.ray.common.core.domain; import java.util.HashMap; /** * @Description: Wechat payment V3 notification response * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml * @Author: Ray * @Date: 2022-02-13 9:39 **/ public class WxPayResult extends HashMap<String, Object> { private static final long serialVersionUID = 1L; private static final String FAIL = "FAIL"; private static final String SUCCESS = "SUCCESS"; public static WxPayResult error() { WxPayResult r = new WxPayResult(); r.put("code", FAIL); r.put("message", "fail"); return r; } public static WxPayResult ok() { WxPayResult r = new WxPayResult(); r.put("code", SUCCESS); r.put("message", "success"); return r; } @Override public WxPayResult put(String key, Object value) { super.put(key, value); return this; } }
Payment exception class
package com.ray.common.exception; /** * Payment exception class * * @author Ray */ public class WeixinPayException extends RuntimeException { private static final long serialVersionUID = 1L; private String msg; private int code = 500; public WeixinPayException() { super("Payment exception, please contact the administrator"); } public WeixinPayException(String msg) { super(msg); this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } }
Native order
Reference documents: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
The merchant background system calls wechat payment first Native order Interface, wechat background system returns link parameter code_url, the merchant background system will code_ The URL value generates a QR code image, and the user initiates payment after scanning the code with the wechat client.
be careful
code_ The validity period of the URL is 2 hours. After expiration, scanning the code can no longer initiate payment.
Therefore, when processing orders, if it is a duplicate order, we can directly return the QR code within the validity period without requesting again.
Business process sequence diagram
Service layer
package com.ray.weixin.service; import com.ray.weixin.utils; import com.ray.common.utils.StringUtils; import com.ray.qhummingbird.domain.PayOrder.PayOrderEntity; import com.ray.qhummingbird.domain.UserPayOrder.UserPayOrderCreateInfoVo; import com.ray.qhummingbird.domain.UserPayOrder.UserPayOrderEntity; import com.ray.qhummingbird.domain.UserPayOrderLog.UserPayOrderLogEntity; import com.ray.weixin.config.WxPayPropertiesV3; import com.ray.weixin.domain.UserPayOrderLogRefundDto; import com.ray.weixin.utils.HtmlUtils; import com.ray.weixin.utils.WxOrderUtils; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result; import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request; import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result; import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryV3Result; import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; /** * @ClassName: WxPayRemoteService * @Description: Wechat payment remote service * @Author: Ray * @Date: 2022-02-10 14:09 */ @Service @Slf4j public class WxPayRemoteService { @Autowired private WxPayService wxService; @Autowired private WxPayPropertiesV3 properties; /** * @Description: Call order API * @Author: Ray * @Date: 2022-02-10 14:54 **/ public Map<String, Object> createOrderV3() throws WxPayException { log.debug("Create order >>>>> "); // Product description String description = "Custom item description"; // Merchant order number String outTradeNo = WxOrderUtils.genOutTradeNo(); // Total amount in minutes Integer total = 1; WxPayUnifiedOrderV3Request request = this.genWxPayUnifiedOrderV3Request(description, outTradeNo, total); String qrCode = wxService.createOrderV3(TradeTypeEnum.NATIVE, request); // Return the required information HashMap<String, Object> map = new HashMap<>();; map.put("qrCode", qrCode); map.put("outTradeNo", outTradeNo); return map; } /** * @Description: Assembly request data * @Author: Ray * @Date: 2022-02-10 14:30 **/ private WxPayUnifiedOrderV3Request genWxPayUnifiedOrderV3Request(String description, String outTradeNo, Integer total) { WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); // Product description request.setDescription(description); // Merchant order number request.setOutTradeNo(outTradeNo); // Notification address request.setNotifyUrl(properties.getNotifyUrl()); // Order amount WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount(); // Total amount in minutes amount.setTotal(total); // Currency type, optional amount.setCurrency("CNY"); request.setAmount(amount); return request; } }
Request layer
package com.ray.weixin.feign; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONUtil; import com.ray.common.core.controller.BaseController; import com.ray.common.core.domain.R; import com.ray.common.core.domain.WxPayResult; import com.ray.common.exception.WeixinPayException; import com.ray.common.utils.StringUtils; import com.ray.weixin.service.WxPayRemoteService; import com.ray.weixin.utils.WxOrderUtils; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result; import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result; import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryV3Result; import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.concurrent.locks.ReentrantLock; /** * @ClassName: WxPayFeign * @Description: Wechat payment interface * @Author: Ray * @Date: 2022-02-08 15:10 */ @RestController @RequestMapping("/feign/wxPay") public class WxPayFeign extends BaseController { @Autowired private WxPayRemoteService wxPayRemoteService; /** * Generate merchant order number */ @GetMapping("genOutTradeNo") public String genOutTradeNo() { return WxOrderUtils.genOutTradeNo(); } /** * Generate refund order */ @GetMapping("genOutRefundNo") public String genOutRefundNo() { return WxOrderUtils.genRefundOrder(); } /** * Call unified ordering interface * */ @GetMapping("createOrderV3") public R createOrderV3() { try { logger.debug("Call unified ordering interface >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); // Query the data first. If it exists and the QR code is within the validity period, it will be returned directly (omitted) // Call order API HashMap<String, Object> map = wxPayRemoteService.createOrderV3(); // Item order number String outTradeNo = map.get("outTradeNo"); // QR code String qrCode = map.get("qrCode"); // Save data (omitted) // Return data return R.data(map); } catch (WxPayException e) { e.printStackTrace(); throw new WeixinPayException(e.getErrCodeDes()); } return R.error(); } }
Payment notification API
Document address: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml
Wechat payment notifies the merchant of the user's payment success message through the payment notification interface
tip: if there is no public address, you can use intranet penetration.
be careful:
• the same notification may be sent to the merchant system multiple times. The merchant system must be able to correctly handle duplicate notifications. It is recommended that when the merchant system receives the notice for processing, it first checks the status of the corresponding business data and determines whether the notice has been processed. If it is not processed, it shall be processed again; If it has been processed, the result will be returned directly. Before checking and processing the status of business data, data lock should be used for concurrency control to avoid data confusion caused by function reentry.
• if no wechat callback is received after all notification frequencies, the merchant shall call the query order interface to confirm the order status.
**Special reminder: * * the merchant system must verify the signature of the content of the opening result notice, and verify whether the information in the notice is consistent with the information on the merchant side, so as to prevent data leakage from causing "false notice" and capital loss.
Notification response
The payment notice will be deemed to be received normally only when the HTTP response code is 200 or 204. When the callback handles exceptions, the HTTP status code of the response should be 500 or 4xx.
The return parameters of payment notice are as follows:
Service layer
/** * @ClassName: WxPayRemoteService * @Description: Wechat payment remote service * @Author: Ray * @Date: 2022-02-10 14:09 */ @Service @Slf4j public class WxPayRemoteService { @Autowired private WxPayService wxService; @Autowired private WxPayPropertiesV3 properties; // Ignore partial code /** * @Description: Resolve payment result v3 notification * @Author: Ray * @Date: 2022-02-10 14:10 **/ public WxPayOrderNotifyV3Result parseOrderNotifyV3Result(HttpServletRequest request) throws WxPayException { // Resolve payment result v3 notification return wxService.parseOrderNotifyV3Result( HtmlUtils.fetchRequest2Str(request), HtmlUtils.fetchRequest2SignatureHeader(request) ); } }
Request layer
/** * @ClassName: WxPayFeign * @Description: Wechat payment interface * @Author: Ray * @Date: 2022-02-08 15:10 */ @RestController @RequestMapping("/feign/wxPay") public class WxPayFeign extends BaseController { @Autowired private WxPayRemoteService wxPayRemoteService; private final ReentrantLock lock = new ReentrantLock(); // Ignore partial code /** * @Description: Asynchronous notification of payment success * @Author: Ray * @Date: 2022-02-13 9:37 **/ @PostMapping("/native/notify") public WxPayResult nativeNotify(HttpServletRequest request) { logger.debug("Asynchronous notification of payment success >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); if (lock.tryLock()) { try { // Resolve payment result v3 notification WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = wxPayRemoteService.parseOrderNotifyV3Result(request); // Get basic information String tradeState = wxPayOrderNotifyV3Result.getResult().getTradeState(); String tradeStateDesc = wxPayOrderNotifyV3Result.getResult().getTradeStateDesc(); String outTradeNo = wxPayOrderNotifyV3Result.getResult().getOutTradeNo(); // Get user payment order information (omitted) // If it has been processed, the success id is returned directly if (StringUtils.isNotBlank(user.getTradeState()) && WxPayConstants.WxpayTradeStatus.SUCCESS.equals(user.getTradeState())) { // Return success identification return WxPayResult.ok(); } // Business logic (omitted) // Return success identification return WxPayResult.ok(); } catch (WxPayException e) { logger.error(e.getMessage()); // Return failure ID return WxPayResult.error(); } finally { lock.unlock(); } } // Return failure ID return WxPayResult.error(); } }
Query order API
Reference documents: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml
Merchants can actively query the order status through the order query interface to complete the next business logic.
be careful
The order can be queried through Wechat payment order No and Merchant order number The two query methods return the same results
Service layer
/** * @ClassName: WxPayRemoteService * @Description: Wechat payment remote service * @Author: Ray * @Date: 2022-02-10 14:09 */ @Service @Slf4j public class WxPayRemoteService { @Autowired private WxPayService wxService; @Autowired private WxPayPropertiesV3 properties; // Ignore partial code /** * @Description: Query order API * @Author: Ray * @Date: 2022-02-10 15:18 **/ public WxPayOrderQueryV3Result queryOrderV3(String transactionId, String outTradeNo) throws WxPayException { WxPayOrderQueryV3Result wxPayOrderQueryV3Result = this.wxService.queryOrderV3(transactionId, outTradeNo); log.debug("Query order results >>>>> " + wxPayOrderQueryV3Result); return wxPayOrderQueryV3Result; } }
Request layer
/** * @ClassName: WxPayFeign * @Description: Wechat payment interface * @Author: Ray * @Date: 2022-02-08 15:10 */ @RestController @RequestMapping("/feign/wxPay") public class WxPayFeign extends BaseController { @Autowired private WxPayRemoteService wxPayRemoteService; // Ignore partial code /** * Call order query interface * */ @GetMapping("queryOrderV3/{outTradeNo}") public R queryOrderV3(@PathVariable("outTradeNo") String outTradeNo) { try { logger.debug("Call order query interface >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); // Query order WxPayOrderQueryV3Result wxPayOrderQueryV3Result = wxPayRemoteService.queryOrderV3("", outTradeNo); // Save data (omitted) // Return data HashMap<String, Object> map = new HashMap<>(); map.put("tradeState", wxPayOrderQueryV3Result.getTradeState()); map.put("tradeStateDesc", wxPayOrderQueryV3Result.getTradeStateDesc()); return R.data(map); } catch (WxPayException e) { e.printStackTrace(); throw new WeixinPayException(e.getErrCodeDes()); } return R.error(); } }
epilogue
The use methods of other interfaces are similar, which will not be demonstrated here.