在我前面的幾篇博客,有介紹了微信支付、微信紅包、企業付款等各類和支付相關的操做,不過上面都是基於微信普通API的封裝,本篇隨筆繼續微信支付這一主題,繼續介紹基於微信網頁JSAPI的方式發起的微信支付功能實現,微信的JSAPI相對於普通的API操做,調試沒有那麼方便,並且有時候有些錯誤須要反覆覈實。本篇隨筆基於實際的微信網頁支付案例,對微信JSAPI的支付實現進行介紹。javascript
在我前面《C#開發微信門戶及應用(39)--使用微信JSSDK實現簽到的功能》介紹的內容裏面,有介紹了不少JS-SDK的基礎知識,咱們基於網頁發起的微信支付,咱們也是基於JS-SDK的基礎上進行發起的,所以須要瞭解這些JS-SDK的使用步驟。html
通常來講,咱們網頁JSAPI發起的微信支付,須要使用wx.chooseWXPay的操做方法,而這個方法也是須要在完成wx.config初始化的時候,由界面元素進行觸發處理的。前端
例如咱們能夠這樣實現整個微信支付的處理過程:java
1)先使用JS對API進行初始化配置ajax
wx.config({ debug: false, appId: appid, // 必填,公衆號的惟一標識 timestamp: timestamp, // 必填,生成簽名的時間戳 nonceStr: noncestr, // 必填,生成簽名的隨機串 signature: signature, // 必填,簽名,見附錄1 jsApiList: [ 'checkJsApi', 'chooseWXPay', 'hideOptionMenu' ] });
2)使用wx.chooseWXPay發起微信支付json
wx.chooseWXPay({ timestamp: 0, // 支付簽名時間戳,注意微信jssdk中的全部使用timestamp字段均爲小寫。但最新版的支付後臺生成簽名使用的timeStamp字段名需大寫其中的S字符 nonceStr: '', // 支付簽名隨機串,不長於 32 位 package: '', // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=***) signType: '', // 簽名方式,默認爲'SHA1',使用新版支付需傳入'MD5' paySign: '', // 支付簽名 success: function (res) { // 支付成功後的回調函數 } });
備註:prepay_id 經過微信支付統一下單接口拿到,paySign 採用統一的微信支付 Sign 簽名生成方法,注意這裏 appId 也要參與簽名,appId 與 config 中傳入的 appId 一致,即最後參與簽名的參數有appId, timeStamp, nonceStr, package, signType。api
3)獲取用戶的openid緩存
在一些JSAPI操做裏面,有時候須要傳入當前用戶的openid,因爲這個值,通常是不能直接得到的,但能夠經過用戶受權代碼獲取,所以咱們能夠在菜單中配置好重定向的URL,根據URL獲取對應的code,而後解析code爲對應的openid便可。服務器
經過code換取的是一個特殊的網頁受權access_token,與基礎支持中的access_token(該access_token用於調用其餘接口)不一樣。公衆號可經過下述接口來獲取網頁受權access_token。若是網頁受權的做用域爲snsapi_base,則本步驟中獲取到網頁受權access_token的同時,也獲取到了openid,snsapi_base式的網頁受權流程即到此爲止。微信
獲取code後,請求如下連接獲取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
要獲取相關的JS-SDK的相關接口參數,咱們須要先生成JSAPI-Ticket憑證,生成這個憑證代碼接口實現以下所示。通常來講,這個接口的數據須要緩存起來的,具體能夠本身實現處理。
/// <summary> /// 獲取JSAPI_TICKET接口 /// </summary> /// <param name="accessToken">調用接口憑證</param> /// <returns></returns> public string GetJSAPI_Ticket(string accessToken) { var url = string.Format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", accessToken); var result = JsonHelper<GetTicketResult>.ConvertJson(url); return result != null ? result.ticket : null; }
咱們要實現JSSDK簽名的處理,必須先根據幾個變量,構建好URL字符串,具體的處理過程,咱們能夠把它們逐一放在一個Hashtable裏面,以下代碼所示。
/// <summary> /// 獲取JSSDK所須要的參數信息,返回Hashtable結合 /// </summary> /// <param name="appId">微信AppID</param> /// <param name="jsTicket">根據Token獲取到的JSSDK ticket</param> /// <param name="url">頁面URL</param> /// <returns></returns> public static Hashtable GetParameters(string appId, string jsTicket, string url) { string timestamp = GetTimeStamp(); string nonceStr = GetNonceStr(); // 這裏參數的順序要按照 key 值 ASCII 碼升序排序 string rawstring = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "×tamp=" + timestamp + "&url=" + url + ""; string signature = GetSignature(rawstring); Hashtable signPackage = new Hashtable(); signPackage.Add("appid", appId); signPackage.Add("noncestr", nonceStr); signPackage.Add("timestamp", timestamp); signPackage.Add("url", url); signPackage.Add("signature", signature); signPackage.Add("jsapi_ticket", jsTicket); signPackage.Add("rawstring", rawstring); return signPackage; }
這樣把它們放在哈希表裏面,方便咱們提取出來使用。
wx.config({ debug: false, appId: appid, // 必填,公衆號的惟一標識 timestamp: timestamp, // 必填,生成簽名的時間戳 nonceStr: noncestr, // 必填,生成簽名的隨機串 signature: signature, // 必填,簽名,見附錄1 jsApiList: [ 'checkJsApi', 'chooseWXPay', 'hideOptionMenu' ] });
爲了在MVC視圖頁面裏面,設置咱們計算出來的值,通常咱們須要在後臺進行計算好,並把它們放在ViewBag變量中就能夠在頁面前端使用了,以下所示是MVC視圖頁面的後臺代碼。
/// <summary> /// 刷新JS-SDK的票據 /// </summary> protected virtual void RefreshTicket(AccountInfo accountInfo) { Hashtable ht = baseApi.GetJSAPI_Parameters(accountInfo.AppID, accountInfo.AppSecret, Request.Url.AbsoluteUri); ViewBag.appid = ht["appid"].ToString(); ViewBag.nonceStr = ht["noncestr"].ToString(); ViewBag.timestamp = ht["timestamp"].ToString(); ViewBag.signature = ht["signature"].ToString(); }
這樣,在MVC的視圖頁面裏面,咱們的代碼能夠這樣實現JSAPI變量的初始化。
<script language="javascript"> var openid = '@ViewBag.openid'; var appid = '@ViewBag.appid'; var noncestr = '@ViewBag.noncestr'; var signature = '@ViewBag.signature'; var timestamp = '@ViewBag.timestamp'; wx.config({ debug: false, appId: appid, // 必填,公衆號的惟一標識 timestamp: timestamp, // 必填,生成簽名的時間戳 nonceStr: noncestr, // 必填,生成簽名的隨機串 signature: signature, // 必填,簽名,見附錄1 jsApiList: [ 'checkJsApi', 'chooseWXPay', 'hideOptionMenu' ] });
在第一小節裏面,我提到了,初始化JS-API後,還須要使用wx.chooseWXPay發起微信支付,這個接口也有幾個相關的參數。
wx.chooseWXPay({ timestamp: 0, // 支付簽名時間戳,注意微信jssdk中的全部使用timestamp字段均爲小寫。但最新版的支付後臺生成簽名使用的timeStamp字段名需大寫其中的S字符 nonceStr: '', // 支付簽名隨機串,不長於 32 位 package: '', // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=***) signType: '', // 簽名方式,默認爲'SHA1',使用新版支付需傳入'MD5' paySign: '', // 支付簽名 success: function (res) { // 支付成功後的回調函數 } });
其中這裏的timestamp和nonceStr的規則和前面初始化操做的參數規則同樣,可是注意不能和初始化接口的timestamp和nonceStr保持同樣,不然發起支付會出現【 支付驗證簽名失敗】的錯誤。
package的變量就是咱們調用統一下單接口的得到的預下單id,格式以下所示:
prepay_id=wx2016051517463160322779de0375788970
而爲了得到這個預下單的ID,咱們先須要根據統一下單接口的須要,構建一個數據對象,以下所示。
PayOrderData data = new PayOrderData() { product_id = id, body = "測試支付" + id, attach = "愛奇迪技術支持", detail = "測試JSAPI支付" + id, total_fee = 1, goods_tag = "test" + id, trade_type = "JSAPI", openid = openid };
而後調用前面封裝過的統一下單接口API獲取對應的統一下單ID
TenPayApi api = new TenPayApi(accountInfo); var orderResult = api.UnifiedOrder(data); LogHelper.Debug(string.Format("統一下單結果:{0}", (orderResult != null) ? orderResult.ToJson() : "爲空值")); if (string.IsNullOrEmpty(orderResult.prepay_id) || string.IsNullOrEmpty(orderResult.appid)) { throw new WeixinException("統一下單結果返回失敗!"); }
signType固定爲MD5,
最後剩下paySign這個比較複雜的參數了,這個參數就是須要根據前面這些參數進行簽名的值。微信支付的簽名仍是和普通API的作法(在前面介紹微信支付的時候,有介紹過相關的規則,具體能夠看看《C#開發微信門戶及應用(32)--微信支付接入和API封裝使用》),引入實體類 WxPayData 來存儲一些業務參數,以及實現參數的簽名處理。
值得注意的是,使用普通API的簽名爲Sign,而使用JSAPI的簽名變量名稱爲paySign,二者處理邏輯同樣,只是名稱不一樣。
這樣咱們在後臺處理相關的變量的代碼以下所示。
/// <summary> /// 獲取JSAPI方式的微信字符串參數對象 /// </summary> /// <param name="accountInfo">當前帳號</param> /// <param name="prepay_id">統一下單ID</param> /// <returns></returns> private WxPayData GetJSPayParam(AccountInfo accountInfo, string prepay_id) { WxPayData data = new WxPayData(); data.SetValue("appId", ViewBag.appId); data.SetValue("timeStamp", data.GenerateTimeStamp()); data.SetValue("nonceStr", data.GenerateNonceStr()); data.SetValue("signType", "MD5"); data.SetValue("package", string.Format("prepay_id={0}", prepay_id)); data.SetValue("paySign", data.MakeSign(accountInfo.PayAPIKey));//簽名 return data; }
而後,再定義一個控制器接口,返回相關的參數數據,部分邏輯代碼以下所示。這樣方便前端經過JSON的格式獲取對應的變量值。
//支付須要的參數 WxPayData param = GetJSPayParam(accountInfo, orderResult.prepay_id); LogHelper.Debug("GetJSPayParam:" + param.ToJson()); var obj = new { timeStamp = param.GetString("timeStamp"), nonceStr = param.GetString("nonceStr"), signType = param.GetString("signType"), package = param.GetString("package"), paySign = param.GetString("paySign") }; return Content(obj.ToJson());
在前面頁面,經過ajax方式得到發起微信支付的相關參數,代碼以下所示。
//發起一個微信支付 function chooseWXPay(id) { //alert(window.location.href); var uid = getUrlVars()["uid"]; $.ajax({ type: 'POST', url: '/JSSDKTest/GetWXPayData', //async: false, //同步 dataType: 'json', data : { id: id, uid: uid, openid : openid }, success: function (json) { wx.chooseWXPay({ appId: appid, timestamp: json.timeStamp, // 支付簽名時間戳,注意微信jssdk中的全部使用timestamp字段均爲小寫。但最新版的支付後臺生成簽名使用的timeStamp字段名需大寫其中的S字符 nonceStr: json.nonceStr, // 支付簽名隨機串,不長於 32 位 package: json.package, // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=***) signType: json.signType, // 簽名方式,默認爲'SHA1',使用新版支付需傳入'MD5' paySign: json.paySign, // 支付簽名 success: function (res) { // 支付成功後的回調函數 if (res.errMsg == 'chooseWXPay:ok') { $.toast('支付成功'); //setTimeout(function () { // window.location.href = "/";//這裏默認跳轉到主頁 //}, 2000); //window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val(); } else if (res.errMsg == 'chooseWXPay:cancel' || res.errMsg == 'chooseWXPay:fail') { $.toast("支付失敗"); //window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val(); } }, cancel: function () { $.toast("用戶取消了支付"); //window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val(); } }); wx.error(function (res) { $.toast("調用支付出現異常"); //window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val(); }) }, error: function (xhr, status, error) { $.toast("操做失敗" + xhr.responseText); //xhr.responseText } }); };
經過上面的代碼,咱們能夠順利發起微信支付,並能夠看到具體的測試結果了,讀者能夠關注咱們的公衆號【廣州愛奇迪】對其中微信支付進行測試瞭解。
微信支付成功後,咱們一樣能夠在微信支付的對話裏面看到響應的結果了。
若是對這個《C#開發微信門戶及應用》系列感興趣,能夠關注個人其餘文章,系列隨筆以下所示:
C#開發微信門戶及應用(40)--使用微信JSAPI實現微信支付功能
C#開發微信門戶及應用(39)--使用微信JSSDK實現簽到的功能
C#開發微信門戶及應用(35)--微信支付之企業付款封裝操做
C#開發微信門戶及應用(32)--微信支付接入和API封裝使用
C#開發微信門戶及應用(31)--微信語義理解接口的實現和處理
C#開發微信門戶及應用(28)--微信「搖一搖·周邊」功能的使用和接口的實現
C#開發微信門戶及應用(23)-微信小店商品管理接口的封裝和測試
C#開發微信門戶及應用(21)-微信企業號的消息和事件的接收處理及解密
C#開發微信門戶及應用(19)-微信企業號的消息發送(文本、圖片、文件、語音、視頻、圖文消息等)
C#開發微信門戶及應用(18)-微信企業號的通信錄管理開發之成員管理
C#開發微信門戶及應用(17)-微信企業號的通信錄管理開發之部門管理
C#開發微信門戶及應用(15)-微信菜單增長掃一掃、發圖片、發地理位置功能
C#開發微信門戶及應用(14)-在微信菜單中採用重定向獲取用戶數據
C#開發微信門戶及應用(11)--微信菜單的多種表現方式介紹
C#開發微信門戶及應用(10)--在管理系統中同步微信用戶分組信息