C#開發(fā)微信門戶及應(yīng)用(40)--使用微信JSAPI實(shí)現(xiàn)微信支付功能

在我前面的幾篇博客逊桦,有介紹了微信支付粹舵、微信紅包、企業(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 + "&timestamp=" + 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)注我的其他文章.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谴返,一起剝皮案震驚了整個(gè)濱河市煞肾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗓袱,老刑警劉巖籍救,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渠抹,居然都是意外死亡蝙昙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門梧却,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奇颠,“玉大人,你說我怎么就攤上這事篮幢〈罂” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵三椿,是天一觀的道長缺菌。 經(jīng)常有香客問我,道長搜锰,這世上最難降的妖魔是什么伴郁? 我笑而不...
    開封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮蛋叼,結(jié)果婚禮上焊傅,老公的妹妹穿的比我還像新娘剂陡。我一直安慰自己,他們只是感情好狐胎,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開白布鸭栖。 她就那樣靜靜地躺著,像睡著了一般握巢。 火紅的嫁衣襯著肌膚如雪晕鹊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天暴浦,我揣著相機(jī)與錄音溅话,去河邊找鬼。 笑死歌焦,一個(gè)胖子當(dāng)著我的面吹牛飞几,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播独撇,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼屑墨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了纷铣?” 一聲冷哼從身側(cè)響起绪钥,我...
    開封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎关炼,沒想到半個(gè)月后程腹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡儒拂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年寸潦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片社痛。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡见转,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒜哀,到底是詐尸還是另有隱情斩箫,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布撵儿,位于F島的核電站乘客,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淀歇。R本人自食惡果不足惜易核,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浪默。 院中可真熱鬧牡直,春花似錦缀匕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饵史,卻和暖如春劲件,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背约急。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苗分,地道東北人厌蔽。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像摔癣,于是被迫代替她去往敵國和親奴饮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容