1. Preparación
En primer lugar, nos quejemos del pago de WeChat. Hay varios modelos de pago que admite, pero los documentos oficiales están particularmente dispersos, y ni siquiera hay algunas demostraciones decentes relacionadas con Java. Nunca antes había hecho el pago de WeChat. Me sorprendió mucho al principio. Finalmente lo logré durante dos días. ¡Lo escribí aquí para disfrutar de las generaciones futuras!
Con respecto al trabajo de preparación, la dirección de documento oficial de "WeChat Scan QR Code Payment Model 2" está aquí https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 Puede echar un vistazo primero. De hecho, hay las siguientes cosas para preparar:
Entre ellos, APP_ID y APP_SECRET se pueden encontrar en la plataforma pública, mientras que MCH_ID y API_KEY se encuentran en la plataforma comercial. En particular, API_Key debe estar configurado en la plataforma comercial. Para "Modo de pago del código de escaneo WeChat 2" (pago y devolución de llamada), solo se utilizará APP_ID, MCH_ID y API_KEY, y no se usa nada más.
No hablaré sobre el entorno de desarrollo. Ya sea que sea SpringMVC, Struts2 o Direct Serverlet, es casi lo mismo. Siempre que pueda asegurarse de que se pueda llamar al método correspondiente. Con respecto a citar paquetes de jar de terceros, solo usé un JDOM que opera XML aquí. Recuerde que es la versión 1.*, no la última 2.* en el sitio web oficial, y los dos son incompatibles. Específicamente, es JDOM-1.1.3.Jar, el paquete de dependencia Jaxen-1.1.6.jar. Para estos dos paquetes, no utilicé el httpclient utilizado en algunos ejemplos. Se siente innecesario y el paquete de dependencia es muy complicado. Por supuesto, eres Maven cuando no lo dije.
2. Desarrollo y combate práctico
1. Primero, conéctese a la interfaz WeChat y obtenga el código QR de pago WeChat.
public String weixin_pay () lanza la excepción {// Información de la cuenta String Appid = payConfigUtil.app_id; // appid // string appSecret = payConfigUtil.app_secret; // appSecret String mch_id = payConfigUtil.mch_id; // Key de cadena de número de negocio = payconfigutil.api_key; // Key String Currtime = PayCommonUtil.GetCurrTime (); Cadena strtime = currtime.substring (8, currtime.length ()); Cadena strrandom = paycommonutil.buildrandom (4) + ""; Cadena nonce_str = strtime + strrandom; Cadena orden_price = 1; // Nota de precio: la unidad de precio se divide en string body = "Goodssssss"; // Nombre del producto String out_trade_no = "11338"; // Número de pedido // Obtener la cadena IP de la computadora inicial spbill_create_ip = payconfigutil.create_ip; // String de interfaz de devolución de llamada notify_url = payconfigutil.notify_url; Cadena comercial_type = "nativa"; SortedMap <Object, Object> PackageParams = new Treemap <Object, Object> (); paquete de paquetes.put ("appid", appid); paqueteparams.put ("mch_id", mch_id); paqueteparams.put ("nonce_str", nonce_str); paquete de paquetes.put ("cuerpo", cuerpo); paqueteparams.put ("out_trade_no", out_trade_no); paquete de paquetes.put ("Total_fee", Order_Price); paqueteparams.put ("spbill_create_ip", spbill_create_ip); paqueteparams.put ("notify_url", notify_url); paquete de paquetes.put ("Trade_Type", Trade_Type); String Sign = PayCommonUtil.CreateSign ("UTF-8", paquetes de paquete, clave); paquete de paquetes.put ("firmar", firmar); String requestXml = payCommonUtil.getRequestXml (paquetes de paquete); System.out.println (requestXml); Cadena resxml = httputil.postdata (payConfigUtil.ufDoder_url, requestXml); Mapa 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"); devolver URLCODE; }Si no sucede nada inesperado, obtenemos una URL de pago del servidor WeChat, que parece weixin: // wxpay/bizpayurl? Pr = pixxxxxx. Después de eso, necesitamos generar un código QR para esta URL, y luego podemos usar nuestro teléfono móvil WeChat para escanear el código para pagar. Hay muchas formas de generar códigos QR. Por favor, tome sus propias necesidades. Proporciono una interfaz de generación de código de Google QR aquí:
public static String QrFromGoogle (String Chl) lanza la excepción {int widhtheight = 300; Cadena ec_level = "l"; int margen = 0; chl = urlencode (chl); Cadena qrfromgoogle = "http://chart.apis.google.com/chart?chs=" + widhtheight + "x" + widhtheight + "& cht = qr & chld =" + ec_level + "|" + margen + "& chl =" + chl; devolver qrfromgoogle; } // Procesamiento de caracteres especiales Public static string urlencode (String src) lanza sin apoyo deCodingException {return urlencoder.encode (src, "utf-8"). Reemplazar ("+", "%20"); } El código anterior involucra varias clases de herramientas: PayConfigutil, PayCommonutil, Httputil y Xmlutil. Entre ellos, PayConfigutil coloca algunas configuraciones y rutas mencionadas anteriormente. PayCommonutil implica varios métodos para obtener el evento actual, generando cadenas aleatorias, obteniendo firmas de parámetros y XML de empalme. El código es el siguiente:
clase pública PayCommonutil { /*** Si la firma es correcta, la regla es: ordenar por el nombre del parámetro AZ y los parámetros que encuentran valores vacíos no participan en la firma. * @return boolean */ public static boolean istenpaysign (string caracterescoding, sortedmap <object, object> paqueteparams, string api_key) {stringbuffer sb = new StringBuffer (); Establecer ES = paqueteparams.entryset (); Iterador it = es.iterator (); while (it.hasnext ()) {map.entry entry = (map.entry) it.next (); Cadena k = (string) Entry.getKey (); String V = (String) Entry.getValue (); if (! "firmar" .equals (k) && null! = v &&! "". Equals (v)) {sb.append (k + "=" + v + "&"); }} sb.append ("key =" + api_key); // File la cadena de resumen mySign = md5util.md5encode (sb.ToString (), caracterSencoding) .tolowercase (); Cadena tenpaysign = (((string) paqueteParams.get ("firmar")). TOlowerCase (); //System.out.println(tenpaysign + "" + mySign); regresar tenpaysign.equals (mysign); } / ** * @author * @Date 2016-4-22 * @Description: firma firma * @param caracterescoding * formato de codificación * @param parámetros * parámetros de solicitud * @return * / public static string createSign (string string caracterescoding, shatedmap <objeto, objeto> paquete de paquete, api_key) {stringbuffer sb = niewbeferfer (); Establecer ES = paqueteparams.entryset (); Iterador it = es.iterator (); while (it.hasnext ()) {map.entry entry = (map.entry) it.next (); Cadena k = (string) Entry.getKey (); String V = (String) Entry.getValue (); if (null! = v &&! "". iguales (v) &&! "firmar" .equals (k) &&! "clave" .equals (k)) {sb.append (k + "=" + v + "&"); }} sb.append ("key =" + api_key); Firma de cadena = md5util.md5encode (sb.toString (), caracterSencoding) .ToUpperCase (); señal de regreso; } / ** * @author * @Date 2016-4-22 * @Description: Convertir los parámetros de solicitud en String de formato XML * @param Parámetros * Parámetros de solicitud * @return * / public static string getRequestxml (sortedmap <objeto, objeto> parámetros) {stringbuffer sb = new stringbuffer (); sb.append ("<xml>"); Establecer ES = parámetros.EntrySet (); Iterador it = es.iterator (); while (it.hasnext ()) {map.entry entry = (map.entry) it.next (); Cadena k = (string) Entry.getKey (); String V = (String) Entry.getValue (); if ("adjunte" .equalSignorecase (k) || "cuerpo" .equalsignorecase (k) || "firma" .equalsignorecase (k)) {sb.append ("<" + k + ">" + "<! [CDATA [" + v + "]> </" + k + ">"); } else {sb.append ("<" + k + ">" + v + "</" + k + ">"); }}} sb.append ("</xml>"); return sb.ToString (); } /*** Saque un entero positivo aleatorio del tamaño de longitud especificado. * * @param longitud * int establece la longitud del número aleatorio recuperado. Longitud inferior a 11 * @return int devuelve el número aleatorio generado. */ public static int buildrandom (int longitud) {int num = 1; Double Random = Math.random (); if (aleatorio <0.1) {aleatorio = aleatorio + 0.1; } para (int i = 0; i <longitud; i ++) {num = num * 10; } return (int) (((nom * num)); } / ** * Obtenga la hora actual yyyymmddhhmmss * * @return string * / public static string getCurrTime () {date ahora = new Date (); SimpleDateFormat outFormat = new SimpleDateFormat ("yyyymmddhhmmss"); Cadena S = OutFormat.Format (ahora); regreso s; }}Httputil y xmlutil son los siguientes:
clase pública httputil {private static final log logger = logs.get (); Private final static int conect_timeout = 5000; // En millones de segundos, la cadena estática final privada 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 lector = null; intente {url url = new url (urlstr); Urlconnection conn = url.openconnection (); Conn.setDoOutput (verdadero); conn.setConnectTimeOut (Connect_TimeOut); Conn.SetReadTimeOut (Connect_TimeOut); if (contentType! = null) conn.setRequestProperty ("Content-type", contentType); OutputStreamWriter escritor = nuevo OutputStreamWriter (conn.getOutputStream (), default_Encoding); if (data == null) data = ""; escritor.write (datos); escritor.flush (); escritor.close (); lector = new BufferedReader (new InputStreamReader (conn.getInputStream (), default_Encoding)); StringBuilder sb = new StringBuilder (); Línea de cadena = nulo; while ((línea = lector.readline ())! = null) {sb.append (línea); sb.append ("/r/n"); } return sb.ToString (); } catch (ioException e) {logger.error ("Error conectado a" + urlstr + ":" + e.getMessage ()); } Finalmente {try {if (lector! = null) lector.close (); } catch (ioException e) {}} return null; }} clase pública XMLUTIL { /*** Parse XML y devuelve el par de valores de llave de elemento de primer nivel. Si el elemento de primer nivel tiene hijos, el valor de este nodo son los datos XML del nodo infantil. * @param strxml * @return * @throws jdomexception * @throws ioexception */public static map doxmlParse (String strxml) lanza jdomexception, ioexception {strxml = strxml.replaceSt ("encod =/". */"", encodando =/"utf-8/" ");"); if (null == strxml || "" .equals (strxml)) {return null; } Mapa m = nuevo hashmap (); InputStream in = new byteArrayInputStream (strxml.getBytes ("utf-8")); SaxBuilder Builder = new SaxBuilder (); Documento doc = builder.build (in); Elemento root = doc.getRootElement (); List list = root.getChildren (); Iterador it = list.iterator (); while (it.hasnext ()) {element e = (element) it.next (); Cadena k = e.getName (); Cadena v = ""; Lista de niños = E.GetChildren (); if (children.isEmpty ()) {v = e.getTextNormalize (); } else {v = xmlutil.getChildrRentext (niños); } m.put (k, v); } // cierre la transmisión en.close (); regresar m; } / ** * Obtenga el XML del nodo infantil * @param niños * @return cadena * / public static string getChildrenText (List Children) {StringBuffer sb = new StringBuffer (); if (? while (it.hasnext ()) {element e = (element) it.next (); Name de cadena = e.getName (); Valor de cadena = e.getTextNormalize (); Lista de lista = E.GetChildren (); sb.append ("<" + name + ">"); if (! list.isEmpty ()) {sb.append (xmlutil.getChildrenText (list)); } sb.append (valor); sb.append ("</" + name + ">"); }} return sb.ToString (); }} Por supuesto, también hay una clase de herramienta de computación MD5
clase pública MD5UTIL {string static privado bytearrayToHexString (byte b []) {stringBuffer resultadosb = new StringBuffer (); para (int i = 0; i <b.length; i ++) resultadosb.append (bytetohexstring (b [i])); return resultadosb.ToString (); } cadena estática privada 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 md5Endode (origen de cadena, string charsetName) {String resultados de resultados = nulo; intente {resultados de resultados = new String (origen); MessageDigest MD = MessageGest.getInstance ("MD5"); if (charsetName == null || "" .equals (charsetName)) resultados = bytearrayToHexString (md.digest (resultados de resultados .getBytes ())); else ResultString = byteArrayToHexString (md.digest (resultados de resultados .getBytes (charsetName))); } catch (excepción de excepción) {} return ResultString; } hexdigits de cadena final estática privada [] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "b", "c", "d", "e", "f"}; } 2. Devolución de llamada de pago
Después de completar el pago, WeChat enviará los resultados de pago relevantes y la información del usuario a la dirección de devolución de llamada que especificamos anteriormente. Necesitamos recibir el procesamiento y devolver la respuesta. Al interactuar con las notificaciones de antecedentes, si WeChat recibe la respuesta de un comerciante sin éxito o tiempo de espera, y WeChat cree que la notificación ha fallado, WeChat reiniciará regularmente las notificaciones a través de ciertas estrategias para aumentar la tasa de éxito de las notificaciones tanto como sea posible, pero Wechat no garantiza que la notificación sea exitosa en el final. (La frecuencia de notificación es 15/15/30/1800/1800/1800/1800/1800/1800/3600, unidad: segundos)
Con respecto a la interfaz de devolución de llamada de pago, primero debemos firmar y verificar el contenido de la notificación del resultado del pago, y luego realizar el proceso de procesamiento correspondiente en función del resultado del pago.
public void weixin_notify (solicitud httpservletRequest, respuesta httpservletResponse) lanza la excepción {// lee los parámetros inputStream inputStream; StringBuffer sb = new StringBuffer (); inputStream = request.getInputStream (); Cadena S; BufferedReader in = new BufferedReader (new InputStreamReader (inputStream, "UTF-8")); while ((s = in.readline ())! = null) {sb.append (s); } in.close (); inputStream.close (); // Parse xml en map map <string, string> m = new Hashmap <String, String> (); m = xmlutil.doxmlParse (sb.ToString ()); // Filtrar configuración vacía Treemap SortedMap <Object, Object> PackageParams = new Treemap <Object, Object> (); Iterador it = m.keySet (). Iterator (); while (it.hasNext ()) {String Parameter = (String) It.Next (); String Parametervalue = M.Get (Parámetro); Cadena v = ""; if (null! = parametervalue) {v = parametervalue.trim (); } paquete Params.put (parámetro, v); } // Cadena de información de la cuenta = PayConfigUtil.api_key; // Key Logger.info (paquete de paramentos); // Determinar si la firma es correcta if (payCommonUtil.istenPaysign ("UTF-8", paquete de paramentos, clave)) { // --------------------------------------------------------------------------------------------------------------------------------------------------- inicio // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------- (String) paqueteparams.get ("out_trade_no"); String Total_fee = (String) PackleParams.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 ("Pago exitoso"); // notificar a WeChat. La confirmación asincrónica es exitosa. Debe escribirlo. De lo contrario, los antecedentes serán notificados todo el tiempo. Después de ocho veces, se considerará que la transacción ha fallado. resxml = "<xml>" + "<surne_code> <! [CDATA [Success]]> </return_code>" + "<surn_msg> <! [CDATA [OK]]> </shurn_msg>" + "</xml>"; } else {logger.info ("Pague fallido, mensaje de error:" + paqueteparams.get ("err_code")); resxml = "<xml>" + "<surne_code> <! [CDATA [Fail]>> </return_code>" + "<surn_msg> <! [CDATA [Mensaje está vacío]]> </return_msg>" + "</xml>"; } // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- logger.info ("Falló de verificación de firma de notificación"); }} El algoritmo de verificación de firma es similar al algoritmo de generación de firma, y se proporciona en la clase de herramienta PayCommonutil anterior.
3. Historias posteriores
Siento que la experiencia de pago de WeChat Scanning es bastante buena. La única desventaja es que los documentos relevantes están dispersos. La demostración oficial no está escrita en Java. ¡Espero que el funcionario oficial de WeChat pueda mejorarlo gradualmente en el futuro!
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.