1. Preparation
First of all, let’s complain about WeChat’s payment. There are several payment models that it supports, but the official documents are particularly scattered, and there are not even a few decent Java-related demos. I have never done WeChat payment before. I was really stunned by it at the beginning. I finally got it through for two days. I wrote it down here to enjoy the future generations!
Regarding the preparation work, the official document address of "WeChat Scan QR code payment model 2" is here https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 You can take a look first. In fact, there are the following things to prepare:
Among them, APP_ID and APP_SECRET can be found on the public platform, while MCH_ID and API_KEY are found on the merchant platform. In particular, API_KEY must be set on the merchant platform. For "WeChat Scan Code Payment Mode 2" (Payment and Callback), only APP_ID, MCH_ID and API_KEY will be used, and nothing else is used.
I won't talk about the development environment. Whether you are springMVC, struts2, or direct serverlet, it's almost the same. As long as you can ensure that the corresponding method can be called. Regarding quoting third-party jar packages, I only used a jdom that operates xml here. Remember that it is the 1.* version, not the latest 2.* on the official website, and the two are incompatible. Specifically, it is jdom-1.1.3.jar, the dependency package jaxen-1.1.6.jar. For these two packages, I didn't use the httpclient used in some examples. It feels unnecessary, and the dependency package is very complicated. Of course, you are maven when I didn't say it.
2. Development and practical combat
1. First, connect to the WeChat interface and obtain the WeChat payment QR code.
public String weixin_pay() throws Exception { // Account information String appid = PayConfigUtil.APP_ID; // appid //String appsecret = PayConfigUtil.APP_SECRET; // appsecret String mch_id = PayConfigUtil.MCH_ID; // Business number String key = PayConfigUtil.API_KEY; // key String currTime = PayCommonUtil.getCurrTime(); String strTime = currTime.substring(8, currTime.length()); String strRandom = PayCommonUtil.buildRandom(4) + ""; String nonce_str = strTime + strRandom; String order_price = 1; // Price Note: The unit of price is divided into String body = "goodsssssss"; // Product name String out_trade_no = "11338"; // Order number// Get the initiating computer ip String spbill_create_ip = PayConfigUtil.CREATE_IP; // Callback interface String notify_url = PayConfigUtil.NOTIFY_URL; String trade_type = "NATIVE"; SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>(); packageParams.put("appid", appid); packageParams.put("mch_id", mch_id); packageParams.put("nonce_str", nonce_str); packageParams.put("body", body); packageParams.put("out_trade_no", out_trade_no); packageParams.put("total_fee", order_price); packageParams.put("spbill_create_ip", spbill_create_ip); packageParams.put("notify_url", notify_url); packageParams.put("trade_type", trade_type); String sign = PayCommonUtil.createSign("UTF-8", packageParams,key); packageParams.put("sign", sign); String requestXML = PayCommonUtil.getRequestXml(packageParams); System.out.println(requestXML); String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML); Map map = XMLUtil.doXMLParse(resXml); //String return_code = (String) map.get("return_code"); //String prepay_id = (String) map.get("prepay_id"); String urlCode = (String) map.get("code_url"); return urlCode; }If nothing unexpected happens, we get a payment url from the WeChat server, which looks like weixin://wxpay/bizpayurl?pr=pIxXXXX. After that, we need to generate a QR code for this url, and then we can use our mobile phone WeChat to scan the code to pay. There are many ways to generate QR codes. Please take your own needs. I provide a Google QR code generation interface here:
public static String QRfromGoogle(String chl) throws Exception { int widhtHeight = 300; String EC_level = "L"; int margin = 0; chl = UrlEncode(chl); String QRfromGoogle = "http://chart.apis.google.com/chart?chs=" + widhtHeight + "x" + widhtHeight + "&cht=qr&chld=" + EC_level + "|" + margin + "&chl=" + chl; return QRfromGoogle; } // Special character processing public static String UrlEncode(String src) throws UnsupportedEncodingException { return URLEncoder.encode(src, "UTF-8").replace("+", "%20"); } The above code involves several tool classes: PayConfigUtil, PayCommonUtil, HttpUtil and XMLUtil. Among them, PayConfigUtil puts some configurations and paths mentioned above. PayCommonUtil involves several methods of obtaining the current event, generating random strings, obtaining parameter signatures and splicing xml. The code is as follows:
public class PayCommonUtil { /** * Whether the signature is correct, the rule is: sort by parameter name az, and parameters that encounter empty values do not participate in the signature. * @return boolean */ public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) { StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); String v = (String)entry.getValue(); if(!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + API_KEY); //File the summary String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase(); String tenpaySign = ((String)packageParams.get("sign")).toLowerCase(); //System.out.println(tenpaySign + " " + mysign); return tenpaySign.equals(mysign); } /** * @author * @date 2016-4-22 * @Description: sign signature* @param characterEncoding * Encoding format* @param parameters * Request parameters* @return */ public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) { StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + API_KEY); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } /** * @author * @date 2016-4-22 * @Description: Convert request parameters to xml format string * @param parameters * Request parameters * @return */ public static String getRequestXml(SortedMap<Object, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) { sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">"); } else { sb.append("<" + k + ">" + v + "</" + k + ">"); } } } sb.append("</xml>"); return sb.toString(); } /** * Take out a random positive integer of the specified length size. * * @param length * int Set the length of the retrieved random number. length less than 11 * @return int Returns the generated random number. */ public static int buildRandom(int length) { int num = 1; double random = Math.random(); if (random < 0.1) { random = random + 0.1; } for (int i = 0; i < length; i++) { num = num * 10; } return (int) ((random * num)); } /** * Get the current time yyyyMMddHHmmss * * @return String */ public static String getCurrTime() { Date now = new Date(); SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss"); String s = outFormat.format(now); return s; } }HttpUtil and XMLUtil are as follows:
public class HttpUtil { private static final Log logger = Logs.get(); private final static int CONNECT_TIMEOUT = 5000; // in millionseconds private final static String DEFAULT_ENCODING = "UTF-8"; public static String postData(String urlStr, String data){ return postData(urlStr, data, null); } public static String postData(String urlStr, String data, String contentType){ BufferedReader reader = null; try { URL url = new URL(urlStr); URLConnection conn = url.openConnection(); conn.setDoOutput(true); conn.setConnectTimeout(CONNECT_TIMEOUT); conn.setReadTimeout(CONNECT_TIMEOUT); if(contentType != null) conn.setRequestProperty("content-type", contentType); OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING); if(data == null) data = ""; writer.write(data); writer.flush(); writer.close(); reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING)); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line); sb.append("/r/n"); } return sb.toString(); } catch (IOException e) { logger.error("Error connecting to " + urlStr + ": " + e.getMessage()); } finally { try { if (reader != null) reader.close(); } catch (IOException e) { } } return null; } } public class XMLUtil { /** * parse xml and return the first-level element key-value pair. If the first-level element has children, the value of this node is the xml data of the child node. * @param strxml * @return * @throws JDOMException * @throws IOException */ public static Map doXMLParse(String strxml) throws JDOMException, IOException { strxml = strxml.replaceFirst("encoding=/".*/"", "encoding=/"UTF-8/""); if(null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if(children.isEmpty()) { v = e.getTextNormalize(); } else { v = XMLUtil.getChildrenText(children); } m.put(k, v); } //Close the stream in.close(); return m; } /** * Get the xml of the child node * @param children * @return String */ public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if(!children.isEmpty()) { Iterator it = children.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if(!list.isEmpty()) { sb.append(XMLUtil.getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } } Of course there is also an MD5 computing tool class
public class MD5Util { private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; } 2. Payment callback
After the payment is completed, WeChat will send the relevant payment results and user information to the callback address we specified above. We need to receive the processing and return the reply. When interacting with background notifications, if WeChat receives a merchant's reply without success or timeout, and WeChat believes that the notification has failed, WeChat will regularly re-initiate notifications through certain strategies to increase the success rate of notifications as much as possible, but WeChat does not guarantee that the notification will be successful in the end. (Notification frequency is 15/15/30/1800/1800/1800/1800/1800/1800/3600, unit: seconds)
Regarding the payment callback interface, we must first sign and verify the content of the payment result notification, and then conduct the corresponding processing process based on the payment result.
public void weixin_notify(HttpServletRequest request,HttpServletResponse response) throws Exception{ //Read the parameters InputStream inputStream ; StringBuffer sb = new StringBuffer(); inputStream = request.getInputStream(); String s ; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((s = in.readLine()) != null){ sb.append(s); } in.close(); inputStream.close(); //Parse xml into map Map<String, String> m = new HashMap<String, String>(); m = XMLUtil.doXMLParse(sb.toString()); //Filter empty settings TreeMap SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>(); Iterator it = m.keySet().iterator(); while (it.hasNext()) { String parameter = (String) it.next(); String parameterValue = m.get(parameter); String v = ""; if(null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } // Account information String key = PayConfigUtil.API_KEY; // key logger.info(packageParams); // Determine whether the signature is correct if(PayCommonUtil.isTenpaySign("UTF-8", packageParams,key)) { //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- //Processing business starts//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- (String)packageParams.get("out_trade_no"); String total_fee = (String)packageParams.get("total_fee"); logger.info("mch_id:"+mch_id); logger.info("openid:"+openid); logger.info("is_subscribe:"+is_subscribe); logger.info("out_trade_no:"+out_trade_no); logger.info("total_fee:"+total_fee); //////////执行自己的业务逻辑//////////////// logger.info("Payment successful"); //Notify WeChat. Asynchronous confirmation is successful. Must write it. Otherwise, the background will be notified all the time. After eight times, it will be considered that the transaction has failed. resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { logger.info("Payment failed, error message: " + packageParams.get("err_code")); resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[Message is empty]]></return_msg>" + "</xml> "; } //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- logger.info("Notification signature verification failed"); } } The signature verification algorithm is similar to the signature generation algorithm, and is provided in the PayCommonUtil tool class above.
3. Later stories
I feel that the payment experience of WeChat scanning is quite good. The only disadvantage is that the relevant documents are scattered. The official demo is not written in Java. I hope that the official WeChat official can gradually improve it in the future!
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.