1. 程式人生 > >C#實現微信支付

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: Homepublic 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>

後端:

?
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970using 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: Homepublic 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);//公眾帳號iddata.SetValue("mch_id", WxPayConfig.MCHID);//商戶號data.SetValue("time_stamp", WxPayApi.GenerateTimeStamp());//時間戳data.SetValue("nonce_str", WxPayApi.GenerateNonceStr());//隨機字串data.SetValue("product_id", productId);//商品IDdata.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,程式碼如下:

?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117using 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: NativeNotifypublic 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哈哈~。。。停,停,停。這時候,思路不能繼續往下走了。

記得,前面有個叫做“統一下單“,那既然有這個步驟,那我們可以利用一下,就是當他統一下單成功的時候,我們可以在頁面更新一下狀態,告訴客戶:您已成功