1: This article only aims at the scavenging payment of the third-party native pc platform
1. Noun Interpretation:
1, WeChat Public Platform The WeChat public platform is the WeChat public account application entry and management background.Merchants can submit basic data, business data and financial data on the public platform to apply for the opening of the WeChat payment function. Platform Entry: http://mp.weixin.qq.com. 2, WeChat Open Platform WeChat Open Platform is the application entry for merchant APP to access the open interface of WeChat payment, through which WeChat APP payment can be applied. Platform entry: http://open.weixin.qq.com. 3, WeChat Merchant Platform The WeChat merchant platform is a collection of merchant functions related to WeChat payment, including parameters configuration, payment data query and statistics, online refunds, vouchers or discount operations. Platform Entry: http://pay.weixin.qq.com. 4, WeChat Enterprise Number The WeChat Enterprise Number is the application entry and management background for the Enterprise Number. Merchants can submit basic data, business data and financial data on the Enterprise Number to apply for the opening of the WeChat payment function. Enterprise number entry: http://qy.weixin.qq.com. 5, WeChat Payment System WeChat payment system refers to the general name of API interface, background business processing system, accounting system, callback notification and other systems involved in the process of WeChat payment. 6, Merchant Cash Collection System Merchant Cash Collection System (POS) is a system for merchants to enter commodity information, generate orders, pay customers, print small tickets and other functions.Accessing the payment function of WeChat mainly involves the development and testing of POS software system, so the commercial billing system mentioned below refers specifically to POS software system. 7, Business Background System The merchant background system is the general name of the merchant background processing business system, such as: merchant website, cash collection system, purchase, storage system, shipping system, customer service system, etc. 8, Scavenger An input device that is used by business systems to quickly read graphic coded information on media.Depending on the type of reading code, it can be divided into barcode scanner and two-dimensional code scanner.According to the principle of reading physics, it can be divided into infrared scanner and laser scanner. 9, Merchant Certificate Merchant certificate is a binary file provided by WeChat. When the merchant system initiates a communication request with the WeChat payment background server, it serves as the credentials to identify the real identity of the merchant in the WeChat payment background. 10, autograph Business background and WeChat payment background generate a result based on the same key and algorithm to verify the identity validity of both parties.The signature algorithm is formulated and made public by WeChat Payment. The commonly used signature methods are MD5, SHA1, SHA256, HMAC, etc. 11, JSAPI Payment for Web Pages JSAPI Web Payment refers to the public number payment mentioned earlier. You can click on the page link in the WeChat Public Number, circle of friends, chat session, or open the merchant HTML5 page in WeChat with the WeChat "Sweep" scan page address 2D code, and order the payment in the page. 12, Native Native Payment Native native payment refers to the sweep payment mentioned above. The merchant generates a two-dimensional code according to the WeChat payment protocol format. Users scan the two-dimensional code through WeChat and enter the payment confirmation interface. The payment is completed by entering the password. 13, Payment Password Payment password is a password set separately by users when they open WeChat Payment to confirm payment completion authorization.The password is different from the WeChat login password. 14, Openid User's identity in the public number, different public numbers have different openids.The merchant background system can obtain the user's openid through API s such as login authorization, payment notification, query orders, etc.The main purpose is to judge the same user, send customer service messages, template messages, and so on.Enterprise number users need to convert enterprise member userids to openids using the enterprise number userid to openid interface.
1. What is the WeChat merchant platform: Address: https://pay.weixin.qq.com Provides information for businesses to view transaction data, withdrawals, etc. 2. Common payment methods: public number payment, scanner payment, app payment, applet payment Official address: https://pay.weixin.qq.com/wiki/doc/api/index.html Case demo: https://pay.weixin.qq.com/guide/webbased_payment.shtml 3. WeChat payment application process https://pay.weixin.qq.com/guide/qrcode_payment.shtml 1) Application for Public Number (Service Number) Authentication Fee 300 2) Open WeChat Payment
- Merchants submit WeChat payment applications on the WeChat public platform or open platform, and WeChat payment staff will open the corresponding WeChat payment rights after verifying the data correctly.After the approval of the WeChat payment application, the merchant receives a message from the WeChat Payment Assistant in the mailbox filled in the application information, which contains the payment account information needed for development.
2. Scavenging payment flow chart:
Business Process Description:
(1) Business background system generates orders based on the goods selected by users.
(2) Users invoke WeChat Payment (Unified Order API) to generate prepayment transactions after confirming the payment;
(3) The WeChat payment system receives the request, generates the prepayment transaction and returns the two-dimensional code link code_url of the transaction session.
(4) The business background system generates two-dimensional codes based on the returned code_url.
- Two-dimensional code background:
http://www.thonky.com/qr-code-tutorial/
http://coolshell.cn/articles/10590.html
(5) The user opens WeChat to scan the QR code, and the WeChat client sends the scanned content to the WeChat payment system.
(6) The WeChat payment system receives a client request, verifies the validity of the link, and initiates a user payment, which requires authorization from the user.
(7) After the user enters the password in the Wechat client and confirms the payment, the Wechat client submits the authorization.
(8) The WeChat payment system completes payment transactions according to user authorization.
(9) After the payment transaction is completed, the Wechat payment system returns the transaction results to the Wechat client, and prompts the user through SMS and Wechat messages.The WeChat client displays the Payment Transaction Results page.
(10) The WeChat payment system notifies the merchant's background system of the payment result by sending an asynchronous message.The merchant background system needs to reply to the receipt and notify the WeChat background system not to send any more payment notifications for the order.
(11) In case no payment notification is received, the merchant background system calls [Query Order API].
(12) The merchant sends the goods to the user after confirming that the order has been paid.
3. Process analysis:
3.1 WeChat Payment Related Tools:
md5, uuid tool class
import java.security.MessageDigest; import java.util.UUID; /** * Encapsulation of common tool classes md5 uuid, etc. */ public class CommonUtils { /** * Generate 32-bit uuid * @return */ public static String generateUUID(){ String uuid = UUID.randomUUID().toString().replaceAll("-","") .substring(0,32); return uuid; } /** * md5 Common Tool Classes * @param data * @return */ public static String MD5(String data){ try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte [] array = md5.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(); }catch (Exception e){ e.printStackTrace(); } return null; } }
httpclient Send Request Tool Class:
import com.google.gson.Gson; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.util.HashMap; import java.util.Map; /** * Encapsulate http get post */ public class HttpUtils { private static final Gson gson = new Gson(); /** * get Method * @param url * @return */ public static Map<String,Object> doGet(String url){ Map<String,Object> map = new HashMap<>(); CloseableHttpClient httpClient = HttpClients.createDefault(); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000) //connection timed out .setConnectionRequestTimeout(5000)//request timeout .setSocketTimeout(5000) .setRedirectsEnabled(true) //Allow automatic redirection .build(); HttpGet httpGet = new HttpGet(url); httpGet.setConfig(requestConfig); try{ HttpResponse httpResponse = httpClient.execute(httpGet); if(httpResponse.getStatusLine().getStatusCode() == 200){ String jsonResult = EntityUtils.toString( httpResponse.getEntity()); map = gson.fromJson(jsonResult,map.getClass()); } }catch (Exception e){ e.printStackTrace(); }finally { try { httpClient.close(); }catch (Exception e){ e.printStackTrace(); } } return map; } /** * Encapsulate post * @return */ public static String doPost(String url, String data,int timeout){ CloseableHttpClient httpClient = HttpClients.createDefault(); //time-out RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout) //connection timed out .setConnectionRequestTimeout(timeout)//request timeout .setSocketTimeout(timeout) .setRedirectsEnabled(true) //Allow automatic redirection .build(); HttpPost httpPost = new HttpPost(url); httpPost.setConfig(requestConfig); httpPost.addHeader("Content-Type","text/html; chartset=UTF-8"); if(data != null && data instanceof String){ //Using string arguments StringEntity stringEntity = new StringEntity(data,"UTF-8"); httpPost.setEntity(stringEntity); } try{ CloseableHttpResponse httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); if(httpResponse.getStatusLine().getStatusCode() == 200){ String result = EntityUtils.toString(httpEntity); return result; } }catch (Exception e){ e.printStackTrace(); }finally { try{ httpClient.close(); }catch (Exception e){ e.printStackTrace(); } } return null; } }
ip harvesting tool class:
import java.net.InetAddress; import java.net.UnknownHostException; import javax.servlet.http.HttpServletRequest; public class IpUtils { public static String getIpAddr(HttpServletRequest request) { String ipAddress = null; try { ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals("127.0.0.1")) { // Get locally configured IP based on network card InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress = inet.getHostAddress(); } } // In the case of multiple agents, the first IP is the client's real IP, and multiple IPs are split by', ' if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length() // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress=""; } return ipAddress; } }
jwt Tool Class:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import net.xdclass.xdvideo.domain.User; import java.util.Date; /** * jwt Tool class */ public class JwtUtils { public static final String SUBJECT = "lucky"; public static final long EXPIRE = 1000*60*60*24*7; //Expiration time, milliseconds, week //Keys public static final String APPSECRET = "lucky666"; /** * Generate jwt * @param user * @return */ public static String geneJsonWebToken(User user){ if(user == null || user.getId() == null || user.getName() == null || user.getHeadImg()==null){ return null; } String token = Jwts.builder().setSubject(SUBJECT) .claim("id",user.getId()) .claim("name",user.getName()) .claim("img",user.getHeadImg()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis()+EXPIRE)) .signWith(SignatureAlgorithm.HS256,APPSECRET).compact(); return token; } /** * Check token * @param token * @return */ public static Claims checkJWT(String token ){ try{ final Claims claims = Jwts.parser().setSigningKey(APPSECRET). parseClaimsJws(token).getBody(); return claims; }catch (Exception e){ } return null; } }
WeChat Payment Tools Class: (The original WeChat Payment website also provides: Tool class Download)
import org.w3c.dom.Entity; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringWriter; import java.util.*; /** * WeChat Payment Tool Class, XML to map,map to xml, generate signatures */ public class WXPayUtil { /** * XML Convert 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>(); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.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) { throw ex; } } /** * Convert Map to String in XML Format * * @param data Map Type data * @return XML Format string * @throws Exception */ public static String mapToXml(Map<String, String> data) throws Exception { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder(); org.w3c.dom.Document document = documentBuilder.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"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); try { writer.close(); } catch (Exception ex) { } return output; } /** * Generate WeChat payment sign * @return */ public static String createSign(SortedMap<String, String> params, String key){ StringBuilder sb = new StringBuilder(); Set<Map.Entry<String, String>> es = params.entrySet(); Iterator<Map.Entry<String,String>> it = es.iterator(); //Generate stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA"; while (it.hasNext()){ Map.Entry<String,String> entry = (Map.Entry<String,String>)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=").append(key); String sign = CommonUtils.MD5(sb.toString()).toUpperCase(); return sign; } /** * Verify Signature * @param params * @param key * @return */ public static boolean isCorrectSign(SortedMap<String, String> params, String key){ String sign = createSign(params,key); String weixinPaySign = params.get("sign").toUpperCase(); return weixinPaySign.equals(sign); } /** * Get an ordered map * @param map * @return */ public static SortedMap<String,String> getSortedMap(Map<String,String> map){ SortedMap<String, String> sortedMap = new TreeMap<>(); Iterator<String> it = map.keySet().iterator(); while (it.hasNext()){ String key = (String)it.next(); String value = map.get(key); String temp = ""; if( null != value){ temp = value.trim(); } sortedMap.put(key,temp); } return sortedMap; } }
3.2 Step 1: Generate orders and place them uniformly:
Specific instructions are as follows: Official Instructions
Unified single URL address: https://api.mch.weixin.qq.com/pay/unifiedorder
Unified single-part essential parameter construction:
1. Signature signature construction: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
After generating the signature, be sure to check it yourself:
Verify with tools
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=20_1
/** * Unified approach * @return */ private String unifiedOrder(VideoOrder videoOrder) throws Exception { //int i = 1/0; //Simulate exception //Generate Signature SortedMap<String,String> params = new TreeMap<>(); params.put("appid",weChatConfig.getAppId()); params.put("mch_id", weChatConfig.getMchId()); params.put("nonce_str",CommonUtils.generateUUID()); params.put("body",videoOrder.getVideoTitle()); params.put("out_trade_no",videoOrder.getOutTradeNo()); params.put("total_fee",videoOrder.getTotalFee().toString()); params.put("spbill_create_ip",videoOrder.getIp()); params.put("notify_url",weChatConfig.getPayCallbackUrl()); params.put("trade_type","NATIVE"); //Signature sign ature String sign = WXPayUtil.createSign(params, weChatConfig.getKey()); params.put("sign",sign); //map to xml String payXml = WXPayUtil.mapToXml(params); System.out.println(payXml); //Unified Ordering String orderStr = HttpUtils.doPost(WeChatConfig.getUnifiedOrderUrl(),payXml,4000); if(null == orderStr) { return null; } Map<String, String> unifiedOrderMap = WXPayUtil.xmlToMap(orderStr); System.out.println(unifiedOrderMap.toString()); if(unifiedOrderMap != null) { return unifiedOrderMap.get("code_url"); } return null; } //Return code_url Generate QR code from link to return to foreground, using google method written in controller layer alone try{ //Generate QR Code Configuration Map<EncodeHintType,Object> hints = new HashMap<>(); //Set Error Correction Level hints.put(EncodeHintType.ERROR_CORRECTION,ErrorCorrectionLevel.L); //Encoding type hints.put(EncodeHintType.CHARACTER_SET,"UTF-8"); BitMatrix bitMatrix = new MultiFormatWriter().encode(codeUrl,BarcodeFormat.QR_CODE,400,400,hints); OutputStream out = response.getOutputStream(); MatrixToImageWriter.writeToStream(bitMatrix,"png",out); }catch (Exception e){ e.printStackTrace(); }
It is clear here that the signature is to encrypt and decrypt the transmitted or received data collection using MD5 or HMAC-SHA256, similar to jwt, so here is to sign all the parameters required for a unified order, then put the signature parameters in, and finally send and receive the request, the parameters must be converted to XML format for parsing, and can be sent when it is parsedConvert to xml, convert the XML to map format when accepting the response, get the code_url from the map converted from the response, and generate a two-dimensional code picture from the link.
Problem encountered, solve according to error code
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
Then the scanner payment steps of mode 2 are the interaction between the WeChat client and the WeChat payment system, but by step 10, he asynchronously calls the merchant's interface to notify the payment result.Calls the merchant interface, which updates the order status according to the data information (in xml format) passed by the Wechat payment system, and tells the Wechat payment system.
The merchant background system needs to reply to the receipt and notify the WeChat background system not to send any more payment notifications for the order.
Use Ngrock to receive WeChat callbacks locally and develop callback interfaces:
Payment Result Notification Callback Address is set in "notify_url" (params.put("notify_url", weChatConfig.getPayCallbackUrl())
Be careful:
Callbacks should be post ed. WeChat documents do not have notifications to write callbacks
You can use this comment @RequestMapping
1. The same notification may be sent to the merchant system multiple times.Business systems must be able to handle duplicate notifications correctly.
2. In the background notification interaction, if WeChat receives a response from the merchant that does not conform to the specifications or time-out, WeChat will decide that the notification failed and resend the notification until it succeeds (in the case of unsuccessful notifications, WeChat will issue multiple notifications with a total notification frequency of 15s/15s/30s/3m/10m/30m/30m/60m/3h/3h/6h-24h4m), but WeChat does not guarantee itNotifications will ultimately succeed.
3. In case the status of the order is unknown or WeChat payment result notification has not been received, it is recommended that merchants actively invoke WeChat payment [Query Order API] to confirm the status of the order.
Special reminders:
1. The merchant system must verify the contents of the payment result notification by signature, and verify whether the returned order amount is the same as the merchant side's order amount, so as to prevent data leakage from causing "false notification" and causing financial losses.
2. When receiving a notification for processing, first check the status of the corresponding business data to determine if the notification has been processed, if it has not been processed before processing, if the processing returns the result directly successfully.Data locks are used for concurrency control before status checking and processing of business data to avoid data clutter caused by function reentry.
3. Technicians can log into the background of WeChat merchants to scan and join the interface alarm group to get interface alarm information.
Reference link: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8
/** * WeChat Payment Callback */ @RequestMapping("/order/callback") public void orderCallback(HttpServletRequest request, HttpServletResponse response) throws Exception { InputStream inputStream = request.getInputStream(); //BufferedReader is a packaging design mode for better performance BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8")); StringBuffer sb = new StringBuffer(); String line ; while ((line = in.readLine()) != null){ sb.append(line); } in.close(); inputStream.close(); Map<String,String> callbackMap = WXPayUtil.xmlToMap(sb.toString()); System.out.println(callbackMap.toString()); SortedMap<String,String> sortedMap = WXPayUtil.getSortedMap(callbackMap); //Determine if the signature is correct if(WXPayUtil.isCorrectSign(sortedMap,weChatConfig.getKey())){ if("SUCCESS".equals(sortedMap.get("result_code"))){ String outTradeNo = sortedMap.get("out_trade_no"); VideoOrder dbVideoOrder = videoOrderService.findByOutTradeNo(outTradeNo); if(dbVideoOrder != null && dbVideoOrder.getState()==0){ //Judgment logic for business scenarios VideoOrder videoOrder = new VideoOrder(); videoOrder.setOpenid(sortedMap.get("openid")); videoOrder.setOutTradeNo(outTradeNo); videoOrder.setNotifyTime(new Date()); videoOrder.setState(1); int rows = videoOrderService.updateVideoOderByOutTradeNo(videoOrder); if(rows == 1){ //Notify WeChat of successful order processing response.setContentType("text/xml"); response.getWriter().println("success"); return; } } } } //Failed to process all response.setContentType("text/xml"); response.getWriter().println("fail"); }