C#开发微信门户及应用(40)--使用微信JSAPI实现微信支付功能

在我前面的几篇博客,有介绍了微信支付、微信红包、企业付款等各种和支付相关的操作,不过上面都是基于微信普通API的封装,本篇随笔继续微信支付这一主题,继续介绍基于微信网页JSAPI的方式发起的微信支付功能实现,微信的JSAPI相对于普通的API操作,调试没有那么方便,而且有时候有些错误需要反复核实。本篇随笔基于实际的微信网页支付案例,对微信JSAPI的支付实现进行介绍。

1、微信JS-SDK的知识

在我前面《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

2、微信支付JSAPI初始化的参数处理

要获取相关的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 + "&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, // 必填,公众号的唯一标识
    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'
    ]
});

3、微信支付JSAPI发起微信支付的参数处理

在第一小节里面,我提到了,初始化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
        }
    });
};

4、微信支付JSAPI发起微信支付的界面效果

通过上面的代码,我们可以顺利发起微信支付,并可以看到具体的测试结果了,读者可以关注我们的公众号【广州爱奇迪】对其中微信支付进行测试了解。




微信支付成功后,我们同样可以在微信支付的对话里面看到响应的结果了。

如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容