Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.


Recently, I connected to the wechat mini program payment function in my work. Although the official documents are quite detailed, I still stepped on a lot of holes in the process of use, and sorted out the general process and code to share. Before you start using the applet payment feature, you need to do the following:

  • Apply for wechat applets and configure the applets ID and key
  • Apply for the wechat merchant platform account for payment, configure the merchant ID and the merchant platform secret key, and bind the small program to the merchant number
  • Back-end services are required in a formal environmenthttpsDomain name, debug mode may not be required

It can be seen that the main logic of small program payment is concentrated in the back end. The front end only needs to request the back end interface with parameters, and then invoke wechat payment in the front end according to the data returned by the back end interface.

Disassemble and explain the process according to the main interaction steps of merchant business system and wechat payment system in the above flow chart.

1. Obtain the user openId

The front end of the applet calls wx.login() to get the login credential code, and the back end calls the interface to get the openID and session_key of the user. Note that you need to carry the appId and appSecret of the applet when making the request.

public OpenIdInfo code2Openid(String code){
    String url = "https://api.weixin.qq.com/sns/jscode2session";
    String param = "appid=" + mpCommonProperty.getAppid() +
            "&secret=" + mpCommonProperty.getAppsecret() +
            "&js_code=" + code +
            "&grant_type=authorization_code";

    String rs = HttpUtils.sendGet(url, param);
    JSONObject json = JSONObject.parseObject(rs);

    if (json.get("errcode") = =null) {
        String openid = json.getString("openid");
        String sessionKey = json.getString("session_key");
        OpenIdInfo openIdInfo = OpenIdInfo.builder()
                .openId(openid).sessionKey(sessionKey).build();
        return openIdInfo;
    }else {
        log.error("get openid error");
        return null; }}Copy the code

It should be noted that the value of session_key will be refreshed every time the interface is invoked, making the previous session_key invalid. Other operations such as resolving the user’s mobile phone number will use this secret key. In order to avoid this situation, the user’s OpenID can be stored in the user system of the business system.

2. Call payment to place unified orders

The unified order interface of wechat requires the transmission of parameters in the form of XML message, so the parameters need to be spliced first. Here only the minimum parameter range required to arouse the payment of the mini program is listed. For more parameter lists, you can check the official documents.

public String generateUniPayXml(UnifiedParam unifiedParam){
    int money = (int) Math.ceil(unifiedParam.getTotalMoney() * 100);   // Convert to points and round up

    Map<String,String> map=new HashMap<>();
    map.put("appid", mpCommonProperty.getAppid()); // Applets id
    map.put("mch_id", mpCommonProperty.getMuchId());  / / merchants
    map.put("nonce_str",UUID.randomUUID().toString().replaceAll("-".""));   // Random string
    map.put("body", unifiedParam.getPayBody());    // Product description
    map.put("out_trade_no", unifiedParam.getOrderNumber());    // Merchant order number
    map.put("total_fee",String.valueOf(money)); // The marked price, the total amount of the order is in minutes
    map.put("spbill_create_ip",IpUtils.getInternetIp());   / / terminal IP
    map.put("notify_url", mpCommonProperty.getServerDomain()+ "/pay/fallback");// Notify the address
    map.put("trade_type"."JSAPI");// Transaction type
    map.put("openid", unifiedParam.getOpenid());Trade_type =JSAPI This parameter is mandatory

    String sign = signCommon(map);
    map.put("sign",sign);    // Generate a signature

    String xml = XmlUtil.generateXmlFromMap(map);
    log.info(xml);
    return xml;
}
Copy the code

Several of the parameters are described:

  • out_trade_no: Merchant order number, generated by some rule in our background, cannot be repeated
  • total_fee: Total amount of the order, which should be paid attention to in minutes and transferred
  • body: Product Description
  • notify_url: the address of the callback interface for the payment result, whose use is described later
  • sign: Signature, which needs to be generated according to the rules of wechat. The algorithm rules are to remove the element with empty value and parameter nameASCIILexicographic sort for concatenation, concatenation API key, useMd5Encrypt:

The signature method is as follows:

public String signCommon(Map<String,String> map){
    Set<String> emptySet=new HashSet<>();
    map.forEach((K,V)->{
        if(StringUtils.isEmpty(map.get(K))){ emptySet.add(K); }});for (String key : emptySet) {
        map.remove(key);
    }

    Set<String> keySet =  map.keySet();
    String[] array = keySet.toArray(new String[keySet.size()]);
    Arrays.sort(array);

    StringBuffer sb=new StringBuffer();
    for (String key : array) {
        sb.append(key+"="+map.get(key)+"&");
    }
    sb.append("key=").append(mpCommonProperty.getMuchSecret());
    System.out.println(sb.toString());
    String md5Sign = Md5Utils.hash(sb.toString());
    return md5Sign;
}
Copy the code

After the above steps are completed, the unified single interface exposed to the outside is as follows:

public Map<String, String> unifiedOrder(UnifiedParam unifiedParam){
    String xml = mpPayUtil.generateUniPayXml(unifiedParam);
    String url= "https://api.mch.weixin.qq.com/pay/unifiedorder";
    String xmlResult  = HttpUtils.sendPost(url, xml);

    // Sending the request succeeded
    if (xmlResult.indexOf("SUCCESS")! = -1){
        Map<String, String> parseXmlToMap = XmlUtil.parseXmlToMap(xmlResult);
        return parseXmlToMap;
    }else{
        throw new RuntimeException("Uniform payment error"); }}Copy the code

After the call, an XML packet is returned as the synchronization result, which is parsed into a Map for use in the next phase. For the returned value and error code of the synchronization interface, see the official documents.

3. Second signature

After the unified ordering interface is called and the synchronization result of wechat is received, it needs to be signed twice. The parameters to be signed include appId, timeStamp, nonceStr, Package and signType.

public PrepayInfo secondSign(Map<String, String> unifiedOrderMap){
    Map<String,String> map=new HashMap<>();
    map.put("appId", mpCommonProperty.getAppid());
    map.put("timeStamp",String.valueOf(System.currentTimeMillis()/1000));
    map.put("nonceStr",unifiedOrderMap.get("nonce_str"));
    map.put("package"."prepay_id="+unifiedOrderMap.get("prepay_id"));
    map.put("signType",WechatConstants.signType);

    String sign = mpPayUtil.signCommon(map);
    map.put("paySign",sign);
    map.put("prePackage",unifiedOrderMap.get("prepay_id"));

    PrepayInfo prepayInfo =new PrepayInfo();
    BeanUtil.copyProperties(map, prepayInfo);
    return prepayInfo;
}
Copy the code

After the second signature is completed, timeStamp, nonceStr, Package, signType and paySign are returned to the front end, where an object is encapsulated for return for convenience, and the front end invokes wechat payment after receiving the parameters.

4. Receive payment notifications

In the parameter of unified order introduced above, the callback address of merchant backend is passed in. After the payment is completed, wechat will call this callback interface to this merchant to notify the payment result.

@PostMapping("fallback")
public void fallback(HttpServletRequest request,HttpServletResponse response) throws IOException {
    StringBuilder sb = new StringBuilder();
    BufferedReader reader = null;
    try (InputStream inputStream = request.getInputStream()) {
        reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
        String line = "";
        while((line = reader.readLine()) ! =null) { sb.append(line); }}catch (IOException e) {
        log.error("GetBodyString error");
    } finally {
        if(reader ! =null) {
            try {
                reader.close();
            } catch (IOException e) {
                log.error(ExceptionUtils.getMessage(e));
            }
        }
    }
    
    String notifyXml=sb.toString();
    Map<String, String> params = XmlUtil.parseXmlToMap(notifyXml);
    boolean result = false;
    String resultXml;
    if ("SUCCESS".equals(params.get("return_code"))) {// Communication success, non-transaction identifier
        // Verify the signature
        if (WechatPayUtil.validSignature(notifyXml)) {
            // Execute business logic
            result=true; }else{
            log.error("Wechat Pay callback verification signature error!"); }}else {
        log.error("Fallback result:"+params.get("return_msg"));
    }
    
    if (result){
        resultXml="
      
       
        
       "
       +
                "
      
       
      ";
    }else {
        resultXml="
      
       
        
       "
      
                + "
      
       
       ";
    }
           
    ServletOutputStream outputStream = response.getOutputStream();
    outputStream.println(result);
    outputStream.close();
}
Copy the code

After receiving the returned packet, you need to use the same signature algorithm to verify the authenticity of the returned packet and execute the subsequent service logic after verifying the authenticity to prevent false notification caused by data leakage and fund loss.

When wechat invokes the callback interface, if we receive the response from our business system that does not meet the specifications or times out, we will judge that this notification failed and resend multiple notifications. In the case of unsuccessful notifications, the official documentation states that the callback interface will be invoked 15 times in a total of 24h4m. Therefore, you must return the received packets according to the regulations to reduce the load of the system to some extent.

In our tests, we found that we could not return the result directly by returning a String. The callback would always be issued and we would have to write the return using HttpServletResponse. Even if this is done, it is recommended that you do idempotent processing before handling the business inside the callback interface to prevent multiple executions of the callback logic from causing data clutter in the business system.