在我前面的幾篇博客逊桦,有介紹了微信支付粹舵、微信紅包、企業(yè)付款等各種和支付相關(guān)的操作戈泼,不過上面都是基于微信普通API的封裝婿禽,本篇隨筆繼續(xù)微信支付這一主題,繼續(xù)介紹基于微信網(wǎng)頁JSAPI的方式發(fā)起的微信支付功能實(shí)現(xiàn)大猛,微信的JSAPI相對(duì)于普通的API操作扭倾,調(diào)試沒有那么方便,而且有時(shí)候有些錯(cuò)誤需要反復(fù)核實(shí)挽绩。本篇隨筆基于實(shí)際的微信網(wǎng)頁支付案例膛壹,對(duì)微信JSAPI的支付實(shí)現(xiàn)進(jìn)行介紹。
1、微信JS-SDK的知識(shí)
在我前面《C#開發(fā)微信門戶及應(yīng)用(39)--使用微信JSSDK實(shí)現(xiàn)簽到的功能》介紹的內(nèi)容里面模聋,有介紹了很多JS-SDK的基礎(chǔ)知識(shí)肩民,我們基于網(wǎng)頁發(fā)起的微信支付,我們也是基于JS-SDK的基礎(chǔ)上進(jìn)行發(fā)起的撬槽,因此需要了解這些JS-SDK的使用步驟此改。
一般來說,我們網(wǎng)頁JSAPI發(fā)起的微信支付侄柔,需要使用wx.chooseWXPay的操作方法共啃,而這個(gè)方法也是需要在完成wx.config初始化的時(shí)候,由界面元素進(jìn)行觸發(fā)處理的暂题。
例如我們可以這樣實(shí)現(xiàn)整個(gè)微信支付的處理過程:
1)先使用JS對(duì)API進(jìn)行初始化配置
wx.config({
debug: false,
appId: appid, // 必填移剪,公眾號(hào)的唯一標(biāo)識(shí)
timestamp: timestamp, // 必填,生成簽名的時(shí)間戳
nonceStr: noncestr, // 必填薪者,生成簽名的隨機(jī)串
signature: signature, // 必填纵苛,簽名,見附錄1
jsApiList: [
'checkJsApi',
'chooseWXPay',
'hideOptionMenu'
]
});
2)使用wx.chooseWXPay發(fā)起微信支付
wx.chooseWXPay({
timestamp: 0, // 支付簽名時(shí)間戳言津,注意微信jssdk中的所有使用timestamp字段均為小寫攻人。但最新版的支付后臺(tái)生成簽名使用的timeStamp字段名需大寫其中的S字符
nonceStr: '', // 支付簽名隨機(jī)串,不長于 32 位
package: '', // 統(tǒng)一支付接口返回的prepay_id參數(shù)值悬槽,提交格式如:prepay_id=***)
signType: '', // 簽名方式怀吻,默認(rèn)為'SHA1',使用新版支付需傳入'MD5'
paySign: '', // 支付簽名
success: function (res) {
// 支付成功后的回調(diào)函數(shù)
}
});
備注:prepay_id 通過微信支付統(tǒng)一下單接口拿到初婆,paySign 采用統(tǒng)一的微信支付 Sign 簽名生成方法蓬坡,注意這里 appId 也要參與簽名,appId 與 config 中傳入的 appId 一致磅叛,即最后參與簽名的參數(shù)有appId, timeStamp, nonceStr, package, signType屑咳。
3)獲取用戶的openid
在一些JSAPI操作里面,有時(shí)候需要傳入當(dāng)前用戶的openid弊琴,由于這個(gè)值兆龙,一般是不能直接獲得的,但可以通過用戶授權(quán)代碼獲取敲董,因此我們可以在菜單中配置好重定向的URL紫皇,根據(jù)URL獲取對(duì)應(yīng)的code,然后解析code為對(duì)應(yīng)的openid即可臣缀。
通過code換取的是一個(gè)特殊的網(wǎng)頁授權(quán)access_token,與基礎(chǔ)支持中的access_token(該access_token用于調(diào)用其他接口)不同泻帮。公眾號(hào)可通過下述接口來獲取網(wǎng)頁授權(quán)access_token精置。如果網(wǎng)頁授權(quán)的作用域?yàn)閟nsapi_base,則本步驟中獲取到網(wǎng)頁授權(quán)access_token的同時(shí)锣杂,也獲取到了openid脂倦,snsapi_base式的網(wǎng)頁授權(quán)流程即到此為止番宁。
獲取code后,請(qǐng)求以下鏈接獲取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
2赖阻、微信支付JSAPI初始化的參數(shù)處理
要獲取相關(guān)的JS-SDK的相關(guān)接口參數(shù)蝶押,我們需要先生成JSAPI-Ticket憑證,生成這個(gè)憑證代碼接口實(shí)現(xiàn)如下所示火欧。一般來說棋电,這個(gè)接口的數(shù)據(jù)需要緩存起來的,具體可以自己實(shí)現(xiàn)處理苇侵。
/// <summary>
/// 獲取JSAPI_TICKET接口
/// </summary>
/// <param name="accessToken">調(diào)用接口憑證</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;
}
我們要實(shí)現(xiàn)JSSDK簽名的處理赶盔,必須先根據(jù)幾個(gè)變量,構(gòu)建好URL字符串榆浓,具體的處理過程于未,我們可以把它們逐一放在一個(gè)Hashtable里面,如下代碼所示陡鹃。
/// <summary>
/// 獲取JSSDK所需要的參數(shù)信息烘浦,返回Hashtable結(jié)合
/// </summary>
/// <param name="appId">微信AppID</param>
/// <param name="jsTicket">根據(jù)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();
// 這里參數(shù)的順序要按照 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, // 必填闷叉,公眾號(hào)的唯一標(biāo)識(shí)
timestamp: timestamp, // 必填,生成簽名的時(shí)間戳
nonceStr: noncestr, // 必填猿推,生成簽名的隨機(jī)串
signature: signature, // 必填片习,簽名,見附錄1
jsApiList: [
'checkJsApi',
'chooseWXPay',
'hideOptionMenu'
]
});
為了在MVC視圖頁面里面蹬叭,設(shè)置我們計(jì)算出來的值藕咏,一般我們需要在后臺(tái)進(jìn)行計(jì)算好,并把它們放在ViewBag變量中就可以在頁面前端使用了秽五,如下所示是MVC視圖頁面的后臺(tái)代碼孽查。
/// <summary>
/// 刷新JS-SDK的票據(jù)
/// </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的視圖頁面里面坦喘,我們的代碼可以這樣實(shí)現(xiàn)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, // 必填,公眾號(hào)的唯一標(biāo)識(shí)
timestamp: timestamp, // 必填瓣铣,生成簽名的時(shí)間戳
nonceStr: noncestr, // 必填答朋,生成簽名的隨機(jī)串
signature: signature, // 必填,簽名棠笑,見附錄1
jsApiList: [
'checkJsApi',
'chooseWXPay',
'hideOptionMenu'
]
});
3梦碗、微信支付JSAPI發(fā)起微信支付的參數(shù)處理
在第一小節(jié)里面,我提到了,初始化JS-API后洪规,還需要使用wx.chooseWXPay發(fā)起微信支付印屁,這個(gè)接口也有幾個(gè)相關(guān)的參數(shù)。
wx.chooseWXPay({
timestamp: 0, // 支付簽名時(shí)間戳斩例,注意微信jssdk中的所有使用timestamp字段均為小寫雄人。但最新版的支付后臺(tái)生成簽名使用的timeStamp字段名需大寫其中的S字符
nonceStr: '', // 支付簽名隨機(jī)串,不長于 32 位
package: '', // 統(tǒng)一支付接口返回的prepay_id參數(shù)值念赶,提交格式如:prepay_id=***)
signType: '', // 簽名方式础钠,默認(rèn)為'SHA1',使用新版支付需傳入'MD5'
paySign: '', // 支付簽名
success: function (res) {
// 支付成功后的回調(diào)函數(shù)
}
});
其中這里的timestamp和nonceStr的規(guī)則和前面初始化操作的參數(shù)規(guī)則一樣晶乔,但是注意不能和初始化接口的timestamp和nonceStr保持一樣珍坊,否則發(fā)起支付會(huì)出現(xiàn)【 支付驗(yàn)證簽名失敗】的錯(cuò)誤。
package的變量就是我們調(diào)用統(tǒng)一下單接口的獲得的預(yù)下單id正罢,格式如下所示:
prepay_id=wx2016051517463160322779de0375788970
而為了獲得這個(gè)預(yù)下單的ID阵漏,我們先需要根據(jù)統(tǒng)一下單接口的需要,構(gòu)建一個(gè)數(shù)據(jù)對(duì)象翻具,如下所示履怯。
PayOrderData data = new PayOrderData()
{
product_id = id,
body = "測試支付" + id,
attach = "愛奇迪技術(shù)支持",
detail = "測試JSAPI支付" + id,
total_fee = 1,
goods_tag = "test" + id,
trade_type = "JSAPI",
openid = openid
};
然后調(diào)用前面封裝過的統(tǒng)一下單接口API獲取對(duì)應(yīng)的統(tǒng)一下單ID
TenPayApi api = new TenPayApi(accountInfo);
var orderResult = api.UnifiedOrder(data);
LogHelper.Debug(string.Format("統(tǒng)一下單結(jié)果:{0}", (orderResult != null) ? orderResult.ToJson() : "為空值"));
if (string.IsNullOrEmpty(orderResult.prepay_id) || string.IsNullOrEmpty(orderResult.appid))
{
throw new WeixinException("統(tǒng)一下單結(jié)果返回失敗裆泳!");
}
signType固定為MD5,
最后剩下paySign這個(gè)比較復(fù)雜的參數(shù)了叹洲,這個(gè)參數(shù)就是需要根據(jù)前面這些參數(shù)進(jìn)行簽名的值。微信支付的簽名還是和普通API的做法(在前面介紹微信支付的時(shí)候工禾,有介紹過相關(guān)的規(guī)則运提,具體可以看看《C#開發(fā)微信門戶及應(yīng)用(32)--微信支付接入和API封裝使用》),引入實(shí)體類 **WxPayData **來存儲(chǔ)一些業(yè)務(wù)參數(shù)闻葵,以及實(shí)現(xiàn)參數(shù)的簽名處理民泵。
值得注意的是,使用普通API的簽名為Sign槽畔,而使用JSAPI的簽名變量名稱為paySign栈妆,兩者處理邏輯一樣,只是名稱不同厢钧。
這樣我們?cè)诤笈_(tái)處理相關(guān)的變量的代碼如下所示鳞尔。
/// <summary>
/// 獲取JSAPI方式的微信字符串參數(shù)對(duì)象
/// </summary>
/// <param name="accountInfo">當(dāng)前賬號(hào)</param>
/// <param name="prepay_id">統(tǒng)一下單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;
}
然后,再定義一個(gè)控制器接口早直,返回相關(guān)的參數(shù)數(shù)據(jù)寥假,部分邏輯代碼如下所示。這樣方便前端通過JSON的格式獲取對(duì)應(yīng)的變量值霞扬。
//支付需要的參數(shù)
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方式獲得發(fā)起微信支付的相關(guān)參數(shù)拾给,代碼如下所示。
//發(fā)起一個(gè)微信支付
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, // 支付簽名時(shí)間戳兔沃,注意微信jssdk中的所有使用timestamp字段均為小寫。但最新版的支付后臺(tái)生成簽名使用的timeStamp字段名需大寫其中的S字符
nonceStr: json.nonceStr, // 支付簽名隨機(jī)串级及,不長于 32 位
package: json.package, // 統(tǒng)一支付接口返回的prepay_id參數(shù)值乒疏,提交格式如:prepay_id=***)
signType: json.signType, // 簽名方式,默認(rèn)為'SHA1'饮焦,使用新版支付需傳入'MD5'
paySign: json.paySign, // 支付簽名
success: function (res) { // 支付成功后的回調(diào)函數(shù)
if (res.errMsg == 'chooseWXPay:ok') {
$.toast('支付成功');
//setTimeout(function () {
// window.location.href = "/";//這里默認(rèn)跳轉(zhuǎn)到主頁
//}, 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("調(diào)用支付出現(xiàn)異常");
//window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val();
})
},
error: function (xhr, status, error) {
$.toast("操作失敗" + xhr.responseText); //xhr.responseText
}
});
};
4怕吴、微信支付JSAPI發(fā)起微信支付的界面效果
通過上面的代碼,我們可以順利發(fā)起微信支付县踢,并可以看到具體的測試結(jié)果了转绷,讀者可以關(guān)注我們的公眾號(hào)【廣州愛奇迪】對(duì)其中微信支付進(jìn)行測試了解。
微信支付成功后硼啤,我們同樣可以在微信支付的對(duì)話里面看到響應(yīng)的結(jié)果了议经。
如果對(duì)這個(gè)《C#開發(fā)微信門戶及應(yīng)用》系列感興趣,可以關(guān)注我的其他文章.