最近負責的一些項目開發,都用到了微信支付(微信公衆號支付、微信H5支付、微信掃碼支付、APP微信支付)。在開發的過程當中,在調試支付的過程當中,或多或少都遇到了一些問題,今天總結下,分享,留存。javascript
先說注意的第一點,全部支付的第一步都是請求統一下單,統一下單,統一下單,請求URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder。統一下單的目的是拿到預支付交易會話標識prepay_id,這個是必須的。全部的支付調用都是經過prepay_id來識別。php
再說一個微信官方提供的一個很重要的工具,微信支付接口簽名校驗工具(網址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1),此工具旨在幫助開發者檢測調用【微信支付接口API】時發送的請求參數中生成的簽名是否正確,提交相關信息後可得到簽名校驗結果。特別實用!特別實用!特別實用!簽名只要正確了,一切就OK了!html
還有就是官方提供的幾種支付方式的對比說明,以下圖所示。java
第一部分 微信公衆號支付web
微信公衆號支付須要配置的參數有:APPID(微信公衆號開發者ID)、APPSECRET(微信公衆號開發者密碼)、MCHID(商戶ID)、KEY(商戶密鑰)。ajax
微信公衆號支付應用的場景是在微信內部的H5環境中是用的支付方式。由於要經過網頁受權獲取用戶的OpenId,因此必需要配置網頁受權域名。同時要配置JS接口安全域名,以下圖所示:算法
以PHP爲例,使用官方demo一個最多見的問題就是500錯誤,回調沒反應,這個通常狀況下是xml數據解析出現的問題(錯誤在這裏WxPay.Data.php中WxPayDataBase類的FromXml()方法),解決方案以下:json
public function FromXml($xml) { if(!$xml) { throw new WxPayException("xml數據異常!"); } //將XML轉爲array //禁止引用外部xml實體 libxml_disable_entity_loader(true); //這句致使出現上述問題 $this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $this->values; }
問題就出如今libxml_disable_entity_loader(),它的做用是設置是否禁止從外部加載XML實體,設爲true就是禁止。可使用將代碼改爲如下內容進行解決:小程序
public function FromXml($xml) { if(!$xml) { throw new WxPayException("xml數據異常!"); } //將XML轉爲array //禁止引用外部xml實體 $disableLibxmlEntityLoader = libxml_disable_entity_loader(true); //改成這句 $this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); libxml_disable_entity_loader($disableLibxmlEntityLoader); //添加這句 return $this->values; }
本人也嘗試過這樣一個簡單的方案,以下,直接屏蔽(在低版本PHP5.2中測試經過)libxml_disable_entity_loader():微信小程序
public function FromXml($xml) { if(!$xml) { throw new WxPayException("xml數據異常!"); } //將XML轉爲array //禁止引用外部xml實體 //libxml_disable_entity_loader(true); //或者是把這句屏蔽 $this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $this->values; }
把這個解決以後就OK了。
還有一個問題就是「curl出錯,錯誤碼:60」,這個錯誤是因爲服務器和PHP版本致使的,最近一次出現是在一個PHP5.2版本的原生項目上。微信官方對支付的數據傳輸提出了三點建議:
◆ 使用HTTPS確保網絡傳輸安全性。
◆ 禁用SSL等不安全協議和算法,建議使用TLS1.2。
◆ 不要輕易的嘗試設計和實現本身的加密傳輸算法,幾乎都會存在問題。
具體的錯誤信息在日誌裏面是這樣的:Fatal error: Uncaught exception ‘WxPayException‘ with message ‘curl出錯,錯誤碼:60‘ in ,目前的解決方案以下:
最原始的3.0demo裏面,找到WxPay.JsApiPay.php文件的99行:
curl_setopt($ch, CURLOP_TIMEOUT, 30);
最先的example代碼裏少了一個「T」,這個問題官方已經解決,正確代碼應該是以下的:
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
還有就是安全校驗的問題,在官方demo WxPay.Api.php 文件中找到以下代碼:
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//嚴格校驗
將上述代碼作出以下修改:
if(stripos($url,"https://")!==FALSE){ curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); } else { curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//嚴格校驗 }
這樣的話,curl出錯,錯誤碼:60這個問題就能夠解決了。
第二部分 微信H5支付
微信H5支付是微信官方2017年上半年剛剛對外開放的支付模式,它主要應用於在手機網站在移動瀏覽器(非微信環境)調用微信支付的場景。底層的技術以及支付連接本質上是財付通。
注意:微信H5支付須要在微信支付商戶平臺單獨申請開通,不然沒法使用。
微信H5支付的流程比較簡單,就是拼接請求的xml數據,進行統一下單,獲取到支付的mweb_url,而後請求這個url網址就行。請求使用curl函數,使用的時候須要注意設置header參數。
$headers = array(); $headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; $headers[] = 'Connection: Keep-Alive'; $headers[] = 'Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3'; $headers[] = 'Accept-Encoding: gzip, deflate'; $headers[] = 'User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20100101 Firefox/22.0';
下面直接奉上整個流程源碼。
$userip = $_SERVER["REMOTE_ADDR"]; //得到用戶設備IP $appid = "wx24d9dbdf00000";//微信 $mch_id = "888888888";//微信官方的 $key = "FSDFSD2356DSD00";//本身設置的微信商家key $nonce_str=MD5($out_trade_no);//隨機字符串 $total_fee = $total_fee*100; //金額 $spbill_create_ip = $userip; //IP $notify_url = "http://www.bojuwang.net/"; //回調地址 $trade_type = 'MWEB';//交易類型 具體看API 裏面有詳細介紹 $scene_info ='{"h5_info":{"type":"Wap","wap_url":"http://www.hnyjzpw.com","wap_name":"支付"}}';//場景信息 必要參數 $signA ="appid=$appid&body=$body&mch_id=$mch_id&nonce_str=$nonce_str¬ify_url=$notify_url&out_trade_no=$out_trade_no&scene_info=$scene_info&spbill_create_ip=$spbill_create_ip&total_fee=$total_fee&trade_type=$trade_type"; $strSignTmp = $signA."&key=$key"; //拼接字符串 注意順序微信有個測試網址 順序按照他的來 直接點下面的校訂測試 包括下面XML 是否正確 $sign = strtoupper(MD5($strSignTmp)); // MD5 後轉換成大寫 $post_data="<xml><appid>$appid</appid><body>$body</body><mch_id>$mch_id</mch_id><nonce_str>$nonce_str</nonce_str><notify_url>$notify_url</notify_url><out_trade_no>$out_trade_no</out_trade_no><scene_info>$scene_info</scene_info><spbill_create_ip>$spbill_create_ip</spbill_create_ip><total_fee>$total_fee</total_fee><trade_type>$trade_type</trade_type><sign>$sign</sign> </xml>";//拼接成XML 格式 $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信傳參地址 $dataxml = http_post($url,$post_data,$headers); $objectxml = (array)simplexml_load_string($dataxml,'SimpleXMLElement',LIBXML_NOCDATA); //將微信返回的XML 轉換成數組 function http_post($url='',$post_data=array(),$header=array(),$timeout=30) { $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳過證書檢查 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 從證書中檢查SSL加密算法是否存在 curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); $response = curl_exec($ch); curl_close($ch); return $response; } if($objectxml['return_code'] == 'SUCCESS'){ $mweb_url= $objectxml['mweb_url']; // header("Location:$mweb_url"); } $redirect_url = urlencode("http://www.bojuwang.net/");
H5支付的回調代碼以下,注意xml數據的接收。這是一個很大的坑,PHP須要使用 $GLOBALS['HTTP_RAW_POST_DATA']解析微信支付結果返回的xml。
$xml = $GLOBALS['HTTP_RAW_POST_DATA']; $dataxml = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);//轉成數組, if($dataxml['return_code'] == 'SUCCESS'){ //success }
第三部分 微信掃碼支付
微信掃碼支付通常應用的場景是PC端電腦支付。微信掃碼支付可分爲兩種模式,根據支付場景選擇相應模式。通常狀況下的PC端掃碼支付選擇的是模式二,須要注意的是模式二無回調函數。
【模式一】商戶後臺系統根據微信支付規則連接生成二維碼,連接中帶固定參數productid(可定義爲產品標識或訂單號)。用戶掃碼後,微信支付系統將productid和用戶惟一標識(openid)回調商戶後臺系統(須要設置支付回調URL),商戶後臺系統根據productid生成支付交易,最後微信支付系統發起用戶支付流程。
【模式二】商戶後臺系統調用微信支付【統一下單API】生成預付交易,將接口返回的連接生成二維碼,用戶掃碼後輸入密碼完成支付交易。注意:該模式的預付單有效期爲2小時,過時後沒法支付。
微信掃碼支付最友好的解決方案就是支付完成以後經過JS設置監聽函數,經過該函數完成跳轉。可參考的代碼以下:
<script type="text/javascript"> $(function () { sendPost(); //調用監聽事件 }); //監聽訂單支付狀態 function sendPost() { //發送AJAX請求 $.ajax({ url: "listen.aspx", type: "POST", timeout: 30000, data: { "order_no": "<%=order_no %>" }, dataType: "json", success: function (data, type) { if (data.status == 1) { $("#tipshow").show(); setTimeout(function () { location.href = data.url; //支付成功後跳轉 }, 1000); } else { time(); } } }); } function time() { setTimeout(function () { sendPost(); }, 5000); } </script>
第四部分 微信小程序支付
微信小程序支付是在小程序環境中使用的微信支付方式。
相對於上述幾個支付方式,微信小程序支付則顯得更簡單一些,不涉及到異步通知。在微信小程序中經過官方提供的API wx.requestPayment(OBJECT)發起微信支付,示例代碼以下:
wx.requestPayment({ 'timeStamp': '', 'nonceStr': '', 'package': '', 'signType': 'MD5', 'paySign': '', 'success':function(res){ }, 'fail':function(res){ } })
經過上面咱們能夠看到,小程序支付的須要timeStamp、nonceStr、package、signType、paySign這五個參數。而後經過回調函數success或者是fail處理業務邏輯。
後臺處理程序的第一步仍是統一下單,經過統一下單拿到prepay_id,而後獲取相關參數,經過接口(Ajax)傳到小程序端發起微信支付。由於統一下單須要用到用戶的OpenId,因此在發起統一下單以前要經過小程序的API wx.login(OBJECT)調用接口獲取登陸憑證(code)進而換取用戶登陸態信息,包括用戶的惟一標識(openid) 及本次登陸的 會話密鑰(session_key)等。用戶數據的加解密通信須要依賴會話密鑰完成。主要是爲了拿到OpenId!
涉及到是一個.NET項目,大體的.NET後臺代碼以下:
1 /// <summary> 2 /// 獲取支付的參數 3 /// </summary> 4 private void get_pay_params(HttpContext context) 5 { 6 string openId = BJRequest.GetFormString("openid"); 7 int user_id = BJRequest.GetFormIntValue("user_id", 0); 8 9 string appId = MiniPayConfig.APPID; 10 string timeStamp = MiniPay.GenerateTimeStamp(); 11 string nonceStr = MiniPay.GenerateNonceStr(); 12 string signType = "MD5"; 13 string outTradeNo=MiniPay.GenerateOutTradeNo(); 14 15 //獲取統一下單結果,主要是爲了拿到prepay_id 16 MiniPay mnpay=new MiniPay(); 17 MiniPayData unifiedOrderResult = mnpay.GetUnifiedOrderResult(openId,outTradeNo); 18 19 20 //小程序支付須要的參數 21 MiniPayData data = new MiniPayData(); 22 data.SetValue("appId", appId); 23 data.SetValue("timeStamp", timeStamp); 24 data.SetValue("nonceStr", nonceStr); 25 data.SetValue("package", "prepay_id=" + unifiedOrderResult.GetValue("prepay_id")); 26 data.SetValue("signType", "MD5"); 27 data.SetValue("paySign", data.MakeSign()); 28 29 30 StringBuilder strTxt = new StringBuilder(); 31 32 strTxt.Append("{"); 33 strTxt.Append("\"timeStamp\":\"" + timeStamp + "\""); 34 strTxt.Append(",\"nonceStr\":\"" + nonceStr + "\""); 35 strTxt.Append(",\"package\":\"" + data.GetValue("package") + "\""); 36 strTxt.Append(",\"signType\":\"" + signType + "\""); 37 strTxt.Append(",\"paySign\":\"" + data.GetValue("paySign") + "\""); 38 strTxt.Append("}"); 39 40 context.Response.Write(strTxt.ToString()); 41 }
微信小程序端拿到參數以後,發起微信支付請求,小程序端代碼以下:
1 //調起微信支付 2 wxpay: function(){ 3 var that=this; 4 wx.request({ 5 url: 'https://888.com/api/order.ashx?action=get_pay_params', 6 method: 'post', 7 data: { 8 openid: that.data.openId, 9 user_id: that.data.userId 10 }, 11 header: { 12 'Content-Type': 'application/x-www-form-urlencoded' 13 }, 14 success: function(res){ 15 //if(res.data.status==1){ 16 var order=res.data; 17 wx.requestPayment({ 18 timeStamp: order.timeStamp, 19 nonceStr: order.nonceStr, 20 package: order.package, 21 signType: 'MD5', 22 paySign: order.paySign, 23 success: function(res){ 24 25 //支付成功,處理相應的訂單 26 wx.request({ 27 url: 'https://8888.com/api/order.ashx?action=order_edit_pay', 28 method: 'post', 29 data: { 30 order_id: that.data.returnOrderId 31 }, 32 header: { 33 'Content-Type': 'application/x-www-form-urlencoded' 34 }, 35 success: function (res) { 36 var data = res.data; 37 if (data.status == 1) { 38 console.log("支付成功,處理訂單:" + that.data.returnOrderId); 39 wx.showToast({ 40 title: "訂單支付並處理成功!", 41 duration: 1000, 42 }); 43 setTimeout(function () { 44 wx.navigateTo({ 45 url: '../user/dingdan?currentTab=2&otype=deliver', 46 }); 47 }, 1500); 48 } else { 49 wx.showToast({ 50 title: "訂單處理失敗!", 51 duration: 2500 52 }); 53 } 54 }, 55 fail: function (e) { 56 wx.showToast({ 57 title: '處理訂單網絡異常!', 58 duration: 2000 59 }); 60 } 61 }); 62 }, 63 fail: function(res) { 64 wx.showToast({ 65 title:'支付失敗', 66 duration:1000 67 }); 68 wx.navigateTo({ 69 url: '../user/dingdan?currentTab=0&otype=all', 70 }); 71 } 72 }) 73 }, 74 fail: function() { 75 // fail 76 wx.showToast({ 77 title: '支付時網絡異常!', 78 duration: 2000 79 }); 80 } 81 }) 82 }
第五部分 微信APP支付
微信APP支付是在APP應用中使用的微信支付方式。
最後,總結一下上述幾種支付方式須要注意的點。
1. 全部的支付參數都須要到微信支付商戶平臺(pay.weixin.qq.com)配置參數。
2. 微信公衆號支付、微信掃碼支付須要在微信公衆號裏面申請開通;APP支付須要在微信開放平臺申請開通(open.weixin.qq.com);小程序支付須要在小程序平臺申請開通。
3. 僅有公衆號支付和掃碼支付需配置支付域名,APP支付、刷卡支付無需配置域名。下圖就是在微信支付商戶平臺配置受權域名的界面。
4. 全部使用JS API方式發起支付請求的連接地址,都必須在當前頁面所配置的支付受權目錄之下。下單前須要調用【網頁受權獲取用戶信息】接口獲取到用戶的Openid。
5. 當公衆平臺接到掃碼支付請求時,會回調當前頁面所配置的支付回調連接傳遞訂單信息。