1. Preparação
Primeiro de tudo, vamos reclamar do pagamento do WeChat. Existem vários modelos de pagamento que ele suporta, mas os documentos oficiais estão particularmente dispersos e não há nem algumas demos decentes relacionados a Java. Eu nunca fiz o pagamento do WeChat antes. Fiquei realmente atordoado com isso no começo. Finalmente consegui passar por dois dias. Eu escrevi aqui para aproveitar as gerações futuras!
Em relação ao trabalho de preparação, o endereço oficial do documento de "WeChat Scan QR Payment Model 2" está aqui https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1, você pode dar uma olhada primeiro. De fato, há as seguintes coisas a se preparar:
Entre eles, APP_ID e APP_SECRET podem ser encontrados na plataforma pública, enquanto MCH_ID e API_KEY são encontrados na plataforma de comerciantes. Em particular, o API_KEY deve ser definido na plataforma do comerciante. Para "WeChat Scan Code Payment Mode 2" (pagamento e retorno de chamada), apenas APP_ID, MCH_ID e API_KEY serão usados e nada mais é usado.
Não vou falar sobre o ambiente de desenvolvimento. Se você é Springmvc, Struts2 ou Direct ServerLet, é quase o mesmo. Contanto que você possa garantir que o método correspondente possa ser chamado. Em relação à citação de pacotes JAR de terceiros, usei apenas uma jddom que opera XML aqui. Lembre -se de que é a versão 1.*, não a mais recente 2.* No site oficial, e os dois são incompatíveis. Especificamente, é JDOM-1.1.3.Jar, o pacote de dependência Jaxen-1.1.6.jar. Para esses dois pacotes, não usei o httpclient usado em alguns exemplos. Parece desnecessário e o pacote de dependência é muito complicado. Claro, você é maven quando eu não disse isso.
2. Desenvolvimento e combate prático
1. Primeiro, conecte -se à interface do WeChat e obtenha o código QR de pagamento do WeChat.
public String weixin_pay () lança exceção {// Informações da conta String appid = payconfiguil.app_id; // appid // string appSecret = payconfiguil.app_secret; // appSecret string mch_id = payconfiguil.mch_id; // Número de negócios String key = payconfiguil.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; // NOTA DE PREÇO: A unidade de preço é dividida em String body = "benssssssss"; // Nome do produto String out_trade_no = "11338"; // Número do pedido // Obtenha o computador IP String string spbill_create_ip = payconfiguil.create_ip; // interface de retorno de chamada string notify_url = payconfiguil.notify_url; String trade_type = "nativo"; STORDMAP <Object, Object> PackageParams = novo Treemap <Object, Object> (); packageParams.put ("Appid", Appid); packageParams.put ("mch_id", mch_id); packageParams.put ("nonce_str", nonce_str); packageParams.put ("corpo", corpo); 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 signo = paycommonutil.createSign ("utf-8", packageParams, chave); packageParams.put ("sinal", sinal); String requestXml = payCommonutil.getRequestxml (packageParams); System.out.println (requestXml); String resxml = httputil.postdata (payconfiguil.ufdoder_url, requestXml); Mapa mapa = xmlutil.doxmlParse (resxml); // string return_code = (string) map.get ("return_code"); // string prepay_id = (string) map.get ("pré -any_id"); String urlcode = (string) map.get ("code_url"); retornar urlcode; }Se nada inesperado acontecer, obtemos um URL de pagamento do servidor WeChat, que parece weixin: // wxpay/bizpayurl? Pr = pixxxxx. Depois disso, precisamos gerar um código QR para este URL e, em seguida, podemos usar nosso telefone celular WeChat para digitalizar o código para pagar. Existem muitas maneiras de gerar códigos QR. Por favor, pegue suas próprias necessidades. Eu forneço uma interface de geração de código QR do Google aqui:
public static string qrfromgoogle (string chl) lança exceção {int widhtheight = 300; String ec_level = "l"; Int margem = 0; CHL = urlencode (Chl); String qrfromgoogle = "http://chart.apis.google.com/Chart?chs=" + widhtheight + "x" + widhtheight + "& cht = qr & chld =" + ec_level + "|" + margem + "& chl =" + chl; retornar qrfromgoogle; } // Processamento especial de caracteres Public Static String Urlencode (String SRC) lança UnsupportEdEncodingException {return urlencoder.encode (src, "utf-8"). Substituir ("+", "%20"); } O código acima envolve várias classes de ferramentas: payconfiguil, paycommonutil, httputil e xmlutil. Entre eles, o PayConfiguil coloca algumas configurações e caminhos mencionados acima. O PayCommonutil envolve vários métodos para obter o evento atual, gerando seqüências aleatórias, obtendo assinaturas de parâmetros e splicing XML. O código é o seguinte:
classe pública PayCommonutil { /*** Se a assinatura está correta, a regra é: classificar pelo nome do parâmetro AZ e parâmetros que encontram valores vazios não participam da assinatura. * @return boolean */ public estático boolean iStenPaysign (String CaracterEncoding, classEdMap <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) entrada.getKey (); String v = (string) Entry.getValue (); if (! "Sign" .equals (k) && null! = v &&! "". Equals (v)) {sb.append (k + "=" + v + "&"); }} sb.append ("key =" + api_key); // arquive a string de resumo mysign = md5util.md5Encode (sb.toString (), característica) .tolowercase (); String tenPaysign = ((String) packageParams.get ("Sign")). ToLowerCase (); //System.out.println(tenPaysign + "" + mySign); retornar tenPaysign.equals (mysign); } / ** * @Author * @Date 2016-4-22 * @Description: assinatura Signature * @param caracterencoding * codificação formato * parâmetros @param * parâmetros de solicitação * @return * / public static string (string) {string caracterencoding, classificada <justerB, stringpPearMs, stringsign) {stringcoding, stringMap <object> packcestParams, string API_Key) {stringninging string; SET ES = packageParams.EntrySet (); Iterator it = es.iterator (); while (it.hasNext ()) {map.entry Entry = (map.entry) it.Next (); String k = (string) entrada.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 signo = md5util.md5Encode (sb.toString (), característica) .toupPercase (); sinal de retorno; } / ** * @Author * @Date 2016-4-22 * @Description: Convert Parâmetros de solicitação para xml format string * @param parâmetros * parâmetros de solicitação * @return * / public static string getRequestxml (STORNEDMAP <object> Parameters) {StringBuffer sb = stringBer (STORNDMAP <Object> sb.append ("<xml>"); Set es = parameters.entrySet (); Iterator it = es.iterator (); while (it.hasNext ()) {map.entry Entry = (map.entry) it.Next (); String k = (string) entrada.getKey (); String v = (string) Entry.getValue (); if ("Anexar" .EqualSignorecase (k) || "corpo" .EqualSignorecase (k) || "Sign" .EqualSignorecase (k)) {sb.append ("<" + k + ">" + "<! [Cdata [" + v + "]> </" + k + ">"); } else {sb.append ("<" + k + ">" + v + "</" + k + ">"); }}} sb.append ("</xml>"); return sb.toString (); } /*** Retire um número inteiro positivo aleatório do tamanho do comprimento especificado. * * @param Comprimento * int Defina o comprimento do número aleatório recuperado. Comprimento menor que 11 * @return int retorna o número aleatório gerado. */ public static int BuildRandom (int length) {int num = 1; dupla aleatória = math.random (); if (aleatório <0,1) {aleatório = aleatório + 0,1; } para (int i = 0; i <comprimento; i ++) {num = num * 10; } return (int) ((aleatório * num)); } / ** * Obtenha a hora atual yyyymmddhhmmss * * @return string * / public static string getCurrtime () {date agora = new Date (); SimpledateFormat forformat = new SimpleDateFormat ("yyyymmddhhmmss"); String s = forformat.format (agora); retorno s; }}Httputil e xmlutil são os seguintes:
classe pública httputil {private static final logger = logs.get (); private final static int conect_timeout = 5000; // em milhões de string estática final privada de MillionSonds Default_encoding = "UTF-8"; public static string postData (string urlstr, string dados) {return PostData (urlstr, dados, nulo); } public static string postData (string urlstr, string dados, string contenttype) {bufferredreader leitor = null; tente {url url = novo 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 O outputStreamWriter (Conn.GetOutputStream (), Default_Encoding); if (data == null) data = ""; writer.write (dados); writer.flush (); writer.close (); leitor = new bufferredreader (new inputStreamReader (Conn.getInputStream (), default_encoding)); Stringbuilder sb = new stringbuilder (); Linha de string = null; while ((line = reader.readline ())! = null) {sb.append (line); sb.append ("/r/n"); } return sb.toString (); } catch (ioexception e) {Logger.error ("Erro conectando -se a" + urlstr + ":" + e.getMessage ()); } finalmente {tente {if (leitor! = null) leitor.close (); } catch (ioexception e) {}} retorna null; }} classe pública xmlutil { /*** Parse XML e retorne o par de valores-chave do elemento de primeiro nível. Se o elemento de primeiro nível tiver filhos, o valor desse nó é os dados XML do nó filho. * @param strxml * @return * @throws jdomexception * @throws ioexception */mapa estático público doxmlparse (strx strxml) lança jdomexception, ioexception {strxml = strxml.replacefirst ("codificação =/". */"" "" if (null == strxml || "" .equals (strxml)) {return null; } Mapa m = new hashmap (); InputStream in = new ByteArrayInputStream (strxml.getbytes ("utf-8")); Saxbuilder Builder = new SaxBuilder (); Documento doc = builder.build (in); Elemento root = doc.getrootelement (); Lista da lista = root.getChildren (); Iterator it = list.iterator (); while (it.hasnext ()) {elemento e = (elemento) it.next (); String k = e.getName (); String v = ""; Listar crianças = e.getChildren (); if (Children.isEmpty ()) {v = e.getTextNormalize (); } else {v = xmlutil.getChildrentExt (crianças); } m.put (k, v); } // feche o fluxo em.close (); retornar m; } / ** * Obtenha o XML do nó filho * @param crianças * @return string * / public static string getChildrentExt (listar crianças) {stringbuffer sb = new StringBuffer (); if (! Children.isempty ()) {iterator it = Childrel.iterator (); while (it.hasnext ()) {elemento e = (elemento) it.next (); Nome da string = e.getName (); String value = e.getTextNormalize (); Lista de lista = e.getChildren (); sb.append ("<" + nome + ">"); if (! list.isEmpty ()) {sb.append (xmlutil.getChildrentExt (list)); } sb.append (valor); sb.append ("</" + name + ">"); }} return sb.toString (); }} Claro que há também uma classe de ferramentas de computação MD5
classe pública md5util {private static string byteArraytoHexString (byte b []) {stringBuffer ResultsB = new StringBuffer (); for (int i = 0; i <b.length; i ++) Resultadosb.append (bytetoHexString (b [i])); retorno resultab.toString (); } string estática privada bytetoHexString (byte b) {int n = b; if (n <0) n += 256; int d1 = n / 16; int d2 = n % 16; retornar hexdigits [d1] + hexdigits [d2]; } public static string md5Encode (origem da string, string charsetName) {string ResultsTring = null; tente {ResultadoTring = new String (origem); Messagedigest md = Messagedigest.getInstance ("md5"); if (charsetName == null || "" .equals (charsetName)) resultTring = bytearraytoHexString (md.digest (resultTring .getBytes ())); else ResultsTring = byteArraytoHexString (md.digest (ResultsTring .GetBytes (CharSetName))); } catch (exceção de exceção) {} retorna o resultado de resultados; } private Static Final String HexDigits [] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A" A "," B "," C "," D "," E "," F "}; } 2. Retorno de chamada de pagamento
Após a conclusão do pagamento, o WeChat enviará os resultados do pagamento relevante e as informações do usuário para o endereço de retorno de chamada que especificamos acima. Precisamos receber o processamento e retornar a resposta. Ao interagir com as notificações de segundo plano, se o WeChat receber a resposta de um comerciante sem sucesso ou tempo limite, e WeChat acredita que a notificação falhou, o WeChat reiniciará regularmente as notificações por meio de certas estratégias para aumentar a taxa de sucesso de notificações o máximo possível, mas o Wechat não garante que a notificação será bem-sucedida no final. (A frequência de notificação é 15/5/30/1800/1800/1800/1800/1800/1800/3600, unidade: segundos)
Em relação à interface de retorno de chamada de pagamento, devemos primeiro assinar e verificar o conteúdo da notificação do resultado do pagamento e, em seguida, realizar o processo de processamento correspondente com base no resultado do pagamento.
public void weixin_notify (solicitação httpServletRequest, httpServletResponse resposta) lança exceção {// leia os parâmetros inputStream inputStream; StringBuffer sb = new StringBuffer (); inputStream = request.getInputStream (); Strings s; BufferredReader in = new BufferredReader (novo InputStreamReader (InputStream, "UTF-8")); while ((s = in.readline ())! = null) {sb.append (s); } in.close (); inputStream.close (); // Parse xml no mapa mapa <string, string> m = new hashmap <string, string> (); m = xmlutil.doxmlParse (sb.toString ()); // filtre Configurações vazias Treemap classEdMap <Object, Object> packageParams = new Treemap <Object, object> (); Iterator it = m.KeySet (). Iterator (); while (it.hasnext ()) {string parâmetro = (string) it.next (); String parameterValue = m.get (parâmetro); String v = ""; if (null! = parameTerValue) {v = parametervalue.trim (); } packageParams.put (parâmetro, v); } // Informações da conta String key = payconfiguil.api_key; // key logger.info (packageParams); // Determine se a assinatura está correta se (payCommonutil.istenPaysign ("utf-8", packageParams, chave)) { //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- //Processing business Inicia // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- (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 ("pagamento bem -sucedido"); // Notifique o WeChat. A confirmação assíncrona é bem -sucedida. Deve escrever. Caso contrário, o plano de fundo será notificado o tempo todo. Após oito vezes, será considerado que a transação falhou. resxml = "<xml>" + "<rort_code> <! [CDATA [success]]> </return_code>" + "<rodelt_msg> <! [CDATA [ok]] </return_msg>" + "</xml>"; } else {Logger.info ("Falha no pagamento, mensagem de erro:" + packageParams.get ("err_code")); resxml = "<xml>" + "<rort_code> <! [CDATA [Fail]]> </return_code>" + "<rodelt_msg> <! [CDATA [Mensagem está vazio]> </return_msg>" + "</xml>"; } //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Logger.info ("Falha na verificação da assinatura de notificação"); }} O algoritmo de verificação de assinatura é semelhante ao algoritmo de geração de assinatura e é fornecido na classe de ferramentas PayCommonutil acima.
3. Histórias posteriores
Sinto que a experiência de pagamento da digitalização do WeChat é muito boa. A única desvantagem é que os documentos relevantes estão espalhados. A demonstração oficial não está escrita em Java. Espero que o funcionário oficial do WeChat possa melhorá -lo gradualmente no futuro!
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.