Wechat code scanning payment (detailed code)

Wechat code scanning payment

Recently, when the project was rebuilt, she was responsible for the payment module. WeChat scan code payment (NATIVE) and Alipay scan code payment were also the first contact. Although it was written out according to official documents and some blogs, there were many problems encountered. It took a lot of detours, wasted a lot of energy and time, and took time to record.

Wechat API

During the test, colleagues recommended using Peanut shell Inside and outside penetration tools.

Internal and external network penetration tutorial:


Wechat development process:

   before development, the merchant must set the payment callback URL in the background of the public platform. The function of URL: receive the product ID and openid of the wechat payment system callback after the user scans the code;

Flow chart provided on the official website:

Business Process Description:

		(1) The merchant background system generates a QR code according to the specified format of wechat payment (see the rules below) and displays it to the user for scanning.
		(2) The user opens wechat "scan" to scan the QR code, and the wechat client sends the scanned content to the wechat payment system.
		(3) The wechat payment system receives the client request and initiates the call to the payment callback URL of the merchant background system. The call request will take parameters such as productid and user's openid, and require the merchant system to return the delivery data package. Please refer to "3.1 callback data input parameters in this section" for details
		(4) The merchant background system receives the callback request from the wechat payment system, and generates the order of the merchant system according to the productid.
		(5) The merchant system calls wechat payment [unified order API] to request an order and obtain the transaction session ID (prepay_id)
		(6) Wechat payment system generates the pre payment transaction according to the request of the merchant system and returns the transaction session ID (pre pay_ id). 
		(7) Merchant background system gets the transaction session ID prepay_id (valid for 2 hours).
		(8) The merchant background system will pay_ ID is returned to wechat payment system. See "3.2 callback data output parameters" for return data
		(9) Wechat payment system initiates the authorized payment process of the client according to the transaction session ID.
		(10) After the user enters the password in the wechat client and confirms the payment, the wechat client submits the payment authorization.
		(11) After verification of wechat payment system, the payment is deducted and the payment transaction is completed.
		(12) After the wechat payment system completes the payment transaction, it returns the transaction result to the wechat client, and prompts the user of the transaction result through SMS and wechat message. Wechat client displays the payment transaction result page.
		(13) Wechat payment system informs the merchant background system of the payment result by sending asynchronous message. The merchant background system needs to reply to the receiving situation and inform the wechat background system not to send the payment notice of this order.
		(14) If no payment notice is received, the merchant background system calls the [query order API].
		(15) The merchant confirms that the order has been paid and then delivers it to the user

In fact, the whole process is that users directly request http through the order information and the unified order API interface provided by wechat, and then wechat will generate a pre payment transaction, and then return a url, and embed the url here into the QR code. Here the QR code is generated and you can consume. After consumption, wechat will call the payment callback interface to notify the payment result. The payment interface and callback interface mentioned here need to be configured in wechat merchant platform.

Get QR Code:

Return the QR code to the front-end display and output the QR code to the local in file format

private Map<String, Object> getPayCodeByWeChat(PayCodeRequest payCodeRequest) {
    log.info("Get wechat QR code request*******");
    Map<String, Object> resultMap = new HashMap<String, Object>();
    String currentUserID = null;
    String orderID = null;
    ResultParams makeCode = null;
    BizContentByWeChatPay bizContent = null;
    try {
        orderID = payCodeRequest.getOrderID();
        currentUserID = payCodeRequest.getCurrentUserID();
        SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");

        PayRecord queryOrderPayRecordByOrderID = orderPayService.queryPayRecordByOrderID(orderID);
        if (null != queryOrderPayRecordByOrderID && OrderPayStatusEnum.PAID.getValue() == queryOrderPayRecordByOrderID.getPayStatus()) {
            log.info("Type of payment:" + queryOrderPayRecordByOrderID.getPayType());
            log.debug("Payment record already exists for order");
            resultMap.put("code", -20003);
            resultMap.put("desc", "The order has been paid and cannot be operated again");
            return resultMap;
        }
        bizContent = new BizContentByWeChatPay();
        String payAmount = payCodeRequest.getPayAmount().replace(",", "");

        bizContent.setBody(payCodeRequest.getValidityPeriod());
        bizContent.setNonce_str(CommonUtils.genId());
        bizContent.setOut_trade_no(CommonUtils.genPayId());
        bizContent.setSubject(payCodeRequest.getProductName());
        bizContent.setTotal_amount(new BigDecimal(payAmount));

        String configValue = queryOneSystemConfig.getConfigValue();

        makeCode = WeChatPayUtil.makeCode(configValue, bizContent);


        if (null == makeCode || !makeCode.getReturn_code().equals("SUCCESS") || !makeCode.getResult_code().equals("SUCCESS")) {
            resultMap.put("code", -20018);
            resultMap.put("desc", "Failed to get wechat payment QR code");
            payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, new Gson().toJson(makeCode), -1);
            return resultMap;
        }

        queryOneSystemConfig = orderPayService.queryOneSystemConfig("qrCodebyAddress");

        String code_url = makeCode.getCode_url();
        // Generate QR code
        String base64Code = QRCodeUtil.getBase64Code(code_url);
        // Recorded and not paid
         
        if (null != queryOrderPayRecordByOrderID) {
            queryOrderPayRecordByOrderID.setOrderID(payCodeRequest.getOrderID());
            queryOrderPayRecordByOrderID.setPayType(OrderPayTypeEnum.WECHAT.getValue());
            queryOrderPayRecordByOrderID.setSign(makeCode.getSign());
            queryOrderPayRecordByOrderID.setEditor(payCodeRequest.getCurrentUserID());
            queryOrderPayRecordByOrderID.setEditTime(new Date());
            queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.UNPAID.getValue());
            queryOrderPayRecordByOrderID.setQr_code(code_url);
            queryOrderPayRecordByOrderID.setNonce_str(bizContent.getNonce_str());
            queryOrderPayRecordByOrderID.setReturn_nonce_str(makeCode.getNonce_str());
            long longValue = (new BigDecimal(payAmount).multiply(new BigDecimal("100"))).longValue();
            queryOrderPayRecordByOrderID.setPayAmount(longValue);
            queryOrderPayRecordByOrderID.setPrepay_id(makeCode.getPrepay_id());
            queryOrderPayRecordByOrderID.setTrade_type(makeCode.getTrade_type());
            queryOrderPayRecordByOrderID.setOut_trade_no(bizContent.getOut_trade_no());
            orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
        } else {
            PayRecord payRecord = new PayRecord();
            payRecord.setOrderID(payCodeRequest.getOrderID());
            payRecord.setPayType(OrderPayTypeEnum.WECHAT.getValue());
            payRecord.setSign(makeCode.getSign());
            payRecord.setOperator(payCodeRequest.getCurrentUserID());
            payRecord.setCreateTime(new Date());
            payRecord.setPayStatus(OrderPayStatusEnum.UNPAID.getValue());
            long longValue = (new BigDecimal(payAmount).multiply(new BigDecimal("100"))).longValue();
            payRecord.setPayAmount(longValue);
            payRecord.setQr_code(code_url);
            payRecord.setNonce_str(bizContent.getNonce_str());
            payRecord.setReturn_nonce_str(makeCode.getNonce_str());
            payRecord.setPrepay_id(makeCode.getPrepay_id());
            payRecord.setTrade_type(makeCode.getTrade_type());
            payRecord.setOut_trade_no(bizContent.getOut_trade_no());
            orderPayService.createOrderPayRecord(payRecord);
        }
        resultMap.put("code", 0);
        resultMap.put("desc", "Success");
        resultMap.put("data", base64Code);
        resultMap.put("out_trade_no", bizContent.getOut_trade_no());
        payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, new Gson().toJson(makeCode), 0);
        return resultMap;
    } catch (Exception e) {
        log.error("Get wechat QR code processing exception*******", e);
        resultMap.put("code", -20018);
        resultMap.put("desc", "Failed to get wechat payment QR code");
        payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, "Get wechat QR code processing exception", 1);
        return resultMap;
    }

}

Wechat payment generates QR code and signature

The general steps of signature generation are as follows:

The first step is to set all the data sent or received as set M, sort the parameters of non null parameter values in set M according to the parameter name ASCII code from small to large (dictionary order), and use the format of URL key value pair (i.e. key1 = value1 & key2 = Value2...) Concatenate into string a. 
Pay special attention to the following important rules: 

  ◆ sort the parameter names in ASCII code from small to large (dictionary order); 
  ◆ if the value of the parameter is empty, it does not participate in signature; 
  ◆ parameter names are case sensitive; 
  ◆ when the verification call returns or wechat actively informs the signature, the transmitted sign parameter does not participate in the signature, and the generated signature is verified with the sign value. 
  ◆ fields may be added to the wechat interface, which must be supported during signature verification 

The second step is to splice the key on the string a to get the string signtemp, MD5 the string signtemp, and then convert all the characters to uppercase to get the sign value signValue.

 Key setting path: wechat merchant platform( pay.weixin.qq . com) -- > account settings - > API security - > key settings

Code display:

  public static ResultParams makeCode(String weChatAccountData, BizContentByWeChatPay bizContent){
   log.info("Get wechat payment QR code********");
log.info("aliPayAccountData: "+weChatAccountData);
   log.info("bizContent: "+new Gson().toJson(bizContent));
   try{
      
      WeChatAccount weChatAccount = new Gson().fromJson(weChatAccountData, WeChatAccount.class);
      String app_id = weChatAccount.getApp_id();
      String merchant_number = weChatAccount.getMerchant_number();
      String notifyUrl = weChatAccount.getNotifyUrl();
      String secret_key = weChatAccount.getSecret_key();
      String pay_path = weChatAccount.getPay_path();
      //Merchant order number, also the order number
      String out_trade_no = bizContent.getOut_trade_no();
      String body = bizContent.getBody();
      BigDecimal total_amount = bizContent.getTotal_amount();
      String nonce_str = bizContent.getNonce_str();
      int intAmount = total_amount.multiply(new BigDecimal("100")).intValue();
      String timeout_express = weChatAccount.getTimeout_express();
      
       //Parameters required for signature
       SortedMap<String,String> parameters = new TreeMap<String,String>();
       parameters.put("appid",app_id);
       //Merchant No
       parameters.put("mch_id",merchant_number);
       parameters.put("body",body);
       //SAPI -JSAPI payment NATIVE -Native payment APP -APP payment
       parameters.put("trade_type","NATIVE");
       //Merchant order number
       parameters.put("out_trade_no",out_trade_no);
       //Total order amount (unit: minute)
       parameters.put("total_fee",String.valueOf(intAmount));
       //Terminal IP / / supports IP addresses in IPV4 and IPV6 formats. Machine ip calling wechat payment API
       parameters.put("spbill_create_ip",GetIp());
       //Commodity ID trade_ This parameter is required when type = native. This parameter is the commodity ID contained in the QR code, which is defined by the merchant.
       parameters.put("product_id",out_trade_no);
       //The callback address to receive the notification of wechat payment results asynchronously. The notification url must be an external accessible url, and cannot carry parameters
       parameters.put("notify_url",notifyUrl);
       //Random string
       parameters.put("nonce_str",nonce_str);
      
       //Parameters required by the interface
       PayParams order = new PayParams();
       order.setAppid(app_id);
       order.setMch_id(merchant_number);
       order.setBody(body);
       order.setTrade_type("NATIVE");
       order.setOut_trade_no(out_trade_no);
       order.setTotal_fee(intAmount);
       order.setSpbill_create_ip(GetIp());
       order.setProduct_id(out_trade_no);
       order.setNotify_url(notifyUrl);
       order.setNonce_str(nonce_str);
       if(null!=timeout_express && !"".equals(timeout_express)){
         Calendar c = Calendar.getInstance();
         c.setTime(new Date());
         int parseTime = Integer.parseInt(timeout_express);
         c.add(Calendar.SECOND, parseTime);
         long time = c.getTime().getTime();
         parameters.put("time_expire", String.valueOf(time));
         order.setTime_expire(String.valueOf(time));
       }
       //autograph 
       String sign = WXPayUtil.getPaySign(parameters,secret_key);
   order.setSign(sign);
       
       //Call wechat payment unified order interface, let wechat also generate a pre payment order
       String xmlResult = HttpUtil.sendPost(pay_path,XMLUtil.convertToXml(order));
       log.info("xmlResult:"+xmlResult);
       
       //Convert the returned xml string to an object
       ResultParams entity = (ResultParams)XMLUtil.convertXmlStrToObject(ResultParams.class,xmlResult);
       log.info("ResultParams:"+new Gson().toJson(entity));
       return entity;
      
   }catch(Exception e){
      log.info("Get wechat payment QR code, call interface exception********",e);
      return null;
   }
  }

Wechat payment tool class (the demo provided by wechat can be downloaded and used)

public class WXPayUtil {
    private static final String SYMBOLS = "xxx";

    private static final Random RANDOM = new SecureRandom();

    /**
     * XML Format string to Map
     *
     * @param strXML XML character string
     * @return XML Map after data conversion
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
           log.error("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    /**
     * Convert Map to a string in XML format
     *
     * @param data Map Type data
     * @return XML Formatted string
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) {
        log.info("mapToXml Start conversion**************");
        StringWriter writer = null;
        try {
            org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
            org.w3c.dom.Element root = document.createElement("xml");
            document.appendChild(root);
            for (String key : data.keySet()) {
                String value = data.get(key);
                if (value == null) {
                    value = "";
                }
                value = value.trim();
                org.w3c.dom.Element filed = document.createElement(key);
                filed.appendChild(document.createTextNode(value));
                root.appendChild(filed);
            }
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            DOMSource source = new DOMSource(document);
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            transformer.transform(source, result);
            String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
            log.info("Conversion completed:", output);
            writer.flush();
            return output;
        } catch (Exception ex) {
            log.error("mapToXml Conversion exception", ex);
            return null;
        } finally {
            if (null != writer) {
                try {
                    writer.close();
                } catch (IOException e) {
                    log.error("close writer abnormal", e);
                }
            }
        }

    }


    /**
     * Generate XML format string with sign
     *
     * @param data Map Type data
     * @param key  API secret key
     * @return XML with sign field
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
    }

    /**
     * Generate XML format string with sign
     *
     * @param data     Map Type data
     * @param key      API secret key
     * @param signType Signature type
     * @return XML with sign field
     */
    public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put(WXPayConstants.FIELD_SIGN, sign);
        return mapToXml(data);
    }


    /**
     * Determine whether the signature is correct
     *
     * @param xmlStr XML Format data
     * @param key    API secret key
     * @return Is the signature correct
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key).equals(sign);
    }

    /**
     * To determine whether the signature is correct, the sign field must be included, otherwise, false is returned. Sign with MD5.
     *
     * @param data Map Type data
     * @param key  API secret key
     * @return Is the signature correct
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
    }

    /**
     * To determine whether the signature is correct, the sign field must be included, otherwise, false is returned.
     *
     * @param data     Map Type data
     * @param key      API secret key
     * @param signType The signature type, MD5 by default, supports HMAC-SHA256 and MD5.
     * @return Is the signature correct
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        log.info("Start validation******");
        if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
            log.debug("Validation failed******");
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * Generate signature
     *
     * @param data Data to be signed
     * @param key  API secret key
     * @return autograph
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, WXPayConstants.SignType.MD5);
    }

    /**
     * Generate signature. Note that if sign is included_ Type field, which must be consistent with the signType parameter.
     *
     * @param data     Data to be signed
     * @param key      API secret key
     * @param signType Signature method
     * @return autograph
     */
    public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConstants.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // If the parameter value is empty, signature is not involved
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (WXPayConstants.SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * Get random string Nonce Str
     *
     * @return String Random string
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * Generate MD5
     *
     * @param data Pending data
     * @return MD5 result
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * Generate hmacha256
     *
     * @param data Pending data
     * @param key  secret key
     * @return Encrypt results
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * journal
     *
     * @return
     */
  /*  public static log getlog() {
        log log = logFactory.getlog("wxpay java sdk");
        return log;
    }*/

    /**
     * Get the current timestamp in seconds
     *
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * Get the current timestamp in milliseconds
     *
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    /**
     * Get signature
     *
     * @param obj
     * @return
     * @throws IllegalAccessException
     * @throws IOException
     */
    public static String getPaySign(Object obj, String secret_key) throws Exception {
        StringBuilder sb = new StringBuilder();
        //Convert objects to TreeMap set (sorted by the ASCII code of key from small to large)
        TreeMap<String, Object> map = (TreeMap) obj;
        Set<Map.Entry<String, Object>> entrySet = map.entrySet();
        //Traversal key value pair
        for (Map.Entry<String, Object> entry : entrySet) {
            //If the value is empty, do not participate in signing
            if (entry.getValue() != null) {
                sb.append(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        //Finally, splice the merchant's API key
        String stringSignTemp = sb.toString() + "key=" + secret_key;
        return MD5(stringSignTemp);
    }


    /**
     * Process the HTTPS API return data and convert it to a Map object. return_ When code is SUCCESS, verify the signature.
     *
     * @param xmlStr            API XML format data returned
     * @param weChatAccountData Wechat account information json
     * @return Map Type data
     * @throws Exception
     */
    public static Map<String, String> processResponseXml(String weChatAccountData, String xmlStr) throws Exception {
        log.info("Start processing xml file***transformation Map***");
        String RETURN_CODE = "return_code";
        String return_code;
        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
        log.info("Converted map:" + respData);
        if (respData.containsKey(RETURN_CODE)) {
            return_code = respData.get(RETURN_CODE);
        } else {
            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
        }

        if (return_code.equals(WXPayConstants.FAIL)) {
            return respData;
        } else if (return_code.equals(WXPayConstants.SUCCESS)) {
            WeChatAccount weChatAccount = new Gson().fromJson(weChatAccountData, WeChatAccount.class);
            if (isResponseSignatureValid(respData, weChatAccount.getSecret_key())) {
                log.debug("Verification successful******");
                return respData;
            } else {
                log.debug("Validation exception******");
                throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
            }
        } else {
            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
        }
    }

    /**
     * To determine whether the sign of xml data is valid, the sign field must be included, otherwise, false is returned.
     *
     * @param reqData Request data to wxpay post
     * @return Is the signature valid
     * @throws Exception
     */
    public static boolean isResponseSignatureValid(Map<String, String> reqData, String secret_key) throws Exception {
        // The signature method of the returned data is the same as that given in the request
        return isSignatureValid(reqData, secret_key, WXPayConstants.SignType.MD5);
    }
    
   /* public static String genSandBoxSign(String mch_id,String nonce_str,String key){
       Map<String, String> param = new HashMap<String, String>();
       
       try {
          param.put("mch_id", mch_id);// Merchant No
           param.put("nonce_str", nonce_str);// Random string
           param.put("sign", WXPayUtil.generateSignature(param, key,WXPayConstants.SignType.MD5));// Sandbox test seems to only support MD5 encryption
           //String xml = mapToXml(param);
          TestM m = new TestM();
          m.setMch_id(mch_id);
          m.setNonce_str(nonce_str);
          m.setSign(param.get("sign"));
           String xmlResult = requestWithoutCert("/sandboxnew/pay/getsignkey",param,1000,1000);
           return xmlResult;
       } catch (Exception e) {
           e.printStackTrace();
           return null;
       }
    }
    
    public static String requestWithoutCert(String urlSuffix, Map<String, String> reqData,
            int connectTimeoutMs, int readTimeoutMs) throws Exception {
      String msgUUID = reqData.get("nonce_str");
      String reqBody = WXPayUtil.mapToXml(reqData);
      
      String resp = requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
      return resp;
    }
    
    */

    /**
     * Retrieable, non bi directional authentication request
     *
     * @return
     *//*
    public static String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs,  boolean autoReport) throws Exception {
        return request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
    }
    
    private static String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
        Exception exception = null;
        long elapsedTimeMillis = 0;
        long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
        boolean firstHasDnsErr = false;
        boolean firstHasConnectTimeout = false;
        boolean firstHasReadTimeout = false;
        DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
        if(domainInfo == null){
            throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
        }
        try {
            String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout);
            return result;
        }
        catch (UnknownHostException ex) {  // dns Resolution error or domain name does not exist
            exception = ex;
            firstHasDnsErr = true;
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            WXPayUtil.getlog().warn("UnknownHostException for domainInfo {}", domainInfo);
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout
            );
        }
        catch (ConnectTimeoutException ex) {
            exception = ex;
            firstHasConnectTimeout = true;
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            WXPayUtil.getlog().warn("connect timeout happened for domainInfo {}", domainInfo);
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout
            );
        }
        catch (SocketTimeoutException ex) {
            exception = ex;
            firstHasReadTimeout = true;
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            WXPayUtil.getlog().warn("timeout happened for domainInfo {}", domainInfo);
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout);
        }
        catch (Exception ex) {
            exception = ex;
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout);
        }
        config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
        throw exception;
    }
    */
    }

class TestM {
    private String mch_id;
    private String nonce_str;
    private String sign;

    public String getMch_id() {
        return mch_id;
    }

    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }

    public String getNonce_str() {
        return nonce_str;
    }

    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

}

class DomainInfo {
    public String domain;       //domain name
    public boolean primaryDomain;     //Whether the domain name is the primary domain name. For example: api.mch.weixin.qq.com Primary domain name

    public DomainInfo(String domain, boolean primaryDomain) {
        this.domain = domain;
        this.primaryDomain = primaryDomain;
    }

    @Override
    public String toString() {
        return "DomainInfo{" +
                "domain='" + domain + '\'' +
                ", primaryDomain=" + primaryDomain +
                '}';
    }
}

Wechat callback is required after code scanning payment:

Callback parameters:
Callback return:
Code display:

public Map<String, String> doWeChatNotify() {
    log.info("Wechat callback payment return result:");
    Map<String, String> resultMap = new HashMap<String, String>();
    Map<String, String> params = new HashMap<String, String>();
    InputStream inStream = null;
    ByteArrayOutputStream outSteam = null;
    String orderID = null;
    try {
        inStream = request.getInputStream();
        outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        String resultxml = new String(outSteam.toByteArray(), "utf-8");
        log.info("Acquired xml:" + resultxml);
        outSteam.flush();
        SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");
        params = WXPayUtil.processResponseXml(queryOneSystemConfig.getConfigValue(), resultxml);
        log.info("xml transformation map result:" + params);

        /**
         * The internal order number of the merchant system requires 32 characters, which can only be numbers and upper and lower case letters_ -|*@, and unique under the same merchant number
         */
        String out_trade_no = params.get("out_trade_no");
        PayRecord queryOrderPayRecordByOrderID = this.orderPayService.queryOrderPayRecordByOut_Trade_NoAndType(out_trade_no, OrderPayTypeEnum.WECHAT.getValue());
        if (null == queryOrderPayRecordByOrderID) {
            resultMap.put("return_code", "FAIL");
            resultMap.put("return_msg", "Wechat payment record not found for current order");
            return resultMap;
        }
        orderID = queryOrderPayRecordByOrderID.getOrderID();
        String return_code = params.get("return_code");
        String return_msg = params.get("return_msg");
        if ("FAIL".equals(return_code)) {
            resultMap.put("return_code", "FAIL");
            resultMap.put("return_msg", return_msg);
            this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "Wechat asynchronous call, return failed");
            return resultMap;
        }
        /**
         * Order amount (points)
         */
        String total_fee = params.get("total_fee");
        /**
         * Merchant No
         */
        //String mch_id = params.get("mch_id");
        String nonce_str = params.get("nonce_str");
        String sign = params.get("sign");
        /**
         * SUCCESS/FAIL
         */
        String result_code = params.get("result_code");
        /**
         * Unique identification of the user under the merchant's appid
         */
        String openid = params.get("openid");
        /**
         * Wechat payment order No
         */
        String transaction_id = params.get("transaction_id");
        /**
         * Time of payment completion
         */
        String time_end = params.get("time_end");
        String trade_type = params.get("trade_type");


        queryOrderPayRecordByOrderID.setPayType(OrderPayTypeEnum.WECHAT.getValue());
        queryOrderPayRecordByOrderID.setComment(queryOrderPayRecordByOrderID.getComment() + "Time:" + new Date() + "==>Wechat asynchronous data");
        queryOrderPayRecordByOrderID.setEditTime(new Date());
        Date payTime = null;
        if (null != time_end && !"".equals(time_end)) {
            SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhhmm");
            payTime = sDateFormat.parse(time_end);
        }

        queryOrderPayRecordByOrderID.setPayTime(payTime);
        queryOrderPayRecordByOrderID.setPayAmount(Long.parseLong(total_fee));
        queryOrderPayRecordByOrderID.setSign(sign);
        queryOrderPayRecordByOrderID.setBuyer_logon_id(openid);
        queryOrderPayRecordByOrderID.setTrade_type(trade_type);
        /**
         * Transaction status: WAIT_BUYER_PAY, TRADE_CLO
         */
        if ("SUCCESS".equals(result_code)) {
            queryOrderPayRecordByOrderID.setNonce_str(nonce_str);
            queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.PAID.getValue());
            queryOrderPayRecordByOrderID.setTrade_no(transaction_id);
            this.orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
            resultMap.put("return_code", "SUCCESS");
            resultMap.put("return_msg", "OK");
            this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 0, "Wechat asynchronous call succeeded");
            return resultMap;
        }
        queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.PAYMENT_FAILED.getValue());
        this.orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
        resultMap.put("return_code", "FAIL");
        resultMap.put("return_msg", "Wechat payment callback failed,return result_code Not for SUCCESS");
        this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "Wechat asynchronous call failed");
        return resultMap;
    } catch (Exception e) {
        log.error("Wechat payment callback returned an exception:+", e);
        resultMap.put("return_code", "FAIL");
        resultMap.put("return_msg", "Wechat payment callback failed");
        this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "Wechat asynchronous call exception");
        return resultMap;
    } finally {

        if (null != inStream) {
            try {
                inStream.close();
            } catch (IOException e) {
                log.error("close inStream abnormal");
            }
        }
        if (null != outSteam) {
            try {
                outSteam.close();
            } catch (IOException e) {
                log.error("close outSteam abnormal");
            }
        }
    }
}

Wechat refund:

The refund requires a certificate. As mentioned above, go to the wechat merchant platform to generate a download. Wechat uses the wechat order number (transaction_id) to refund, transaction_ The parameter ID is returned during the payment callback. Please save it

private Map<String, Object> getPayCodeByWeChat(PayRecord queryPayRecordByOrderID, long transactionPrice, TradeRefundRequest tradeRefundRequest) {
    log.info("Wechat refund request*******");
    Map<String, Object> resultMap = new HashMap<>(16);
    try {
        SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");
        RefundParams refundParams = new RefundParams();
        refundParams.setNonce_str(CommonUtils.genId());
        refundParams.setOut_refund_no(CommonUtils.genId());
        String refund_amount = tradeRefundRequest.getRefund_amount().replace(",", "");
        int intValue = new BigDecimal(refund_amount).multiply(new BigDecimal("100")).intValue();

        refundParams.setRefund_fee(intValue);
        refundParams.setTotal_fee(new BigDecimal(transactionPrice).intValue());
        refundParams.setOut_trade_no(tradeRefundRequest.getOrderID());

        RefundResultParams refundResponse = WeChatPayUtil.refund(queryOneSystemConfig.getConfigValue(), refundParams);
        log.debug("Wechat refund result:" + new Gson().toJson(refundResponse));
        if (null == refundResponse || !refundResponse.getReturn_code().equals("SUCCESS") || !refundResponse.getResult_code().equals("SUCCESS")) {
            log.error("Wechat refund failed*******");
            resultMap.put("code", -20010);
            resultMap.put("desc", "Refund failed");
            return resultMap;
        }

        queryPayRecordByOrderID.setRefund_request_no(refundParams.getOut_refund_no());
        queryPayRecordByOrderID.setRefund_amount(new BigDecimal(refund_amount).multiply(new BigDecimal("100")).longValue());
        queryPayRecordByOrderID.setRefundTime(new Date());
        queryPayRecordByOrderID.setRefund_operator(tradeRefundRequest.getCurrentUserID());
        queryPayRecordByOrderID.setRefund_reason(tradeRefundRequest.getRefund_reason());
        queryPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.REFUNDING.getValue());
        orderPayService.updateOrderPayRecord(queryPayRecordByOrderID);
        resultMap.put("code", 0);
        resultMap.put("desc", "Operation successful");
        resultMap.put("request_no", queryPayRecordByOrderID.getRefund_request_no());
        resultMap.put("refund_amount", queryPayRecordByOrderID.getRefund_amount());
        return resultMap;
    } catch (Exception e) {
        log.error("Wechat refund processing exception*******", e);
        resultMap.put("code", -20010);
        resultMap.put("desc", "Refund failed");
        return resultMap;
    }

}

Tags: xml Session ascii Mac

Posted on Mon, 15 Jun 2020 04:48:05 -0400 by ams007