微信小程式支付 1、背景 因業務需要接入微信支付功能(客戶端是微信小程式),因公司伺服器版本較低,服務端採用.Net Framework 版本(並採用盛派微信SDK) 2、文檔地址 1)小程式支付:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api. ...
微信小程式支付
1、背景
因業務需要接入微信支付功能(客戶端是微信小程式),因公司伺服器版本較低,服務端採用.Net Framework 版本(並採用盛派微信SDK)
2、文檔地址
1)小程式支付:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1
2)小程式調起支付API:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
3)支付下單:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
4)發起微信支付:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html
5)盛派SDK:http://sdk.weixin.senparc.com/
3、代碼實現
小程式支付的交互圖如下:
3.1 服務端實現
1)引入包:
2)註冊:在 Global.asmx 里進行註冊
/* * CO2NET 全局註冊開始 * 建議按照以下順序進行註冊 */ /* * CO2NET 是從 Senparc.Weixin 分離的底層公共基礎模塊,經過了長達 6 年的迭代優化。 * 關於 CO2NET 在所有項目中的通用設置可參考 CO2NET 的 Sample: * https://github.com/Senparc/Senparc.CO2NET/blob/master/Sample/Senparc.CO2NET.Sample.netcore/Startup.cs */ //設置全局 Debug 狀態 var isGLobalDebug = true; //全局設置參數,將被儲存到 Senparc.CO2NET.Config.SenparcSetting var senparcSetting = SenparcSetting.BuildFromWebConfig(isGLobalDebug); //也可以通過這種方法在程式任意位置設置全局 Debug 狀態: //Senparc.CO2NET.Config.IsDebug = isGLobalDebug; //CO2NET 全局註冊,必須!! IRegisterService register = RegisterService.Start(senparcSetting).UseSenparcGlobal(); #region 全局緩存配置(按需) #region 配置和使用 Redis -- DPBMARK Redis //配置全局使用Redis緩存(按需,獨立) var redisConfigurationStr = senparcSetting.Cache_Redis_Configuration; var useRedis = !string.IsNullOrEmpty(redisConfigurationStr) && redisConfigurationStr != "Redis配置"; if (useRedis)//這裡為了方便不同環境的開發者進行配置,做成了判斷的方式,實際開發環境一般是確定的,這裡的if條件可以忽略 { /* 說明: * 1、Redis 的連接字元串信息會從 Config.SenparcSetting.Cache_Redis_Configuration 自動獲取並註冊,如不需要修改,下方方法可以忽略 /* 2、如需手動修改,可以通過下方 SetConfigurationOption 方法手動設置 Redis 鏈接信息(僅修改配置,不立即啟用) */ Senparc.CO2NET.Cache.Redis.Register.SetConfigurationOption(redisConfigurationStr); //以下會立即將全局緩存設置為 Redis Senparc.CO2NET.Cache.Redis.Register.UseKeyValueRedisNow();//鍵值對緩存策略(推薦) //Senparc.CO2NET.Cache.Redis.Register.UseHashRedisNow();//HashSet儲存格式的緩存策略 //也可以通過以下方式自定義當前需要啟用的緩存策略 //CacheStrategyFactory.RegisterObjectCacheStrategy(() => RedisObjectCacheStrategy.Instance);//鍵值對 //CacheStrategyFactory.RegisterObjectCacheStrategy(() => RedisHashSetObjectCacheStrategy.Instance);//HashSet } //如果這裡不進行Redis緩存啟用,則目前還是預設使用記憶體緩存 #endregion // DPBMARK_END #region 配置和使用 Memcached -- DPBMARK Memcached //配置Memcached緩存(按需,獨立) var memcachedConfigurationStr = senparcSetting.Cache_Memcached_Configuration; var useMemcached = !string.IsNullOrEmpty(memcachedConfigurationStr) && memcachedConfigurationStr != "Memcached配置"; if (useMemcached) //這裡為了方便不同環境的開發者進行配置,做成了判斷的方式,實際開發環境一般是確定的,這裡的if條件可以忽略 { /* 說明: * 1、Memcached 的連接字元串信息會從 Config.SenparcSetting.Cache_Memcached_Configuration 自動獲取並註冊,如不需要修改,下方方法可以忽略 /* 2、如需手動修改,可以通過下方 SetConfigurationOption 方法手動設置 Memcached 鏈接信息(僅修改配置,不立即啟用) */ Senparc.CO2NET.Cache.Memcached.Register.SetConfigurationOption(memcachedConfigurationStr); //以下會立即將全局緩存設置為 Memcached Senparc.CO2NET.Cache.Memcached.Register.UseMemcachedNow(); //也可以通過以下方式自定義當前需要啟用的緩存策略 CacheStrategyFactory.RegisterObjectCacheStrategy(() => MemcachedObjectCacheStrategy.Instance); } #endregion // DPBMARK_END #endregion #region 註冊日誌(按需,建議) register.RegisterTraceLog(ConfigWeixinTraceLog);//配置TraceLog #endregion /* 微信配置開始 * 建議按照以下順序進行註冊 */ //設置微信 Debug 狀態 var isWeixinDebug = true; //全局設置參數,將被儲存到 Senparc.Weixin.Config.SenparcWeixinSetting var senparcWeixinSetting = SenparcWeixinSetting.BuildFromWebConfig(isWeixinDebug); //也可以通過這種方法在程式任意位置設置微信的 Debug 狀態: //Senparc.Weixin.Config.IsDebug = isWeixinDebug; //微信全局註冊,必須!! register.UseSenparcWeixin(senparcWeixinSetting, senparcSetting) #region 註冊公眾號或小程式(按需) //註冊公眾號(可註冊多個) -- DPBMARK MP .RegisterMpAccount(senparcWeixinSetting, "【盛派網路小助手】公眾號")// DPBMARK_END //註冊多個公眾號或小程式(可註冊多個) -- DPBMARK MiniProgram .RegisterWxOpenAccount(senparcWeixinSetting, "小程式名稱")// DPBMARK_END //除此以外,仍然可以在程式任意地方註冊公眾號或小程式: //AccessTokenContainer.Register(appId, appSecret, name);//命名空間:Senparc.Weixin.MP.Containers #endregion // DPBMARK_END #region 註冊微信支付(按需) -- DPBMARK TenPay //註冊舊微信支付版本(V2)(可註冊多個) .RegisterTenpayOld(senparcWeixinSetting, "標記名稱")//這裡的 name 和第一個 RegisterMpAccount() 中的一致,會被記錄到同一個 SenparcWeixinSettingItem 對象中 //註冊最新微信支付版本(V3)(可註冊多個) .RegisterTenpayV3(senparcWeixinSetting, "標記名稱")//記錄到同一個 SenparcWeixinSettingItem 對象中 #endregion // DPBMARK_END // DPBMARK_END ; /* 微信配置結束 */
/// <summary> /// 配置微信跟蹤日誌 /// </summary> private void ConfigWeixinTraceLog() { //Senparc.CO2NET.Config.IsDebug = false; //這裡設為Debug狀態時,/App_Data/WeixinTraceLog/目錄下會生成日誌文件記錄所有的API請求日誌,正式發佈版本建議關閉 Senparc.Weixin.WeixinTrace.SendCustomLog("系統日誌", "系統啟動");//只在Senparc.Weixin.Config.IsDebug = true的情況下生效 //自定義日誌記錄回調 Senparc.Weixin.WeixinTrace.OnLogFunc = () => { //加入每次觸發Log後需要執行的代碼 }; //當發生基於WeixinException的異常時觸發 Senparc.Weixin.WeixinTrace.OnWeixinExceptionFunc = ex => { //加入每次觸發WeixinExceptionLog後需要執行的代碼 //發送模板消息給管理員 var eventService = new EventService(); eventService.ConfigOnWeixinExceptionFunc(ex); }; }
3)統一下單:
*** 因實際代碼設計隱私問題,因此剔除了,如有問題請聯繫我。
public async Task<ActionResult> GetWxOpenPrepayid(string sessionId,string cost) { try { var sessionBag = SessionContainer.GetSession(sessionId); var openId = sessionBag.OpenId; //生成訂單10位序列號,此處用時間和隨機數生成,商戶根據自己調整,保證唯一 var sp_billno = string.Format("{0}{1}{2}", "商戶號" /*10位*/, SystemTime.Now.ToString("yyyyMMddHHmmss"), TenPayV3Util.BuildRandomStr(6)); var timeStamp = TenPayV3Util.GetTimestamp(); var nonceStr = TenPayV3Util.GetNoncestr(); var price = Convert.ToInt32(Convert.ToDecimal(cost) * 100);//單位:分 var xmlDataInfo = new TenPayV3UnifiedorderRequestData( "小程式AppId", "小程式商戶號", body, sp_billno, price, "127.0.0.1", "回調地址", TenPayV3Type.JSAPI, openId, "小程式商戶key", nonceStr, attach: "附加數據"); var result = TenPayV3.Unifiedorder(xmlDataInfo);//調用統一訂單介面 // WeixinTrace.SendCustomLog("統一訂單介面調用結束", "請求:" + xmlDataInfo.ToJson() + "\r\n\r\n返回結果:" + result.ToJson()); var packageStr = "prepay_id=" + result.prepay_id; //var cacheStrategy = CacheStrategyFactory.GetObjectCacheStrategyInstance(); //cacheStrategy.Set($"WxOpenUnifiedorderRequestData-{openId}", xmlDataInfo, TimeSpan.FromDays(4));//3天內可以發送模板消息 //cacheStrategy.Set($"WxOpenUnifiedorderResultData-{openId}", result, TimeSpan.FromDays(4));//3天內可以發送模板消息 return ToSuccess(data: new { result.prepay_id, appId = "小程式AppId", timeStamp, nonceStr, tradeId = sp_billno, package = packageStr, //signType = "MD5", paySign = TenPayV3.GetJsPaySign("小程式AppId", timeStamp, nonceStr, packageStr, "小程式支付key") }); } catch (Exception ex) { return ToError(msg: ex.Message); }
*** 這個介面 在什麼地方用呢(A:在小程式客戶端調用微信支付的時候需要用到【而且是必須的】,也就是下午的 小程式客戶端調用:wx.requestPayment 的時候)
4)回調地址(支付結果通知):
*** 需要註意的地方已 標出了。(值得註意的就是這個地址必須是外網能訪問到的)
代碼實現:
public ActionResult InquiryNotify() { try { var resHandler = new ResponseHandler(null); var return_code = resHandler.GetParameter("return_code"); var return_msg = resHandler.GetParameter("return_msg"); if (return_code != "SUCCESS") return PayResultNotify(); var attach = resHandler.GetParameter("attach"); if (string.IsNullOrEmpty(attach)) return PayResultNotify(); //這裡請簽名驗證,並校驗返回的訂單金額是否與商戶側的訂單金額一致 var cost = resHandler.GetParameter("total_fee");//金額 var tradeId = resHandler.GetParameter("out_trade_no");//商戶訂單號 //驗證支付狀態與金額 //業務邏輯處理,採用非同步回調 return PayResultNotify("SUCCESS", "OK"); } catch (Exception ex) { return PayResultNotify(); } } private ActionResult PayResultNotify(string returnCode = "FAIL", string returnMsg = "wrong") { var xml = $@"<xml> <return_code><![CDATA[{returnCode}]]></return_code> <return_msg><![CDATA[{returnMsg}]]></return_msg> </xml>"; return Content(xml, "text/xml"); }
3.2、客戶端實現(小程式)
/** * 支付 */ bindPay: function(e) { let that = this let url = wx.getStorageSync(config.domainName) url += '服務端介面地址' let data = { sessionId: wx.getStorageSync(config.sessionId), cost: "支付金額", } wxRequest.getRequest(url, data).then((res) => { if (res.data.success) { let payInfo = res.data.data wx.requestPayment({ timeStamp: payInfo.timeStamp, nonceStr: payInfo.nonceStr, package: payInfo.package, paySign: payInfo.paySign, signType: 'MD5', success: function(res) { /*這裡一定要註意,這裡的方法有可能會不執行(支付完成後 用戶在不點擊 支付完成頁面底部的“完成”按鈕時 這裡的方法是不會執行到的,因此這裡請不要寫業務邏輯代碼)*/ that.queryPayResult(payInfo.tradeId) }, fail: function(res) {
/*用戶取消支付後 會執行這裡的方面*/ that.payCancel(payInfo.tradeId) } }) } else { app.showToast(res.data.msg) } }) }
至此,小程式的支付就完成了。感謝 盛派網路 對微信相關介面的封裝,用起來很方便
說明:
1:如有疑問,可以與我取得聯繫
2:已官方文檔為主,很有可能過些時間後文檔及SDK會發生變化
3:官方文檔已在上文中給出
4:文章首發於公眾號
5:服務端使用的小程式包是盛派的SDK(https://weixin.senparc.com)GitHub:https://github.com/Senparc/WeiXinMPSDK
6:.Net Core 類似,有何疑問也可以與我取得聯繫
如果對您有幫助,點個推薦唄!