微信Native支付對接(掃碼)
由於有業務需求對接了微信和paypal支付,這邊做個記錄
微信支付開發文檔:https://pay.weixin.qq.com/wiki/doc/api/index.html
一、支付方式
這邊有個坑,微信h5支付和Native支付都是微信外部使用的支付方式,但是h5支付適用於移動端,因為支付時是從外部喚醒本地移動端的微信app進行支付;而Native支付則是在pc端生成訂單后,用戶使用移動端的微信app掃碼完成支付
- 付款碼支付:需要用戶有掃碼槍.如:肯德基,麥當勞的支付
- JSAPI支付: 主要服務於微信內部調用的支付接口
- Native支付:掃碼支付
- h5支付:手機瀏覽器調用的支付
二、支付流程介紹
用戶:客戶
前端:ui界面,客戶端
后端:java寫的服務端
微信系統:包括生產訂單的接口等
流程:
- 用戶點擊前端的支付按鈕
- 前端將對應的訂單信息發送給后端
- 后端預處理訂單的信息(如在數據庫中生成對應的記錄),然后調用微信支付系統生成訂單的接口
- 微信支付系統返回對應訂單的支付地址給后端
- 后端將該訂單地址返還給前端,前端調用QRCode工具生成二維碼
- 用戶打開手機微信app掃碼完成支付(由於和我們業務並沒有直接的相關性,這個過程沒有直接在上圖中表現出來)
- 微信系統異步(有延時)收到微信支付成功的回調,回調函數中應該包括對於驗證訂單的有效性(如果被人攻破就麻煩了),存儲數據庫(確認訂單已結算),回復微信系統(否則微信會在一天之內多次發送回調信息)等步驟
- 后端可以通知前端訂單完成,以便后續的操作(具體的做法可以是從訂單生成之后,前端可以以一定的時間間隔詢問訂單的狀態)
注:其它支付手段的流程應該也是大致相同的
三、准備工作
首先必然是下載sdk(software development kit).值的注意的一點是,對比於網上的許多舊的微信博客,可以發現微信的api有了極大的改進,我們只要調用少許的接口即可完成開發.同時api的命名目前也是比較統一的
-
申請一個商機號
注:商家號的申請是需要相關的營業執照的
-
上微信商戶號中開通Native支付的服務
-
准備調用訂單支付接口相關的參數(這里只展示一下必填項)
字段名 變量名 描述 公眾賬號ID appid 微信支付分配的公眾賬號ID 商戶號 mch_id 微信支付分配的商戶號 隨機字符串 nonce_str 由內置的隨機數生成算法生成 簽名 sign 由內置的簽名算法生成 商品描述 body 商品的簡單描述 商品訂單號 out_trade_no 可以直接用訂單id 標價金額 total_fee 單位為分(CNY) 終端IP spbill_creat 服務器IP 通知地址 notify_url 異步回調的地址(回調不能在本地測試) 交易類型 trade_type NATIVE -Native支付 商品ID product_id 商品id,Native必填 密匙 key 商家自行設置的密匙 - 在微信商戶號后台獲得appid,mch_id,設置key
- out_trade_no必須大於10,不能重復
- total_fee不能有小數點(正常情況也不應該有小數點)
- nonce_str變量用於提高生成的sign不確定
- sign的算法是先將所有的參數進行排序,然后按key=value的形式拼接為字符串,最后加上key.最后,對拼接后字符串進行加密,加密方式一種是用MD5,一種使用HMAC-SHA256.具體的過程可以查看sdk原碼,實際使用只要傳入參數map給相關的api即可,但是了解一下有助於調試,另外附加一個微信官方的簽名校驗工具
- key是保證整個支付過程加密以及回調地址不被攻破的關鍵!
- 可以用額外的attach附加參數,如訂單id
-
sdk下載地址(maven的相關地址大家可以自己去找找):https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
四、開發工作
這里主要說明一下后端接口的調用
-
調用接口生成訂單
public String getWxQrCode(HashMap<String,Object> paraMap) throws Exception { /*數據庫操作*/ Integer maxId = userMapper.getMaxUserId(); Integer id = maxId==null?1:maxId+1; paraMap.put("user_id",id); userMapper.addUser(paraMap); /*獲得code_url*/ Map<String, String> map = new HashMap<>(); map.put("appid",wxPayConfigBean.getAppID()); map.put("mch_id",wxPayConfigBean.getMchID()); map.put("body",wxPayConfigBean.getBody()); map.put("out_trade_no",id.toString()); map.put("total_fee",paraMap.get("order_amount").toString()); map.put("spbill_create_ip",paraMap.get("ip").toString()); map.put("notify_url",wxPayConfigBean.getNotifyUrl()); map.put("trade_type",wxPayConfigBean.getTradeType()); map.put("attach",id.toString()); //Native必傳 map.put("product_id",paraMap.get("order_productid").toString()); //獲得sign WXPay wxPay = new WXPay(wxPayConfigBean); Map<String, String> backMap = wxPay.unifiedOrder(map); HashMap<String, Object> resMap = new HashMap<>(); resMap.put("url",backMap.get("code_url")); resMap.put("id",id); return JsonUtil.toJsonString(resMap); }
-
回調
public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { //拿到微信回調信息(以字節流的方式) InputStream inputStream = request.getInputStream(); BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8")); //String是字符串變量,StringBuffer是字符串變量 StringBuffer sb = new StringBuffer(); //將字節流轉換為字符串 String line; while ((line = in.readLine()) != null){ //每次用line讀取一行的字節流,如果這一行不為空就擴展到sb上 sb.append(line); } System.out.println("*****************************sb**************************"); System.out.println(sb); System.out.println("*****************************sb**************************"); //關閉 in.close(); inputStream.close(); String strXml = sb.toString(); Map<String, String> toMap = WXPayUtil.xmlToMap(strXml); System.out.println(toMap); //獲取業務信息 String outTradeNo = toMap.get("out_trade_no"); String totalFee = toMap.get("total_fee"); String appId = toMap.get("appId"); String mchId = toMap.get("mch_id"); String resultCode = toMap.get("result_code"); String attach = toMap.get("attach"); //附加數據 //該對象用於通知微信 PrintWriter writer = response.getWriter(); //驗簽 String res = null; Map<String,String> paraMap = WXPayUtil.xmlToMap(strXml); boolean signatureValid = WXPayUtil.isSignatureValid(paraMap, wxPayConfigBean.getKey(), WXPayConstants.SignType.HMACSHA256); //注:注意返回時有nonce_str if (signatureValid){ System.out.println("驗證成功~"); if ("SUCCESS".equals(toMap.get("result_code"))){ System.out.println("返回的是SUCCESS"); /*自己的業務邏輯*/ } //返回值——仍然有問題,不斷刷回復 String noticeStr = setXML("SUCCESS",""); writer.write(noticeStr); writer.flush(); //情況緩存區,並完成文件寫入操作 } }else{ System.out.println("驗證失敗"); String noticeStr = setXML("FATL",""); writer.write(noticeStr); writer.flush(); }; } private static String setXML(String return_code, String return_msg) { return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>"; }
注:
- 有時候回調好像沒有正確地返還個微信支付系統,對java的輸入輸出不太了解,麻煩有大佬知道問題的請留言
- 在最開始嘗試對接微信支付時查閱了較多的博客,代碼,在這里表示感謝.但因為實在太過龐雜,就不一一列出了.