在我前面的幾篇博客,有介紹了微信支付、微信紅包、企業付款等各種和支付相關的操作,不過上面都是基於微信普通API的封裝,本篇隨筆繼續微信支付這一主題,繼續介紹基於微信網頁JSAPI的方式發起的微信支付功能實現,微信的JSAPI相對於普通的API操作,調試沒有那麼方便,而且有時候有些錯誤需要反復核實。本篇隨筆基於實際的微信網頁支付案例,對微信JSAPI的支付實現進行介紹。
在我前面《C#開發微信門戶及應用(39)--使用微信JSSDK實現簽到的功能》介紹的內容裡面,有介紹了很多JS-SDK的基礎知識,我們基於網頁發起的微信支付,我們也是基於JS-SDK的基礎上進行發起的,因此需要了解這些JS-SDK的使用步驟。
一般來說,我們網頁JSAPI發起的微信支付,需要使用wx.chooseWXPay的操作方法,而這個方法也是需要在完成wx.config初始化的時候,由界面元素進行觸發處理的。
例如我們可以這樣實現整個微信支付的處理過程:
1)先使用JS對API進行初始化配置
wx.config({
debug: false,
appId: appid, // 必填,公眾號的唯一標識
timestamp: timestamp, // 必填,生成簽名的時間戳
nonceStr: noncestr, // 必填,生成簽名的隨機串
signature: signature, // 必填,簽名,見附錄1
jsApiList: [
'checkJsApi',
'chooseWXPay',
'hideOptionMenu'
]
});
2)使用wx.chooseWXPay發起微信支付
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。
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#開發微信門戶及應用(38)--微信搖一搖紅包功能
C#開發微信門戶及應用(37)--微信公眾號標簽管理功能
C#開發微信門戶及應用(36)--微信卡劵管理的封裝操作
C#開發微信門戶及應用(35)--微信支付之企業付款封裝操作
C#開發微信門戶及應用(34)--微信裂變紅包
C#開發微信門戶及應用(33)--微信現金紅包的封裝及使用
C#開發微信門戶及應用(32)--微信支付接入和API封裝使用
C#開發微信門戶及應用(31)--微信語義理解接口的實現和處理
C#開發微信門戶及應用(30)--消息的群發處理和預覽功能
C#開發微信門戶及應用(28)--微信“搖一搖·周邊”功能的使用和接口的實現
C#開發微信門戶及應用(27)-公眾號模板消息管理
C#開發微信門戶及應用(26)-公眾號微信素材管理
C#開發微信門戶及應用(25)-微信企業號的客戶端管理功能
C#開發微信門戶及應用(24)-微信小店貨架信息管理
C#開發微信門戶及應用(23)-微信小店商品管理接口的封裝和測試
C#開發微信門戶及應用(22)-微信小店的開發和使用
C#開發微信門戶及應用(21)-微信企業號的消息和事件的接收處理及解密
C#開發微信門戶及應用(20)-微信企業號的菜單管理
C#開發微信門戶及應用(19)-微信企業號的消息發送(文本、圖片、文件、語音、視頻、圖文消息等)
C#開發微信門戶及應用(18)-微信企業號的通訊錄管理開發之成員管理
C#開發微信門戶及應用(17)-微信企業號的通訊錄管理開發之部門管理
C#開發微信門戶及應用(16)-微信企業號的配置和使用
C#開發微信門戶及應用(15)-微信菜單增加掃一掃、發圖片、發地理位置功能
C#開發微信門戶及應用(14)-在微信菜單中采用重定向獲取用戶數據
C#開發微信門戶及應用(13)-使用地理位置擴展相關應用
C#開發微信門戶及應用(12)-使用語音處理
C#開發微信門戶及應用(11)--微信菜單的多種表現方式介紹
C#開發微信門戶及應用(10)--在管理系統中同步微信用戶分組信息
C#開發微信門戶及應用(9)-微信門戶菜單管理及提交到微信服務器
C#開發微信門戶及應用(8)-微信門戶應用管理系統功能介紹
C#開發微信門戶及應用(7)-微信多客服功能及開發集成
C#開發微信門戶及應用(6)--微信門戶菜單的管理操作
C#開發微信門戶及應用(5)--用戶分組信息管理
C#開發微信門戶及應用(4)--關注用戶列表及詳細信息管理
C#開發微信門戶及應用(3)--文本消息和圖文消息的應答
C#開發微信門戶及應用(2)--微信消息的處理和應答
C#開發微信門戶及應用(1)--開始使用微信接口