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 Scan Code Payment Mode 2" (การชำระเงินและการโทรกลับ) จะใช้เฉพาะ APP_ID, MCH_ID และ API_KEY และจะไม่มีการใช้อะไรอีก
ฉันจะไม่พูดถึงสภาพแวดล้อมการพัฒนา ไม่ว่าคุณจะเป็น SpringMVC, Struts2 หรือ Direct Serverlet ก็เกือบจะเหมือนกัน ตราบใดที่คุณสามารถมั่นใจได้ว่าวิธีการที่สอดคล้องกันสามารถเรียกได้ เกี่ยวกับการอ้างถึงแพ็คเกจ JAR ของบุคคลที่สามฉันใช้ JDOM ที่ทำงาน XML ที่นี่เท่านั้น โปรดจำไว้ว่ามันเป็นรุ่น 1.* ไม่ใช่รุ่นล่าสุด 2* บนเว็บไซต์ทางการและทั้งสองไม่เข้ากัน โดยเฉพาะอย่างยิ่งมันคือ jdom-1.1.3.jar แพ็คเกจการพึ่งพา Jaxen-1.1.6.jar สำหรับแพ็คเกจทั้งสองนี้ฉันไม่ได้ใช้ httpClient ที่ใช้ในบางตัวอย่าง รู้สึกไม่จำเป็นและแพ็คเกจการพึ่งพามีความซับซ้อนมาก แน่นอนคุณเป็นคนที่ไม่ได้พูด
2. การพัฒนาและการต่อสู้เชิงปฏิบัติ
1. ก่อนอื่นเชื่อมต่อกับอินเตอร์เฟส WeChat และรับรหัส QR การชำระเงิน WECHAT
สตริงสาธารณะ weixin_pay () พ่นข้อยกเว้น {// ข้อมูลบัญชีสตริง appid = payconfigutil.app_id; // appid // appsecret = payconfigutil.app_secret; // appsecret string mch_id = payconfigutil.mch_id; // คีย์สตริงหมายเลขธุรกิจ = payconfigutil.api_key; // คีย์สตริง currtime = paycommonutil.getCurrtime (); string strtime = currtime.substring (8, currtime.length ()); String strrandom = paycommonutil.buildrandom (4) + ""; สตริง nonce_str = strtime + strrandom; string order_price = 1; // ราคาหมายเหตุ: หน่วยของราคาแบ่งออกเป็นสตริงตัว = "goodsssssss"; // สตริงชื่อผลิตภัณฑ์ out_trade_no = "11338"; // หมายเลขคำสั่งซื้อ // รับสตริงคอมพิวเตอร์ IP ที่เริ่มต้น spbill_create_ip = payconfigutil.create_ip; // สตริงอินเตอร์เฟสการโทรกลับ notify_url = payconfigutil.notify_url; String Trade_type = "Native"; SortedMap <Object, Object> PackageParams = new Treemap <Object, Object> (); PackageParams.put ("appid", appid); PackageParams.put ("MCH_ID", MCH_ID); PackageParams.put ("nonce_str", nonce_str); packageparams.put ("ร่างกาย", ร่างกาย); 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 ("Sign", Sign); String requestxml = payCommonutil.getRequestxml (packageParams); System.out.println (requestxml); String resxml = httputil.postdata (payconfigutil.ufdoder_url, requestxml); แผนที่แผนที่ = xmlutil.doxmlparse (resxml); // สตริง return_code = (สตริง) map.get ("return_code"); // สตริง prepay_id = (สตริง) map.get ("prepay_id"); string urlCode = (string) map.get ("code_url"); ส่งคืน urlcode; -หากไม่มีอะไรเกิดขึ้นที่ไม่คาดคิดเราจะได้รับ URL การชำระเงินจาก WeChat Server ซึ่งดูเหมือนว่า weixin: // wxpay/bizpayurl? pr = pixxxxx หลังจากนั้นเราจำเป็นต้องสร้างรหัส QR สำหรับ URL นี้และจากนั้นเราสามารถใช้ WeChat โทรศัพท์มือถือของเราเพื่อสแกนรหัสเพื่อชำระเงิน มีหลายวิธีในการสร้างรหัส QR โปรดทำตามความต้องการของคุณเอง ฉันให้อินเทอร์เฟซการสร้างรหัส Google QR ที่นี่:
สตริงคงที่สาธารณะ QRFromGoOGLE (String CHL) พ่นข้อยกเว้น {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; กลับ qrfromgoogle; - // การประมวลผลอักขระพิเศษ public String urlencode (String Src) พ่น unsupportencodingexception {return urlencoder.encode (src, "UTF-8") แทนที่ ("+", "%20"); - รหัสข้างต้นเกี่ยวข้องกับคลาสเครื่องมือหลายรายการ: Payconfigutil, PayCommonutil, Httputil และ Xmlutil ในหมู่พวกเขา Payconfigutil วางการกำหนดค่าและเส้นทางที่กล่าวถึงข้างต้น PayCommonutil เกี่ยวข้องกับวิธีการหลายวิธีในการรับเหตุการณ์ปัจจุบันสร้างสตริงแบบสุ่มได้รับลายเซ็นพารามิเตอร์และการประกบ XML รหัสมีดังนี้:
PayCommonutil ระดับสาธารณะ { /*** ไม่ว่าจะเป็นลายเซ็นที่ถูกต้องกฎคือ: เรียงลำดับตามชื่อพารามิเตอร์ AZ และพารามิเตอร์ที่พบค่าว่างไม่ได้เข้าร่วมในลายเซ็น * @return Boolean */ Public Static Boolean Istenpaysign (String characterencoding, SortedMap <Object, Object> PackageParams, String API_KEY) {StringBuffer SB = new StringBuffer (); ตั้งค่า es = packageparams.entryset (); ตัววนซ้ำมัน = es.iterator (); ในขณะที่ (it.hasnext ()) {map.entry entry = (map.entry) it.next (); String k = (string) entry.getKey (); String V = (String) entry.getValue (); if (! "sign" .equals (k) && null! = v &&! "". เท่ากับ (v)) {sb.append (k + "=" + v + "&"); }} sb.append ("key =" + api_key); // ไฟล์สรุปสตริง mysign = md5util.md5encode (sb.toString (), characterencoding) .toLowerCase (); String tenpaysign = ((String) PackageParams.get ("sign")). toLowerCase (); //system.out.println(TenPaySign + "" + MySign); กลับ tenpaysign.equals (MySign); } / ** * @author * @date 2016-4-22 * @description: Sign Signature * @param characterencoding * รูปแบบการเข้ารหัส * @param พารามิเตอร์ * พารามิเตอร์การร้องขอ * @return * / สตริงคงที่ public String) {stringbuff) ตั้งค่า es = packageparams.entryset (); ตัววนซ้ำมัน = es.iterator (); ในขณะที่ (it.hasnext ()) {map.entry entry = (map.entry) it.next (); String k = (string) entry.getKey (); String V = (String) entry.getValue (); if (null! = v &&! "". เท่ากับ (v) &&! "sign" .equals (k) &&! }} sb.append ("key =" + api_key); String sign = md5util.md5encode (sb.toString (), characterencoding) .touppercase (); สัญญาณส่งคืน; } / ** * @author * @date 2016-4-22 * @description: แปลงพารามิเตอร์คำขอเป็นสตริงรูปแบบ XML * @param พารามิเตอร์ * พารามิเตอร์คำขอ * @return * / สตริงคงที่สาธารณะ getRequestxml sb.append ("<xml>"); ตั้งค่า es = parameters.entryset (); ตัววนซ้ำมัน = es.iterator (); ในขณะที่ (it.hasnext ()) {map.entry entry = (map.entry) it.next (); String k = (string) entry.getKey (); String V = (String) entry.getValue (); if ("แนบ" .equalsignorecase (k) || "body" .equalsignorecase (k) || "sign" .equalsignorecase (k)) {sb.append ("<" + k + ">" + "<! [cdata [" + v + "]> </" + k + " } else {sb.append ("<" + k + ">" + v + "</" + k + ">"); }}} sb.append ("</xml>"); ส่งคืน sb.toString (); } /*** นำจำนวนเต็มบวกแบบสุ่มของขนาดความยาวที่ระบุ * * ความยาว @param * int ตั้งค่าความยาวของหมายเลขสุ่มที่ดึงมา ความยาวน้อยกว่า 11 * @return int ส่งคืนหมายเลขสุ่มที่สร้างขึ้น */ public Static int buildRandom (ความยาว int) {int num = 1; สุ่มสองครั้ง = math.random (); ถ้า (สุ่ม <0.1) {สุ่ม = สุ่ม + 0.1; } สำหรับ (int i = 0; i <length; i ++) {num = num * 10; } return (int) ((สุ่ม * num)); } / ** * รับเวลาปัจจุบัน yyyymmddhhmmss * * @return string * / สตริงคงที่สาธารณะ getCurrTime () {วันที่ = วันที่ใหม่ (); SimpledateFormat outformat = new SimpledateFormat ("yyyymmddhhmmss"); สตริง s = outformat.format (ตอนนี้); กลับ s; -httputil และ xmlutil มีดังนี้:
คลาสสาธารณะ httputil {logger logger สุดท้ายคงที่ = logs.get (); INT int int int connect_timeout ส่วนตัว = 5000; // ในหลายล้านวินาทีสตริงคงสุดท้ายส่วนตัว default_encoding = "UTF-8"; สตริงคงที่สาธารณะ postdata (string urlstr, ข้อมูลสตริง) {return postdata (urlstr, data, null); } สตริงคงที่สาธารณะ postdata (สตริง urlstr, ข้อมูลสตริง, สตริง contentType) {bufferedReader reader = null; ลอง {url url = url ใหม่ (urlstr); urlConnection conn = url.openconnection (); conn.setDooutput (จริง); conn.setConnectTimeout (connect_timeout); conn.setReadtimeout (connect_timeout); if (contentType! = null) conn.setRequestProperty ("ประเภทเนื้อหา", contentType); OutputStreamWriter Writer = new OutputStreamWriter (conn.getOutputStream (), default_encoding); if (data == null) data = ""; Writer.write (ข้อมูล); Writer.flush (); Writer.close (); reader = ใหม่ bufferedReader (ใหม่ inputStreamReader (conn.getInputStream (), default_encoding)); StringBuilder sb = new StringBuilder (); สตริงบรรทัด = null; ในขณะที่ ((line = reader.readline ())! = null) {sb.append (บรรทัด); sb.append ("/r/n"); } return sb.toString (); } catch (ioexception e) {logger.error ("ข้อผิดพลาดการเชื่อมต่อกับ" + urlstr + ":" + e.getMessage ()); } ในที่สุด {ลอง {ถ้า (reader! = null) reader.close (); } catch (ioexception e) {}} return null; - คลาสสาธารณะ XMLUTIL { /*** แยกวิเคราะห์ XML และส่งคืนคู่คีย์ค่าองค์ประกอบระดับแรก หากองค์ประกอบระดับแรกมีลูกค่าของโหนดนี้คือข้อมูล XML ของโหนดเด็ก * @param strxml * @return * @throws jdomexception * @throws ioexception */แผนที่สาธารณะคงที่ doxmlparse (String strxml) พ่น jdomexception, ioexception {strxml = strxml.replacefirst ("encoding =". */" if (null == strxml || "" .equals (strxml)) {return null; } แผนที่ m = ใหม่ hashmap (); inputStream ใน = new ByteArrayInputStream (strxml.getBytes ("UTF-8")); SaxBuilder Builder = New SaxBuilder (); เอกสารเอกสาร = builder.build (in); Element root = doc.getRootelement (); รายการรายการ = root.getChildren (); iterator it = list.iterator (); ในขณะที่ (it.hasnext ()) {องค์ประกอบ e = (องค์ประกอบ) it.next (); สตริง k = e.getName (); สตริง v = ""; รายการเด็ก = e.getChildren (); if (children.isempty ()) {v = e.getTextNormalize (); } else {v = xmlutil.getChildRentext (เด็ก); } m.put (k, v); } // ปิดสตรีม in.close (); กลับ M; } / ** * รับ XML ของโหนดลูก * @param children * @return String * / สตริงคงที่สาธารณะ getChildRentext (รายการเด็ก) {StringBuffer sb = new StringBuffer (); if (! เด็ก. isempty ()) {iterator it = children.iterator (); ในขณะที่ (it.hasnext ()) {องค์ประกอบ e = (องค์ประกอบ) it.next (); ชื่อสตริง = e.getName (); ค่าสตริง = e.getTextNormalize (); รายการรายการ = e.getChildren (); sb.append ("<" + ชื่อ + ">"); if (! list.isempty ()) {sb.append (xmlutil.getChildRentext (รายการ)); } sb.append (ค่า); sb.append ("</" + ชื่อ + ">"); }} ส่งคืน sb.toString (); - แน่นอนว่ายังมีคลาสเครื่องมือคอมพิวเตอร์ MD5
คลาสสาธารณะ MD5UTIL {สตริงคงที่ส่วนตัว ByTeArrayToHexString (ไบต์ B []) {StringBuffer ResultsB = New StringBuffer (); สำหรับ (int i = 0; i <b.length; i ++) ผลลัพธ์ b.append (bytetohexstring (b [i])); ส่งคืนผลลัพธ์ b.toString (); } สตริงคงที่ส่วนตัว bytetohexstring (byte b) {int n = b; ถ้า (n <0) n += 256; int d1 = n / 16; int d2 = n % 16; กลับ hexdigits [d1] + hexdigits [D2]; } สตริงคงที่สาธารณะ MD5ENCODE (ต้นกำเนิดสตริง, charsetName สตริง) {String ResultsTring = NULL; ลอง {resultString = สตริงใหม่ (ต้นกำเนิด); MESAGEDIGEST MD = MESEGATEGEST.GETINSTANCE ("MD5"); if (charsetName == null || "" .equals (charsetName)) ResultString = byteArraytoHexstring (md.Digest (ResultString .getBytes ())); else resultsTring = byteArraytoHexstring (md.digest (ResultString .getBytes (charsetName))); } catch (ข้อยกเว้นข้อยกเว้น) {} ส่งคืน ResultString; } สตริงสุดท้ายคงที่ส่วนตัว hexdigits [] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; - 2. การชำระเงินการโทรกลับ
หลังจากการชำระเงินเสร็จสิ้น WeChat จะส่งผลลัพธ์การชำระเงินที่เกี่ยวข้องและข้อมูลผู้ใช้ไปยังที่อยู่การโทรกลับที่เราระบุไว้ข้างต้น เราจำเป็นต้องได้รับการประมวลผลและส่งคืนการตอบกลับ เมื่อมีปฏิสัมพันธ์กับการแจ้งเตือนพื้นหลังหาก WeChat ได้รับคำตอบของผู้ค้าโดยไม่ประสบความสำเร็จหรือหมดเวลาและ WeChat เชื่อว่าการแจ้งเตือนล้มเหลว WeChat จะเริ่มต้นใหม่เป็นประจำอีกครั้งผ่านกลยุทธ์บางอย่างเพื่อเพิ่มอัตราความสำเร็จของการแจ้งเตือนมากที่สุด แต่ WeChat ไม่รับประกันว่าการแจ้งเตือนจะประสบความสำเร็จ (ความถี่การแจ้งเตือนคือ 15/15/30/1800/1800/1800/1800/1800/1800/3600 หน่วย: วินาที)
เกี่ยวกับอินเทอร์เฟซการโทรกลับการชำระเงินเราจะต้องลงชื่อและตรวจสอบเนื้อหาของการแจ้งเตือนผลการชำระเงินก่อนจากนั้นดำเนินการกระบวนการประมวลผลที่สอดคล้องกันตามผลการชำระเงิน
โมฆะสาธารณะ Weixin_Notify (คำขอ httpservletRequest, การตอบสนอง httpservletResponse) พ่นข้อยกเว้น {// อ่านพารามิเตอร์อินพุตอินพุตสตรีม; StringBuffer sb = new StringBuffer (); inputStream = request.getInputStream (); สตริง s; bufferedReader ใน = new bufferedReader (ใหม่ inputStreamReader (inputStream, "UTF-8")); ในขณะที่ ((s = in.readline ())! = null) {sb.append (s); } in.close (); inputstream.close (); // แยกวิเคราะห์ XML ลงในแผนที่ <สตริง, สตริง> m = hashmap ใหม่ <สตริง, สตริง> (); m = xmlutil.doxmlparse (sb.toString ()); // ตัวกรองการตั้งค่าที่ว่างเปล่า treemap sortedMap <object, object> packageParams = new treemap <object, Object> (); iterator it = m.keyset (). iterator (); ในขณะที่ (it.hasnext ()) {พารามิเตอร์สตริง = (สตริง) it.next (); String parameterValue = M.Get (พารามิเตอร์); สตริง v = ""; if (null! = parameterValue) {v = parameterValue.trim (); } packageParams.put (พารามิเตอร์, v); } // คีย์สตริงข้อมูลบัญชี = payconfigutil.api_key; // key logger.info (packageparams); // พิจารณาว่าลายเซ็นนั้นถูกต้องหรือไม่ถ้า (PayCommonutil.istenPaysign ("UTF-8", packageParams, key)) { // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ เริ่มต้น // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - (สตริง) 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); /////////// 执行自己的业务逻辑///////////////////////////////////////////////////////////////////////////////////////////////////////-0ง- "" การชำระเงิน ") // แจ้ง WeChat การยืนยันแบบอะซิงโครนัสประสบความสำเร็จ ต้องเขียนมัน มิฉะนั้นพื้นหลังจะได้รับแจ้งตลอดเวลา หลังจากแปดครั้งจะได้รับการพิจารณาว่าการทำธุรกรรมล้มเหลว resxml = "<xml>" + "<return_code> <! [cdata [ความสำเร็จ]]> </return_code>" + "<return_msg> <! [cdata [ตกลง]]> </return_msg>" + " } else {logger.info ("การชำระเงินล้มเหลวข้อความแสดงข้อผิดพลาด:" + packageParams.get ("err_code")); resxml = "<xml>" + "<return_code> <! [cdata [ล้มเหลว]]> </return_code>" + "<return_msg> <! - logger.info ("การตรวจสอบลายเซ็นการแจ้งเตือนล้มเหลว"); - อัลกอริทึมการตรวจสอบลายเซ็นนั้นคล้ายกับอัลกอริทึมการสร้างลายเซ็นและมีให้ในคลาสเครื่องมือ PayCommonutil ด้านบน
3. เรื่องต่อมา
ฉันรู้สึกว่าประสบการณ์การชำระเงินของการสแกน WeChat นั้นค่อนข้างดี ข้อเสียเพียงอย่างเดียวคือเอกสารที่เกี่ยวข้องกระจัดกระจาย การสาธิตอย่างเป็นทางการไม่ได้เขียนไว้ใน Java ฉันหวังว่าเจ้าหน้าที่ WeChat อย่างเป็นทางการจะค่อยๆปรับปรุงในอนาคต!
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น