1. Préparation
Tout d'abord, se plaignons du paiement de WeChat. Il existe plusieurs modèles de paiement qu'il prend en charge, mais les documents officiels sont particulièrement dispersés, et il n'y a même pas quelques démos décentes liées à Java. Je n'ai jamais effectué de paiement WeChat auparavant. J'en ai été vraiment stupéfait au début. Je l'ai finalement réussi pendant deux jours. Je l'ai écrit ici pour profiter des générations futures!
En ce qui concerne les travaux de préparation, l'adresse officielle du document de "WeChat Scan QR Code Payment Model 2" est ici https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 Vous pouvez d'abord jeter un œil. En fait, il y a les choses suivantes à préparer:
Parmi eux, App_ID et App_secret se trouvent sur la plate-forme publique, tandis que MCH_ID et API_KEY se trouvent sur la plate-forme marchande. En particulier, API_KEY doit être réglé sur la plate-forme marchande. Pour "WeChat Scan Code Mode de paiement 2" (paiement et rappel), seuls App_ID, MCH_ID et API_KEY ne seront utilisés, et rien d'autre n'est utilisé.
Je ne parlerai pas de l'environnement de développement. Que vous soyez SpringMVC, Struts2 ou Direct Serverlet, c'est presque la même chose. Tant que vous pouvez vous assurer que la méthode correspondante peut être appelée. En ce qui concerne les packages en pot tiers, je n'ai utilisé qu'un jdom qui fonctionne ici. N'oubliez pas que c'est la version 1. *, pas la dernière 2. * sur le site officiel, et les deux sont incompatibles. Plus précisément, c'est JDom-1.1.3.jar, le package de dépendance jaxen-1.1.6.jar. Pour ces deux packages, je n'ai pas utilisé le httpclient utilisé dans certains exemples. Il semble inutile et le package de dépendance est très compliqué. Bien sûr, vous êtes Maven quand je ne l'ai pas dit.
2. Développement et combat pratique
1. Tout d'abord, connectez-vous à l'interface WeChat et obtenez le code QR de paiement WeChat.
String public weixin_pay () lève une exception {// String d'information du compte appid = payconfigutil.app_id; // appid // string appSecret = PayConfigutil.app_secret; // AppSecret String mch_id = PayConfigutil.mch_id; // Numéro d'entreprise 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; // Prix Remarque: L'unité de prix est divisée en cordes corporelles = "bourse"; // Nom du produit String Out_Trade_no = "11338"; // Numéro de commande // Obtenez la chaîne IP d'ordinateur initiat SPBILL_CREATE_IP = PayConfigutil.create_ip; // String d'interface de rappel notify_url = payconfigutil.notify_url; String Trade_Type = "Native"; TriMedMap <objet, objet> packageParams = new Treemap <objet, objet> (); packageParams.put ("appid", appid); packageParams.put ("mch_id", mch_id); packageParams.put ("nonce_str", nonce_str); packageParams.put ("corps", corps); 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 ("signe", signe); 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"); RETOUR URLCODE; }Si rien de inattendu ne se produit, nous obtenons une URL de paiement du serveur WeChat, qui ressemble à Weixin: // wxpay / bizpayUrl? Pr = pixxxxx. Après cela, nous devons générer un code QR pour cette URL, puis nous pouvons utiliser notre téléphone portable WeChat pour scanner le code à payer. Il existe de nombreuses façons de générer des codes QR. Veuillez répondre à vos propres besoins. Je fournis ici une interface de génération de code Google QR:
La chaîne statique publique qrfromoGle (String chl) lève l'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; retour qrfromoogle; } // Traitement de caractères spécial Urlencode de chaîne statique (String SRC) lève unportdencodingException {return urlencoder.encode (src, "utf-8"). Remplace ("+", "% 20"); } Le code ci-dessus comprend plusieurs classes d'outils: PayConfigutil, PayComMonutil, Httputil et XmLutil. Parmi eux, PayConfigutil met quelques configurations et chemins mentionnés ci-dessus. PayCommonutil implique plusieurs méthodes d'obtention de l'événement actuel, de génération de chaînes aléatoires, d'obtention de signatures de paramètres et d'épissage du XML. Le code est le suivant:
classe publique PayComMonutil {/ ** * Que la signature soit correcte, la règle est: Trier par nom de paramètre AZ, et les paramètres qui rencontrent les valeurs vides ne participent pas à la signature. * @return boolean * / public static boolean istenpaysign (String CaracterEncoding, tridmap <objet, objet> packageParams, String api_key) {stringBuffer sb = new StringBuffer (); Set es = packageParams.EntrySet (); Iterator it = es.iterator (); while (it.hasnext ()) {map.entry entrée = (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 la chaîne de résumé mysign = md5util.md5encode (sb.toString (), caractéristique) .tolowerCase (); String tenpaysign = ((string) packageParams.get ("signe")). TolowerCase (); //System.out.println(tenpaySign + "" + mySign); return tenpaysign.equals (mysign); } / ** * @Author * @Date 2016-4-22 * @Description: Sign Signature * @Param CaracterEncoding * Format d'encodage * @param Paramètres * Paramètres de demande * @return * / public static String Createsign (String CaractorEncoding, SORDMAP <Object, Objectbuffer (); String api_key) {StringBuffer Sb = New StringbUffer (); Set es = packageParams.EntrySet (); Iterator it = es.iterator (); while (it.hasnext ()) {map.entry entrée = (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); Sign de chaîne = md5util.md5encode (sb.toString (), caractéristique) .ToupperCase (); signe de retour; } / ** * @Author * @Date 2016-4-22 * @Description: Convertir les paramètres de demande en format xml chaîne * @param Paramètres * Paramètres de demande * @return * / public static string getRequestxml (sortmedmap <Object, Object> Paramètres) {stringbuffer sb = new StringBuffer (); sb.append ("<xml>"); Set es = Paramètres.EntrySet (); Iterator it = es.iterator (); while (it.hasnext ()) {map.entry entrée = (map.entry) it.next (); String k = (String) entry.getKey (); String v = (String) entry.getValue (); if ("attach" .equalsignorecase (k) || "body" .equalSignorecase (k) || "signe" .equalsignorecase (k)) {sb.append ("<" + k + ">" + "<! cdata [" + v + "]]> </" + k + ">"); } else {sb.append ("<" + k + ">" + v + "</" + k + ">"); }}} sb.append ("</xml>"); return sb.toString (); } / ** * Sortez un entier positif aléatoire de la taille de longueur spécifiée. * * @param longueur * int définir la longueur du numéro aléatoire récupéré. Longueur inférieure à 11 * @return int renvoie le nombre aléatoire généré. * / public static int buildRandom (int longueur) {int num = 1; Double Random = Math.Random (); if (aléatoire <0,1) {aléatoire = aléatoire + 0,1; } pour (int i = 0; i <longueur; i ++) {num = num * 10; } return (int) ((aléatoire * num)); } / ** * Obtenez l'heure actuelle YyyyMMDDHHMMSS * * @return String * / public static String getCurrtime () {Date Now = new Date (); SimpledateFormat outformat = new SimpledateFormat ("yyyymmddhhmmss"); String s = outformat.format (maintenant); retour s; }}Httputil et Xmlutil sont les suivants:
classe publique httputil {private static final logger = logs.get (); INT STATIQUE final privé Connect_timeout = 5000; // en millions-cends 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; essayez {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); écrivain.flush (); écrivain.close (); Reader = new BufferedReader (new InputStreamReader (Conn.getInputStream (), default_encoding)); StringBuilder sb = new StringBuilder (); Chaîne line = null; while ((line = reader.readline ())! = null) {sb.append (line); sb.append ("/ r / n"); } return sb.toString (); } catch (ioException e) {Logger.Error ("Erreur se connectant à" + urlstr + ":" + e.getMessage ()); } enfin {try {if (reader! = null) Reader.close (); } catch (ioException e) {}} return null; }} Classe publique XMLUTIL {/ ** * Parse XML et renvoyez la paire de valeurs clés de l'élément de premier niveau. Si l'élément de premier niveau a des enfants, la valeur de ce nœud est les données XML du nœud enfant. * @param strXml * @return * @throws jDomexception * @throws ioException * / public static map doxmlparse (String strXml) lance jDomeXception, ioException {strXml = strXml.replaceFirst ("Encoding = /". * "", "Encoding = /" utf-8 / ""); if (null == strXml || "" .equals (strXml)) {return null; } Map m = new hashmap (); InputStream dans = new bytearrayInputStream (strXml.getBytes ("utf-8")); SAXBUILDER BUILDER = NOUVEAU SAXBUILDER (); Document doc = builder.build (in); Élément root = doc.getrootelement (); List list = root.getchildren (); Iterator it = list.iterator (); while (it.hasnext ()) {élément e = (élément) it.next (); String k = e.getName (); String v = ""; Liste des enfants = e.getChildren (); if (enfants.iSempty ()) {v = e.getTextNormalize (); } else {v = xmlutil.getChildRentext (enfants); } M.put (k, v); } // Fermez le flux dans.close (); retour m; } / ** * Obtenez le XML du nœud enfant * @param enfants * @return String * / public static string getChildRentext (list enfants) {StringBuffer sb = new StringBuffer (); if (! enfants.isempty ()) {iterator it = enfants.iterator (); while (it.hasnext ()) {élément e = (élément) 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 (valeur); sb.append ("</" + name + ">"); }} return sb.toString (); }} Bien sûr, il existe également une classe d'outils informatiques MD5
classe publique md5Util {String statique privé bytearraytohexString (byte b []) {stringBuffer ResultsB = new StringBuffer (); pour (int i = 0; i <b.length; i ++) ResultsB.APPEND (bytetoHExString (b [i])); Retour ResultsB.ToString (); } chaîne statique privée byTEToHExString (octet b) {int n = b; if (n <0) n + = 256; int d1 = n / 16; int d2 = n% 16; retour hexdigits [d1] + hexdigits [d2]; } public static String md5encode (String Origin, String CharSetName) {String ResultString = null; try {resultTring = new String (origin); MessagediGest MD = MessagediGest.getInstance ("MD5"); if (charSetName == null || "" .equals (charSetName)) RUSTURSTRING = byteArraytoHexString (md.digest (RUSTUSSTRING .getBytes ())); Else ResulteString = bytearraytohexString (MD.DIGEST (RUSTUSSTRING .GETBYTES (charSetName))); } catch (exception exception) {} return resultTring; } chaîne finale statique privée hexdigits [] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; } 2. Rappel de paiement
Une fois le paiement terminé, WeChat enverra les résultats de paiement pertinents et les informations de l'utilisateur à l'adresse de rappel que nous avons spécifiée ci-dessus. Nous devons recevoir le traitement et retourner la réponse. Lorsque vous interagissez avec les notifications de fond, si WeChat reçoit la réponse d'un marchand sans succès ni délai d'expiration, et WeChat estime que la notification a échoué, WeChat réinitialisera régulièrement les notifications par le biais de certaines stratégies pour augmenter le taux de réussite des notifications autant que possible, mais WECHAT ne garantit pas que la notification réussira à la fin. (La fréquence de notification est 15/15/30/1800/1800/1800/1800/1800/1800/3600, unité: secondes)
En ce qui concerne l'interface de rappel de paiement, nous devons d'abord signer et vérifier le contenu de la notification des résultats de paiement, puis effectuer le processus de traitement correspondant en fonction du résultat de paiement.
public void weixin_notify (httpsservletRequest request, httpservletResponse réponse) lève l'exception {// lire les paramètres inputStream inputStream; StringBuffer sb = new StringBuffer (); inputStream = request.getInputStream (); String S; BufferedReader dans = new BufferedReader (new InputStreamReader (InputStream, "UTF-8")); while ((s = in.readline ())! = null) {sb.append (s); } in.close (); inputStream.close (); // Parse XML dans Map Map <String, String> M = new HashMap <String, String> (); m = xmlutil.doxmlparse (sb.toString ()); // Filtre Paramètres vides Treemap tridmap <objet, objet> packageParams = new Treemap <objet, objet> (); Iterator it = M.KeySet (). Iterator (); while (it.hasnext ()) {string paramètre = (string) it.next (); String ParameTervalue = M.get (paramètre); String v = ""; if (null! = ParameTervalue) {v = ParameTervalue.trim (); } packageParams.put (paramètre, v); } // Informations de compte Key Key = PayConfigutil.API_KEY; // key logger.info (packageParams); // Déterminez si la signature est correcte si (PayComMonutil.istenPaySign ("UTF-8", PackageParams, Key)) { // ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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 ("Paiement réussi"); // informer WeChat. La confirmation asynchrone est réussie. Doit l'écrire. Sinon, l'arrière-plan sera informé tout le temps. Après huit fois, il sera considéré que la transaction a échoué. resxml = "<xml>" + "<Auttour_code> <! [CDATA [Success]]> </ return_code>" + "<retour_msg> <! [cdata [ok]]> </ return_msg>" + "</xml>"; } else {logger.info ("Paiement échoué, message d'erreur:" + packageParams.get ("err_code")); resxml = "<xml>" + "<Adgower_code> <! [cdata [échoué]]> </ return_code>" + "<retour_msg> <! [cdata [message est vide]]> </ return_msg>" + "</ xml>"; } // ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Logger.info ("La vérification de la signature de la notification a échoué"); }} L'algorithme de vérification de signature est similaire à l'algorithme de génération de signature et est fourni dans la classe d'outils PayCommonutil ci-dessus.
3. Stories ultérieures
Je pense que l'expérience de paiement de la numérisation WeChat est assez bonne. Le seul inconvénient est que les documents pertinents sont dispersés. La démo officielle n'est pas écrite en Java. J'espère que le fonctionnaire officiel de WeChat pourra progressivement l'améliorer à l'avenir!
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.