1. 준비
우선, WeChat의 지불에 대해 불평합시다. 지원하는 몇 가지 결제 모델이 있지만 공식 문서는 특히 흩어져 있으며 적절한 Java 관련 데모도 없습니다. 나는 전에 wechat 결제를 한 적이 없다. 나는 처음에 그것에 의해 정말 기절했다. 나는 마침내 이틀 동안 그것을 얻었다. 나는 미래 세대를 즐기기 위해 여기에 썼다!
준비 작업과 관련하여 "WeChat Scan QR Code Payment Model 2"의 공식 문서 주소는 여기에 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1입니다. 실제로 준비해야 할 사항은 다음과 같습니다.
그중에서도 APP_ID 및 APP_SECRET은 공개 플랫폼에서 찾을 수 있으며 MCH_ID 및 API_KEY는 판매자 플랫폼에서 찾을 수 있습니다. 특히 API_Key는 판매자 플랫폼에 설정해야합니다. "WeChat 스캔 코드 결제 모드 2"(지불 및 콜백)의 경우 APP_ID, MCH_ID 및 API_KEY 만 사용되며 다른 것이 사용되지 않습니다.
나는 개발 환경에 대해 이야기하지 않을 것입니다. SpringMVC, Struts2 또는 Direct Serverlet이든 거의 동일합니다. 해당 방법을 호출 할 수있는 한. 타사 항아리 패키지를 인용하는 것과 관련하여 XML을 여기에서 작동하는 JDOM 만 사용했습니다. 그것은 1.* 버전, 최신 2.* 공식 웹 사이트에서, 두 사람은 호환되지 않습니다. 구체적으로, 그것은 종속성 패키지 jaxen-1.1.6.jar입니다. 이 두 패키지의 경우 몇 가지 예에서 사용 된 httpclient를 사용하지 않았습니다. 불필요하게 느껴지고 종속성 패키지는 매우 복잡합니다. 물론, 내가 말하지 않았을 때 당신은 Maven입니다.
2. 개발과 실제 전투
1. 먼저 WeChat 인터페이스에 연결하고 WeChat 결제 QR 코드를 얻습니다.
public String weixin_pay ()는 예외 {// 계정 정보 문자열 appid = payconfigutil.app_id; // appid // String appsecret = payconfigutil.app_secret; // appsecret String mch_id = payconfigutil.mch_id; // 비즈니스 번호 문자열 키 = PayConfigutil.api_key; // 키 스트링 currime = paycommonutil.getCurrtime (); 문자열 strime = currtime.substring (8, currtime.length ()); 문자열 strrandom = paycommonutil.buildrandom (4) + ""; 문자열 nonce_str = strime + strrandom; 문자열 order_price = 1; // 가격 참고 : 가격 단위는 문자열 body = "goodssssssss"로 나뉩니다. // 제품 이름 문자열 out_trade_no = "11338"; // 주문 번호 // 시작 컴퓨터 IP 문자열 SPBILL_CREATE_IP = PAYCONFIGUTIL.CREATE_IP; // 콜백 인터페이스 문자열 notify_url = payconfigutil.notify_url; String trade_type = "기본"; SortedMap <객체, 개체> PackageParams = new Treemap <개체, 개체> (); 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); 문자열 부호 = paycommonutil.createsign ( "UTF-8", PackageParams, Key); PackageParams.put ( "부호", 부호); 문자열 requestXml = PayCommonUtil.getRequestXml (PackageParams); System.out.println (requestxml); 문자열 RESXML = httputil.postData (PayConfigutil.ufDoder_URL, requestXml); 맵 맵 = xmlutil.doxmlparse (resxml); // String return_code = (String) map.get ( "return_code"); // String prepay_id = (String) map.get ( "pleray_id"); String urlCode = (String) map.get ( "code_url"); urlcode를 반환합니다. }예상치 못한 일이 발생하지 않으면 Weixin : // wxpay/bizpayurl? pr = pixxxxx와 같은 WeChat 서버에서 지불 URL을 얻습니다. 그런 다음이 URL에 대한 QR 코드를 생성 한 다음 휴대 전화 wechat을 사용하여 지불 할 코드를 스캔 할 수 있습니다. QR 코드를 생성하는 방법에는 여러 가지가 있습니다. 자신의 필요를 가져 가십시오. 여기에 Google QR 코드 생성 인터페이스를 제공합니다.
public static string qrfromgoogle (String CHL)은 예외 {int widhtheight = 300; 문자열 ec_level = "l"; int margin = 0; chl = urlencode (chl); 문자열 qrfromgoogle = "http://chart.apis.google.com/chart?chs=" + widhtheight + "x" + widhtheight + "& cht = qr & chld =" + ec_level + "|" + 마진 + "& chl =" + chl; QRFROMGOOGLE을 반환합니다. } // 특수 문자 처리 공용 정적 문자열 UrlenCode (String SRC)는 UnsupportedEncodingException {return urlencoder.encode (src, "utf-8")를 던졌습니다. 대체 ( "+", "%20"); } 위의 코드에는 PayConfigutil, PayCommonutil, Httputil 및 Xmlutil의 여러 도구 클래스가 포함됩니다. 그중에 PayConfigutil은 위에서 언급 한 구성과 경로를 넣습니다. PayCommonutil에는 현재 이벤트를 얻고, 임의의 문자열을 생성하고, 매개 변수 서명을 얻고, XML을 접합시키는 몇 가지 방법이 포함됩니다. 코드는 다음과 같습니다.
공개 클래스 PayCommonUtil { /*** 서명이 올바른지 여부에 관계없이 규칙은 다음과 같습니다. 매개 변수 이름 AZ로 정렬하고 빈 값에 직면하는 매개 변수는 서명에 참여하지 않습니다. * @return boolean */ public static boolean istenpaysign (문자열 문자 encoding, sortedMap <개체, 개체> 패키지 파람, 문자열 api_key) {StringBuffer sb = new StringBuffer (); set es = packageparams.entryset (); 반복자 it = es.iterator (); while (it.hasnext ()) {map.entry entry = (map.entry) it.next (); 문자열 k = (문자열) entry.getKey (); 문자열 v = (문자열) entry.getValue (); if (! "sign".equals (k) && null! = v &&! "". Equals (v)) {sb.append (k + "=" + v + "&"); }} sb.append ( "key =" + api_key); // 요약 문자열 mysign = md5util.md5encode (sb.toString (), char 문자열 tenPaysign = ((String) PackageParams.get ( "sign")). tolowercase (); //system.out.println(tenpaysign + "" + mysign); return tenpaysign.equals (mySign); } / ** * @Author * @Date 2016-4-22 * @Description : 서명 서명 * @Param 캐릭터 에코딩 * 인코딩 형식 * @param 매개 변수 * @param 매개 변수 * @return * / public static string createSign (string characterEncoding, string <object, object> packageparams, string api_key) {stringbuffer sb = new StringBuffer (); set es = packageparams.entryset (); 반복자 it = es.iterator (); while (it.hasnext ()) {map.entry entry = (map.entry) it.next (); 문자열 k = (문자열) entry.getKey (); 문자열 v = (문자열) entry.getValue (); if (null! = v &&! "". }} sb.append ( "key =" + api_key); 문자열 부호 = md5util.md5encode (sb.toString (), 특성 에코딩) .toupperCase (); 반환 사인; } / ** * @Author * @Date 2016-4-22 * @Description : 요청 매개 변수를 XML 형식으로 변환 * @Param 매개 변수 * @Param 매개 변수 * @return * / public static string getRequestXml (sortedMap <객체, 파라미터) {StringBuffer SB = new StringBuffer (); sb.append ( "<xml>"); set es = parameters.entryset (); 반복자 it = es.iterator (); while (it.hasnext ()) {map.entry entry = (map.entry) it.next (); 문자열 k = (문자열) entry.getKey (); 문자열 v = (문자열) entry.getValue (); if ( "att } else {sb.append ( "<" + k + ">" + v + "</" + k + ">"); }}} sb.append ( "</xml>"); 반환 sb.toString (); } /*** 지정된 길이 크기의 임의의 양수 정수를 꺼내십시오. * * * @param 길이 * int 검색된 랜덤 번호의 길이를 설정합니다. 길이는 11 * @return int 생성 된 랜덤 번호를 반환합니다. */ 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 <길이; i ++) {num = num * 10; } return (int) ((random * num)); } / ** * 현재 시간을 가져옵니다. simpledateformat outformat = new simpledateformat ( "yyyymmddhhmmss"); 문자열 s = outformat.format (지금); 반환 s; }}httputil 및 xmlutil은 다음과 같습니다.
공개 클래스 httputil {private static final logger = logs.get (); 개인 최종 정적 int connect_timeout = 5000; // 수백만 초의 개인 최종 정적 문자열 default_encoding = "utf-8"; public static string postData (String urlstr, String Data) {return postData (urlstr, data, null); } public static string postData (문자열 URLSTR, 문자열 데이터, 문자열 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 (데이터); Writer.flush (); Writer.close (); reader = new bufferedReader (new inputStreamReader (conn.getInputStream (), default_encoding); StringBuilder sb = new StringBuilder (); 문자열 라인 = null; while ((line = reader.readline ())! = null) {sb.append (line); sb.append ( "/r/n"); } return sb.toString (); } catch (ioexception e) {logger.error ( "" + urlstr + "에 연결되는 오류 :" + e.getMessage ()); } 마침내 {try {if (reader! = null) reader.close (); } catch (ioexception e) {}} return null; }} 공개 클래스 XMLUTIL { /*** XML을 구문 분석하고 첫 번째 레벨 요소 키 값 쌍을 반환합니다. 첫 번째 요소에 어린이가있는 경우이 노드의 값은 자식 노드의 XML 데이터입니다. * @param stxml * @return * @throws jdomexception * @throws ioexception */public static map doxmlparse (String strxml)는 jdomexception, ioexception {strxml = strxml.replacefirst ( "encoding =/". */"") if (null == stxml || "".equals (strxml)) {return null; } map m = new Hashmap (); inputStream in = new BytearRayInputStream (stxml.getBytes ( "UTF-8")); SaxBuilder Builder = New SaxBuilder (); 문서 doc = builder.build (in); 요소 root = doc.getRootElement (); 목록 List = root.getChildren (); iterator it = list.iterator (); while (it.hasnext ()) {element e = (요소) it.next (); 문자열 k = e.getName (); 문자열 v = ""; 어린이를 목록 = e.getchildren (); if (children.isempty ()) {v = e.getTextNormanize (); } else {v = xmlutil.getchildrentext (어린이); } m.put (k, v); } // 스트림을 닫아 in.close (); 반환 m; } / ** * 어린이 노드의 XML을 가져옵니다 * @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 = (요소) it.next (); 문자열 이름 = e.getName (); 문자열 값 = e.getTextNormanize (); 목록 목록 = e.getchildren (); sb.append ( "<" + name + ">"); if (! list.isempty ()) {sb.append (xmlutil.getchildrentext (list)); } sb.append (값); sb.append ( "</" + name + ">"); }} return sb.toString (); }} 물론 MD5 컴퓨팅 도구 클래스도 있습니다.
공개 클래스 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 (바이트 b) {int n = b; if (n <0) n += 256; int d1 = n / 16; int d2 = n % 16; 반환 육각형 [d1] + hexDigits [d2]; } public static string md5encode (문자열 원점, 문자열 charsetname) {String resulttring = null; try {resultstring = new String (Origin); MessageDigest MD = MessageDigest.getInstance ( "MD5"); if (charsetname == null || "".Equals (charSetName)) resultString = bytearRaytoHexString (md.Digest (resultString .getBytes ())); else resulttring = bytearraytoHexString (md.Digest (resultstring .getBytes (charsetname))); } catch (예외 예외) {} return resulttring; } private static final String hexDigits [] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "E", "F"}; } 2. 지불 콜백
결제가 완료되면 WeChat은 관련 결제 결과 및 사용자 정보를 위에서 지정한 콜백 주소로 보냅니다. 처리를 받고 응답을 반환해야합니다. 배경 알림과 상호 작용할 때 WeChat이 성공 또는 시간 초과없이 판매자의 답변을 받고 WeChat이 통지가 실패했다고 생각하면 WeChat은 특정 전략을 통해 정기적으로 알림의 성공률을 최대한 많이 증가시킬 것입니다. (알림 빈도는 15/15/30/1800/1800/1800/1800/1800/1800/3600, 단위 : 초)입니다.
결제 콜백 인터페이스와 관련하여 먼저 지불 결과 알림의 내용을 서명하고 확인한 다음 지불 결과에 따라 해당 처리 프로세스를 수행해야합니다.
public void weixin_notify (httpservletrequest request, httpservletresponse 응답) 예외 {// inputstream inputstream 매개 변수를 읽습니다. StringBuffer sb = new StringBuffer (); inputStream = request.getInputStream (); 문자열 s; bufferedReader in = new bufferedReader (new inputStreamReader (inputStream, "UTF-8")); while ((s = in.readline ())! = null) {sb.append (s); } in.close (); inputStream.close (); // XML을 맵 맵에 구문 분석 <String, String> M = new Hashmap <String, String> (); m = xmlutil.doxmlparse (sb.tostring ()); // 빈 설정 필터 treemap sortedMap <개체, 객체> packageparams = new treemap <객체, 개체> (); 반복자 IT = M.KEYSET (). iterator (); while (it.hasnext ()) {String parameter = (string) it.next (); 문자열 parameterValue = m.get (매개 변수); 문자열 v = ""; if (null! = parameterValue) {v = parametervalue.trim (); } packageparams.put (매개 변수, v); } // 계정 정보 문자열 key = payconfigutil.api_key; // key logger.info (PackageParams); // 서명이 올바른지 여부를 결정합니다 (payCommonUtil.istenPaysign ( "UTF-8", PackageParams, key)) { //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- //Processing business starts//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- (문자열) 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 ( "지불 성공"); // weChat에 알립니다. 비동기 확인이 성공적입니다. 그것을 써야합니다. 그렇지 않으면 배경에 항상 알림을받습니다. 8 번 후에는 거래가 실패한 것으로 간주됩니다. resxml = "<xml>" + "<return_code> <! } else {logger.info ( "지불 실패, 오류 메시지 :" + packageparams.get ( "err_code")); resxml = "<xml>" + "<return_code> <! } //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- logger.info ( "알림 서명 확인 실패"); }} 서명 검증 알고리즘은 시그니처 생성 알고리즘과 유사하며 위의 PayCommonUtil 도구 클래스에 제공됩니다.
3. 이후 이야기
WeChat 스캔의 지불 경험이 상당히 좋다고 생각합니다. 유일한 단점은 관련 문서가 흩어져 있다는 것입니다. 공식 데모는 Java로 작성되지 않았습니다. WeChat 공무원이 앞으로 점차적으로이를 개선 할 수 있기를 바랍니다!
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.