C#實現微信支付
今天,我們來一起探討一下這個微信掃碼支付。何為掃碼支付呢?這裡面,掃的碼就是二維碼了,就是我們經常掃一掃的那種二維碼圖片,例如,我們自己新增好友的時候,可以通過輸入對方的微訊號,也可以掃一掃對方的二維碼。掃碼支付,作為,微信支付裡面,不可或缺的一個功能,對商品的支付提供了極為方便的體驗,用途也非常的多。
例如我們在地鐵、公交站常見的那些自動售貨機(不錯,就是那種投硬幣,就可以自動出貨的那種機器)中都用到。微信(支付寶)的掃碼支付的出現,大大的減少了這方面的風險,近些年來,二維碼的應用越來越廣,甚至有些地方,直接用來自動售票(就是把起始點設定好,票價設定好,直接把二維碼貼出來,讓乘客自動掃相關的二維碼,完成購票,上車的時候,只需要提供自己的支付憑證給乘車員驗證即可),這樣,不僅綠色環保了,還大大的提高了售票的速度(去過大車站購票的人應該深有體驗,排隊買個票,好歹半個小時以上,心裡也是萬頭草泥馬在奔騰的)。
咱就不扯遠了,說回咱麼今天要做的微信支付之掃碼支付。微信官方的文件,這個掃碼支付(NativePay)分為兩種,一種是“生成掃描支付模式”,另外一種是“生成直接支付url,支付url有效期為2小時”,至於這裡面,兩種掃碼模式,怎麼靈活利用呢,官方也沒有一個明確的說明。個人理解為,第一種(生成掃描支付模式),適用於固定二維碼的,就是永久使用的那種。
例如一些商家的公眾號的二維碼,是永久的,什麼時候掃,都是關注這個公眾號的,但是,這種的話,我記得微信是有限量的,貌似是一個公眾號,限量10w,個人觀點,覺得這個限量,是足夠我們使用的。第二種(生成直接支付url,支付url有效期為2小時),這種的話,因為有有效期這種時間限制,超過了2個小時,該二維碼就失效,但是對生成的二維碼數量沒有限制,所以,這種個人觀點覺得適用於那種臨時根據實際情況生成的二維碼,例如:公眾平臺登陸的時候二次驗證的二維碼,自定義生成,僅為一次性繳費使用的二維碼,等等)。接下來,我們就開始講講實際例子,首先將的就是第一種模式。
掃碼支付之模式一(生成掃描支付模式):
首先,我們新建一個“MVC”的專案(asp.net的官方的demo就是了,要asp.net的自己看demo吧,demo地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1),然後把系統自動生成的HomeControler和View中的Home都刪了。
然後自己新建一個HomeControler,程式碼如下:
?12345 | // GET: Home public ActionResult Index() { return View(); } |
再新增一個View,程式碼如下:
?1234567891011121314 | @{ Layout = null; } <!DOCTYPE html> < html > < head > < meta name = "viewport" content = "width=device-width" /> < title >首頁</ title > </ head > < body > < div > </ div > </ body > </ html > |
接下來,我們先把官方的demo的一些我們會用到的東西拷貝過來,其中包括以下幾個資料夾,如下圖:
就這個lib和business兩個,把這兩個資料夾,支付複製到咱們的新專案中,並且包含在專案中,如下:
然後我們再“重新生成”以下專案,或者快捷鍵:ctrl+shift+b,這時候,會提下如下錯誤:
這時候,我們去新增引用,把lib資料夾中的LitJson.dll 新增上即可,如下圖:
到這裡,我們就基本把官方的demo的環境給搭建好了,接下來,我們就要開始編寫程式碼了。
首先,我的邏輯是,從前到後,就是從前端到後端。前端是顯示二維碼的地方,那麼我們就先給他一個div(本文使用到的是jquery的二維碼生成外掛,全名叫:jquery.qrcode.min.js,我會傳到附件上),然後在頁面載入完畢的時候,會請求後臺,讓他返回二維碼字串,然後再通過jquery的二維碼生成外掛,讓他生成二維碼並顯示在前臺,程式碼如下:
前端:
?123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960 | @{ Layout = null ; } <!DOCTYPE html> <html> <head> <meta name= "viewport" content= "width=device-width" /> <title>首頁</title> <link href= "~/Scripts/jquery-easyui-1.4.5/themes/bootstrap/easyui.css" rel= "stylesheet" /> <link href= "~/Scripts/jquery-easyui-1.4.5/themes/mobile.css" rel= "stylesheet" /> <link href= "~/Scripts/jquery-easyui-1.4.5/themes/icon.css" rel= "stylesheet" /> </head> <body> <p> 模式一:生成掃描支付模式 <br /> <div id= "QRCode1" > </div> </p> <p> 模式二:生成直接支付url,支付url有效期為2小時 <br /> <div id= "QRCode2" > </div> </p> <script src= "~/Scripts/jquery-1.10.2.js" ></script> <script src= "~/Scripts/jquery-easyui-1.4.5/jquery.easyui.min.js" ></script> <script src= "~/Scripts/jquery-easyui-1.4.5/jquery.easyui.mobile.js" ></script> <script src= "~/Scripts/jquery-easyui-1.4.5/easyloader.js" ></script> <script src= "~/Scripts/jquery.qrcode.min.js" ></script> <script type= "text/javascript" > $( function () { fGetQRCode1(); }) function fGetQRCode1() { $.messager.progress({ title: "" , msg: "正在生成二維碼:模式一,請稍後..." }); $.ajax({ type: "post" , url: "/Home/GetQRCode1" , data: { time: new Date(), productId:7788 }, success: function (json) { $.messager.progress( 'close' ); //記得關閉 if (json.result) { $( '#QRCode1' ).qrcode(json.str); //生成二維碼 } else { $( '#QRCode1' ).html( "二維碼生成失敗" ); } } }) } </script> </body> </html> |
後端:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 | using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using WxPayAPI; namespace WxPay.Controllers { public class HomeController : Controller { // GET: Home public ActionResult Index() { return View(); } /// <summary> /// 模式一 /// </summary> /// <returns></returns> [HttpPost] public ActionResult GetQRCode1() { object objResult = "" ; string strProductID = Request.Form[ "productId" ]; string strQRCodeStr = GetPrePayUrl(strProductID); if (! string .IsNullOrWhiteSpace(strProductID)) { objResult = new { result = true , str = strQRCodeStr }; } else { objResult = new { result = false }; } return Json(objResult); } /** * 生成掃描支付模式一URL * @param productId 商品ID * @return 模式一URL */ public string GetPrePayUrl( string productId) { WxPayData data = new WxPayData(); data.SetValue( "appid" , WxPayConfig.APPID); //公眾帳號id data.SetValue( "mch_id" , WxPayConfig.MCHID); //商戶號 data.SetValue( "time_stamp" , WxPayApi.GenerateTimeStamp()); //時間戳 data.SetValue( "nonce_str" , WxPayApi.GenerateNonceStr()); //隨機字串 data.SetValue( "product_id" , productId); //商品ID data.SetValue( "sign" , data.MakeSign()); //簽名 string str = ToUrlParams(data.GetValues()); //轉換為URL串 return url; } /** * 引數陣列轉換為url格式 * @param map 引數名與引數值的對映表 * @return URL字串 */ private string ToUrlParams(SortedDictionary< string , object > map) { string buff = "" ; foreach (KeyValuePair< string , object > pair in map) { buff += pair.Key + "=" + pair.Value + "&" ; } buff = buff.Trim( '&' ); return buff; } } } |
這時候,模式一是不是感覺就完成了?那麼我們現在試試,我們瀏覽該頁面,如下:
然後用微信掃一掃功能掃一下,發現提示如下:
這是什麼鬼,是不是,你心裡面是不是想知道為啥,那我來告訴你,這是為啥,這是因為,你還沒有設定回撥頁面或者回調頁面有問題,這個時候,我們再新建一個Control,命名為:NativeNotifyController.cs,程式碼如下:
?123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.Web.Mvc; using WxPayAPI; namespace WxPay.Controllers { public class NativeNotifyController : Controller { // GET: NativeNotify public ActionResult Index() { string strData = ProcessNotify(); Response.Write(strData); return View(); } public string ProcessNotify() { WxPayData notifyData = GetNotifyData(); //檢查openid和product_id是否返回 if (!notifyData.IsSet( "openid" ) || !notifyData.IsSet( "product_id" )) { WxPayData res = new WxPayData(); res.SetValue( "return_code" , "FAIL" ); res.SetValue( "return_msg" , "回撥資料異常" ); return res.ToXml(); } //調統一下單介面,獲得下單結果 string openid = notifyData.GetValue( "openid" ).ToString(); string product_id = notifyData.GetValue( "product_id" ).ToString(); WxPayData unifiedOrderResult = new WxPayData(); try { unifiedOrderResult = UnifiedOrder(openid, product_id); } catch (Exception ex) //若在調統一下單介面時拋異常,立即返回結果給微信支付後臺 { WxPayData res = new WxPayData(); res.SetValue( "return_code" , "FAIL" ); res.SetValue( "return_msg" , "統一下單失敗" ); return res.ToXml(); } //若下單失敗,則立即返回結果給微信支付後臺 if (!unifiedOrderResult.IsSet( "appid" ) || !unifiedOrderResult.IsSet( "mch_id" ) || !unifiedOrderResult.IsSet( "prepay_id" )) { WxPayData res = new WxPayData(); res.SetValue( "return_code" , "FAIL" ); res.SetValue( "return_msg" , "統一下單失敗" ); return res.ToXml(); } //統一下單成功,則返回成功結果給微信支付後臺 WxPayData data = new WxPayData(); data.SetValue( "return_code" , "SUCCESS" ); data.SetValue( "return_msg" , "OK" ); data.SetValue( "appid" , WxPayConfig.APPID); data.SetValue( "mch_id" , WxPayConfig.MCHID); data.SetValue( "nonce_str" , WxPayApi.GenerateNonceStr()); data.SetValue( "prepay_id" , unifiedOrderResult.GetValue( "prepay_id" )); data.SetValue( "result_code" , "SUCCESS" ); data.SetValue( "err_code_des" , "OK" ); data.SetValue( "sign" , data.MakeSign()); return data.ToXml(); } /// <summary> /// 接收從微信支付後臺傳送過來的資料並驗證簽名 /// </summary> /// <returns>微信支付後臺返回的資料</returns> public WxPayData GetNotifyData() { //接收從微信後臺POST過來的資料 System.IO.Stream s = Request.InputStream; int count = 0; byte [] buffer = new byte [1024]; StringBuilder builder = new StringBuilder(); while ((count = s.Read(buffer, 0, 1024)) > 0) { builder.Append(Encoding.UTF8.GetString(buffer, 0, count)); } s.Flush(); s.Close(); s.Dispose(); //轉換資料格式並驗證簽名 WxPayData data = new WxPayData(); try { data.FromXml(builder.ToString()); } catch (WxPayException ex) { //若簽名錯誤,則立即返回結果給微信支付後臺 WxPayData res = new WxPayData(); res.SetValue( "return_code" , "FAIL" ); res.SetValue( "return_msg" , ex.Message); } return data; } private WxPayData UnifiedOrder( string openId, string productId) { //統一下單 WxPayData req = new WxPayData(); req.SetValue( "body" , "廣東XXXX股份有限公司" ); req.SetValue( "attach" , "附加資訊,用於後臺或者存入資料庫,做自己的判斷" ); req.SetValue( "out_trade_no" , WxPayApi.GenerateOutTradeNo()); req.SetValue( "total_fee" , 1); req.SetValue( "time_start" , DateTime.Now.ToString( "yyyyMMddHHmmss" )); req.SetValue( "time_expire" , DateTime.Now.AddMinutes(10).ToString( "yyyyMMddHHmmss" )); req.SetValue( "goods_tag" , "商品的備忘,可以自定義" ); req.SetValue( "trade_type" , "NATIVE" ); req.SetValue( "openid" , openId); req.SetValue( "product_id" , productId); WxPayData result = WxPayApi.UnifiedOrder(req); return result; } } } |
記得,也要新建一個View,就是在Index那裡,右鍵新增一個View,View的程式碼如下(你沒眼花,就是空的,不管他):
1234567891011121314 | @{ Layout = null; } <!DOCTYPE html> < html > < head > < meta name = "viewport" content = "width=device-width" /> < title >Index</ title > </ head > < body > < div > </ div > </ body > </ html > |
接著,把這個專案,釋出出來,放到伺服器的iis上,這裡面,我把他釋出在http://sm.lmx.ren/上面(必須要釋出到網上哈,如果不懂釋出的,你可以自己去學習基礎知識先了),這還沒完,還需要把到公眾平臺上,設定回撥頁面,操作如下:
這樣,就大功告成了。這時候,我們再試試掃碼,發現已經得到以下提示了,這樣子,就代表,我們的模式一,已經成功完成了。如下圖:
這時候,細心的朋友就會提問了,我這都支付成功了,怎麼頁面沒啥提示呀,這頁面不互動很不友好啊。嗯,沒錯,童鞋,你有前途,現在我就告訴你,怎麼做互動,但是,為了你日後更加有前途,我只告訴你邏輯,具體怎麼實現,自己來想,多動腦。
那麼邏輯是怎麼的呢?常規邏輯下,我們微信掃頁面上的這個二維碼的時候,這個時候,他已經把我們二維碼裡面的引數,傳到微信伺服器,然後有他們開始統一下單(如果對邏輯不清晰,可以看看官方的文件:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_3):他們在統一下單的時候,就會生成一個product_id,這個傢伙的作用呢 ,就是告訴你現在微信伺服器,已經生成了一個單號,勞資已經收到你的支付請求了,趕緊給老子付款,O(∩_∩)O哈哈~。。。停,停,停。這時候,思路不能繼續往下走了。
記得,前面有個叫做“統一下單“,那既然有這個步驟,那我們可以利用一下,就是當他統一下單成功的時候,我們可以在頁面更新一下狀態,告訴客戶:您已成功