在我前面的几篇博客,有介绍了微信支付、微信红包、企业付款等各类和支付相关的操做,不过上面都是基于微信普通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)--在管理系统中同步微信用户分组信息