公司最近在作微信小程序,被分配到作支付這一塊,如今對這一塊作一個簡單的總結和梳理。php
支付,對於購物來講,能夠說是佔據了十分重要的一塊,畢竟能收到錢纔是重點。算法
固然在開發以前,咱們須要有下面這些東西:json
固然這些是不用咱們本身申請的,公司會有人申請好,而後要什麼跟這我的說,讓他提供就能夠了。小程序
首先來看一下官方給出的業務流程時序圖微信小程序
這個圖很清晰的表達了在小程序支付中的整個流程,每一步要作些什麼。api
一個完整的支付,通常狀況下都是包含了下面三個主要的點;服務器
下面就重點來簡單實現一下上面說的第一點,支付,也是能夠進行下面兩步的在大前提。微信
簡單起見,在index.wxml中添加一個輸入框和一個button,綁定一下相應的事件,輸入框主要是用於輸入訂單號,按鈕用於模擬提交一個訂單併發起支付。網絡
<!--index.wxml--> <view class="container"> <input type="text" bindinput="getOrderCode" style="border:1px solid #ccc;" /> <button bindtap="pay">當即支付</button> </view>
而後在index.js中寫上一小段代碼,主要是處理上面按鈕的點擊事件。session
Page({ data: { txtOrderCode: '' }, pay: function () { var ordercode = this.data.txtOrderCode; wx.login({ success: function (res) { if (res.code) { wx.request({ url: 'https://www.yourdomain.com/pay', data: { code: res.code,//要去換取openid的登陸憑證 ordercode: ordercode }, method: 'GET', success: function (res) { console.log(res.data) wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, signType: 'MD5', paySign: res.data.paySign, success: function (res) { // success console.log(res); }, fail: function (res) { // fail console.log(res); }, complete: function (res) { // complete console.log(res); } }) } }) } else { console.log('獲取用戶登陸態失敗!' + res.errMsg) } } }); }, getOrderCode: function (event) { this.setData({ txtOrderCode: event.detail.value }); } })
能夠看到,在這裏Catcher先經過wx.login這個API先取到了登陸的憑證code,並把這個憑證code作爲請求參數用wx.request這個API發起一個網絡請求。
在這個網絡請求處理後會返回小程序支付所須要的相關參數。拿到這些參數後,再調用wx.requestPayment這個支付API,此時纔算是真正的發起支付。
至此,小程序這邊的事已經作完了,接下來就是要去處理接口那邊的事了,其實接口要作的就是返回小程序須要的幾個參數。可是要拿到這幾個參數仍是須要作很多事情的。
據悉最新版的Senparc.Weixin.MP已經支付了小程序相關的內容,可是公司用的版本仍是比較低
而且近期也沒有打算對這個組件進行升級。因此就從白紙一張開始了。
用的是mvc,因此這個小程序發起的網絡請求會由下面的action的執行,裏面的實現,每一步作了什麼應該也已經很清晰了。
public ActionResult Pay(string code, string ordercode) { var paramter = new Parameters(); paramter.out_trade_no = ordercode; //使用登陸憑證 code 獲取 session_key 和 openid var unifiedorderRes = GetOpenIdAndSessionKey(paramter.appid, paramter.secret, code); //反序列化session_key 和 openid成ChangeResponseEntity實體 var tmp = JsonConvert.DeserializeObject<ChangeResponseEntity>(unifiedorderRes); //統一下單的url和參數 var payUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; var param = GetUnifiedOrderParam(tmp.openid, paramter); //統一下單後拿到的xml結果 var payResXML = Helper.DoPost(param, payUrl); var payRes = XDocument.Parse(payResXML); var root = payRes.Element("xml"); //序列化相應參數返回給小程序 var res = GetPayRequestParam(root, paramter.appid, paramter.key); return Json(res, JsonRequestBehavior.AllowGet); }
因爲只是一個演示的過程,不想這些數據常常以字符串的形式頻繁出如今代碼中,因此把相關的參數所有都放到了一個名爲Parameters的類中(放到配置文件中也是能夠的),除了訂單號是從小程序傳過來的,固然在實際中這是不合理的,畢竟像金額這些東西,不可能每次都是同一個!這點是要注意的。
下面先來看看這個Parameters類的定義:
public class Parameters { public string appid { get { return "申請的appid"; } } public string mchid { get { return "申請的商戶號"; } } public string nonce { get { return Helper.GetNoncestr(); } } public string notify_url { get { return "http://yourdomain.com/notifyurl"; } } public string body { get { return "testpay"; } } public string out_trade_no { get; set; } public string spbill_create_ip { get { return "IP地址"; } } public string total_fee { get { return "1"; } } public string trade_type { get { return "JSAPI"; } } public string key { get { return "在商家後臺設置的密鑰"; } } public string secret { get { return "在配置小程序時的密鑰"; } } }
首先是獲取到登陸憑證後發起的這個網絡請求。這個網絡請求是決定了此次支付可否成功的第一步!
下面要作的是用登陸憑證去換咱們要的openid。
/// <summary> /// 取openid和session_key /// </summary> /// <param name="appid"></param> /// <param name="secret"></param> /// <param name="js_code"></param> /// <returns></returns> private string GetOpenIdAndSessionKey(string appid, string secret, string js_code) { var url = string.Format("https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code" , appid,secret,js_code); var request = WebRequest.Create(url) as HttpWebRequest; var response = request.GetResponse(); var respStream = response.GetResponseStream(); var res = string.Empty; using (var reader = new StreamReader(respStream, Encoding.UTF8)) { res = reader.ReadToEnd(); } return res; }
要換取openid,就要向微信提供的地址發起一個網絡請求,並在URL帶上appid,secret和憑證code這三個參數。
而後就能夠拿到一個下面形式的json字符串
{ "openid": "OPENID", "session_key": "SESSIONKEY" }
拿到以後天然就是要對這個字符串進行json的反序列化,這裏用到了json.net這個包。
根據時序圖,下面要調用統一下單這個接口了。
上面的代碼,在統一下單這一塊,又分爲下面幾個步驟
參數的處理:
具體規則參見:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
/// <summary> /// 取統一下單的請求參數 /// </summary> /// <param name="openid"></param> /// <param name="param"></param> /// <returns></returns> private string GetUnifiedOrderParam(string openid, Parameters param) { //參與統一下單簽名的參數,除最後的key外,已經按參數名ASCII碼從小到大排序 var unifiedorderSignParam = string.Format("appid={0}&body={1}&mch_id={2}&nonce_str={3}¬ify_url={4}&openid={5}&out_trade_no={6}&spbill_create_ip={7}&total_fee={8}&trade_type={9}&key={10}" , param.appid, param.body, param.mchid, param.nonce, param.notify_url , openid, param.out_trade_no, param.spbill_create_ip, param.total_fee, param.trade_type, param.key); //MD5 var unifiedorderSign = Helper.GetMD5(unifiedorderSignParam).ToUpper(); //構造統一下單的請求參數 return string.Format(@"<xml> <appid>{0}</appid> <body>{1}</body> <mch_id>{2}</mch_id> <nonce_str>{3}</nonce_str> <notify_url>{4}</notify_url> <openid>{5}</openid> <out_trade_no>{6}</out_trade_no> <spbill_create_ip>{7}</spbill_create_ip> <total_fee>{8}</total_fee> <trade_type>{9}</trade_type> <sign>{10}</sign> </xml> ", param.appid, param.body, param.mchid, param.nonce, param.notify_url, openid , param.out_trade_no, param.spbill_create_ip, param.total_fee, param.trade_type, unifiedorderSign); }
這裏要注意一點,因爲咱們的傳的trade_type是JSAPI,因此這裏必須是要加上openid進行處理的。
而後就是解析統一下單返回的XML了,說是解析,其實也就是要拿到咱們須要的數據罷了。這裏最後會獲得一個小程序支付API須要的參數實體。
/// <summary> /// 獲取返回給小程序的支付參數 /// </summary> /// <param name="root"></param> /// <param name="appid"></param> /// <param name="key"></param> /// <returns></returns> private PayRequesEntity GetPayRequestParam(XElement root,string appid,string key) { //當return_code 和result_code都爲SUCCESS時纔有咱們要的prepay_id if (root.Element("return_code").Value == "SUCCESS" && root.Element("result_code").Value == "SUCCESS") { var package = "prepay_id=" + root.Element("prepay_id").Value; var nonceStr = Helper.GetNoncestr(); var signType = "MD5"; var timeStamp = Convert.ToInt64((DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds).ToString(); var paySignParam = string.Format("appId={0}&nonceStr={1}&package={2}&signType={3}&timeStamp={4}&key={5}", appid, nonceStr, package, signType, timeStamp, key); var paySign = Helper.GetMD5(paySignParam).ToUpper(); var payEntity = new PayRequesEntity { package = package, nonceStr = nonceStr, paySign = paySign, signType = signType, timeStamp = timeStamp }; return payEntity; } return new PayRequesEntity(); }
支付參數實體對應的內容以下:
/// <summary> /// 小程序支付須要的參數 /// </summary> public class PayRequesEntity { /// <summary> /// 時間戳從1970年1月1日00:00:00至今的秒數,即當前的時間 /// </summary> public string timeStamp { get; set; } /// <summary> /// 隨機字符串,長度爲32個字符如下。 /// </summary> public string nonceStr { get; set; } /// <summary> /// 統一下單接口返回的 prepay_id 參數值 /// </summary> public string package { get; set; } /// <summary> /// 簽名算法 /// </summary> public string signType { get; set; } /// <summary> /// 簽名 /// </summary> public string paySign { get; set; } }
須要注意的是,這裏的簽名操做,必定是要配合appId,這也是Catcher在支付這一塊踩的惟一的一個坑,因此提醒一下各位讀者,但願能避開這個坑。
還有最後一步就是要返回一個序列化的對象給小程序,以供小程序使用。
到這裏,後臺接口也已經OK了,如今就用真機掃描二維碼,點擊當即支付按鈕,此時就會彈出要你輸入密碼的框框,輸入你的微信支付密碼,以下所示:
而後就會提示支付成功,以下所示:
幾秒鐘以後就會收到微信支付發來的消息,通知你在何時支出了多少錢。
前面也提到了,通知是用戶支付成功後,微信的服務器會向咱們統一下單指定的notify_url發起一個異步的回調。
下面用僞代碼來表示這一過程
public ActionResult Notify() { //1.獲取微信通知的參數 //2.更新訂單的相關狀態 //3.返回一個xml格式的結果給微信服務器 var res = @"<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>"; return Content(res); }
這裏須要注意的是要處理好微信重複通知的狀況!
通知和查詢本質上都是想知道訂單是否支付成功了。
它們的區別是:通知是微信主動通知商家; 查詢是商家主動向微信發起查詢;
這兩個動做的主體是不同的。
當微信能正常發起推送而且商家接收這個推送的服務器又沒有掛的時候,查詢的做用是微乎其微的。
固然,不可避免的會出現,微信不能正常發起推送或者商家的服務器掛了,這個時候查詢的做用就變得很重要了!!
這個時候咱們就要建交起一個定時做業來專門處理這種狀況了,能夠選擇Quartz.Net,Hangfire等!
這個做業的內容具體以下:
public void QueryJob() { //1.找到要查詢的訂單號 //2.根據訂單號和appId等內容向https://api.mch.weixin.qq.com/pay/orderquery這個地址發起網絡請求 //3.拿到微信返回的結果 //4.根據結果進行相應的處理 }
至於多久執行一次這個做業,可能就要根據使用小程序進行購物的數量多很少來作一個大體的估計。
小程序的支付仍是算是比較簡單,畢竟文檔還算齊全,基本照着文檔的提示就能把這個支付作好。