[微信JS-SDK]微信公众号JS开发之卡券领取功能详解


微信团队在2015年初改革了微信JS的API,本文主要详细说明其中用到的卡券领取功能.


wechat_card_01.jpg

微信卡券需要认证过的公众号才能申请开通,而且创建的卡券也是要审核才能投放的.微信的卡券对于用户体验上来说比较好,以前促销活动的优惠券,会通过手机短信等方式发送给用户,现在有了"微信卡包"这个将卡券集中管理展示的功能,对于商家来说确实是能很好的拉动线下消费.


卡券管理入口在微信公众号管理后台的功能菜单里,本文先不提如何创建卡券,主要是讲述如何实现将已经生成好的卡券放到自己页面上让用户去领取.


首先要提到目前公众号开发中需要记住的3个重要的需要全局缓存的安全加密凭证:

第一个是:access_token 什么是access_token呢?看介绍.(转载请注明出处:猿资猿味)

1、为了保密appsecrect,第三方需要一个access_token获取和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则会造成access_token覆盖而影响业务;
2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器对外输出的依然是老access_token,此时公众平台后台会保证在刷新短时间内,新老access_token都可用,这保证了第三方业务的平滑过渡;
3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。

如果第三方不使用中控服务器,而是选择各个业务逻辑点各自去刷新access_token,那么就可能会产生冲突,导致服务不稳定。

接口调用请求说明

http请求方式: GEThttps://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

参数说明

参数是否必须说明
grant_type获取access_token填写client_credential
appid第三方用户唯一凭证
secret第三方用户唯一凭证密钥,即appsecret

返回说明

正常情况下,微信会返回下述JSON数据包给公众号:

{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数说明
access_token获取到的凭证
expires_in凭证有效时间,单位:秒


第二个是:jsapi_ticket 介绍如下.

jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket

  1. 参考上面介绍获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token

  2. 用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi


成功返回如下JSON:

{
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}

获得jsapi_ticket之后,就可以用来生成JS-SDK权限验证的签名了,也需要全局缓存下来。


第三个是:卡券 api_ticket 介绍如下.

卡券 api_ticket 是用于调用卡券相关接口的临时票据,有效期为 7200 秒,通过 access_token 来获取。这里要注意与 jsapi_ticket 区分开来。由于获取卡券 api_ticket 的 api 调用次数非常有限,频繁刷新卡券 api_ticket 会导致 api 调用受限,影响自身业务,开发者必须在自己的服务全局缓存卡券 api_ticket

  1. 参考上面介绍获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token

  2. 用第一步拿到的access_token 采用http GET方式请求获得卡券 api_ticket(有效期7200秒,开发者必须在自己的服务全局缓存卡券 api_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=wx_card


卡券扩展字段cardExt说明

cardExt本身是一个JSON字符串,是商户为该张卡券分配的唯一性信息,包含以下字段:


字段是否必填说明
code指定的卡券code码,只能被领一次。use_custom_code字段为true的卡券必须填写,非自定义code不必填写
openid指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非自定义openid不必填写
timestamp时间戳,商户生成从1970年1月1日00:00:00至今的秒数,即当前的时间,且最终需要转换为字符串形式;

由商户生成后传入。

signature签名,商户将接口列表中的参数按照指定方式进行签名,签名方式使用SHA1,具体签名方案参见下文;由商户按照规范签名后传入。
balance红包余额,以分为单位。红包类型必填(LUCKY_MONEY),其他卡券类型不填

在得到上面这3个凭证之后就可以开始接下来的第二步:网站引入微信的JS文件,注入config配置.这一步操作需要注意的是,网站的域名必须在微信公众号后台添加到了"设置"->"公众号设置"->"功能设置"->"JS接口安全域名"里.

wechat_card_02.jpg

好了,开始引入JS文件.(转载请注明出处:猿资猿味)

在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js

备注:支持使用 AMD/CMD 标准模块加载方法加载


通过config接口注入权限验证配置

所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。

wx.config({
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId: '', // 必填,公众号的唯一标识
    timestamp: , // 必填,生成签名的时间戳
    nonceStr: '', // 必填,生成签名的随机串
    signature: '',// 必填,签名,见附录1
    jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});

上面的jsApiList内填的是要使用的JS接口,我们是要让用户领取卡券,所以需要用到的JS接口方法:addCard

在html文件中加入以下javascript:

<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
		<script>
    			wx.config({
    			    debug: true,
    			    appId: "{$signature['appid']}",
    			    timestamp: {$signature['timestamp']},
    			    nonceStr: "{$signature['noncestr']}",
    			    signature: "{$signature['signature']}",
    			    jsApiList: [
    			    		'addCard'
    		    			]
    			});
			wx.ready(function(){
                                //添加卡券
				document.querySelector('#addCard').onclick = function () {
					wx.addCard({
					  cardList: [
					    {
					      cardId: "xxxxxxxxxxxxxxxxxxxxxx",
					      cardExt: '{"timestamp":"1426222398","signature":"fdd892770eb681e925f92acb9015c75107b2227a"}'
					    }
					  ],
					  success: function (res) {
					    alert('已添加卡券:' + JSON.stringify(res.cardList));
					  }
					});
				};
			});
	</script>

上面这段代码里重要的参数是:wx.config 这个配置要通过后台计算好后印射前端html里面才行,是动态的.签名算法需要用到的是上面介绍的jsapi_ticket,详细生成规则算法如下:

参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。

即signature=sha1(string1)。 

示例:

  • noncestr=Wm3WZYTPz0wzccnW

  • jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg

  • timestamp=1414587457

  • url=http://mp.weixin.qq.com?params=value

步骤1. 

对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

步骤2. 对string1进行sha1签名,得到signature:

0f9de62fce790f9a083d5c99e95740ceb90c27ed

注意事项

  1. 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。

  2. 签名用的url必须是调用JS接口页面的完整URL。

  3. 出于安全考虑,开发者必须在服务器端实现签名的逻辑


用php写一个满足此条件的签名函数:

function getJsSign($jsapi_ticket,$url, $timestamp=0, $noncestr=''){
	    if (!$timestamp)
	        $timestamp = time();
	    if (!$noncestr)
	        $noncestr = generateNonceStr();
	    $ret = strpos($url,'#');
	    if ($ret)
	        $url = substr($url,0,$ret);
	    $url = trim($url);
	    if (empty($url))
	        return false;
	    $arrdata = array("timestamp" => $timestamp, "noncestr" => $noncestr, "url" => $url, "jsapi_ticket" => $jsapi_ticket);
	    ksort($arrdata);
	    $paramstring = "";
	    foreach($arrdata as $key => $value){
	        if(strlen($paramstring) == 0)
	            $paramstring .= $key . "=" . $value;
	        else
	            $paramstring .= "&" . $key . "=" . $value;
	    }
	    $sign = sha1($paramstring);
	    if (!$sign)
	        return false;
	    return $sign;
}
function generateNonceStr($length=16){
    // 密码字符集,可任意添加你需要的字符
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    $str = "";
    for($i = 0; $i < $length; $i++)
    {
    $str .= $chars[mt_rand(0, strlen($chars) - 1)];
    }
    return $str;
}

用generateNonceStr函数生成随机字符串,再传入时间戳,当前页面的url,以及之前缓存好的jsapi_ticket这4个参数经过getJsSign函数处理即可得到相应的签名了,把这些参数映射给js的wx.config,就完成了.这里的签名如果不通过是没办法调用JS API的,调试前打开debug模式,确保弹出的信息是OK再进行.


JS的签名通过后,就可以调试卡券领取接口JS了.方法是addCard  需要注入到wx.ready(function(){})里面.

wx.addCard({
    cardList: [
                    {
		        cardId: "xxxxxxxxxxxxxxxxxxxxxx",
		        cardExt: '{"timestamp":"1426222398","signature":"fdd892770eb681e925f92acb9015c75107b2227a"}'
		     }
		    ],
	success: function (res) {
		      alert('已添加卡券:' + JSON.stringify(res.cardList));
		}
	});

addCard方法可以绑定到监听事件document.querySelector('#addCard').onclick上.cardId是卡券的ID,生成的时候能够得到,也可以在后台查看;cardExt的话要注意了,如果生成的卡券没有用自定义code,那么只需要timestamp和signature这两个字段就行了,但是如果生成的卡券是自定义code的,那么需要指定一个code给cardExt,否则在领取时按钮会显示"参数错误".这里的signature是卡券的签名,和上面提到的JS签名不一样的,此签名的计算方法说明如下:

  1. 将 api_ticket(特别说明:api_ticket 相较 appsecret 安全性更高,同时兼容老版本文档中使用的 appsecret 作为签名凭证。)、timestamp、card_id、code、openid、balance的value值进行字符串的字典序排序。

  2. 将所有参数字符串拼接成一个字符串进行sha1加密,得到signature。

  3. signature中的timestamp和card_ext中的timestamp必须保持一致。

  4. 假如数据示例中code=23456,timestamp=141231233,card_id=345667,api_ticket=45678则signature=sha1(14123123323456345667456789)=4F76593A4245644FAE4E1BC940F6422A0C3EC03E。

卡券签名cardSign说明

  1. 将 api_ticket(特别说明:api_ticket 相较 appsecret 安全性更高,同时兼容老版本文档中使用的 appsecret 作为签名凭证。)、app_id、location_id、times_tamp、nonce_str、card_id、card_type的value值进行字符串的字典序排序。

  2. 将所有参数字符串拼接成一个字符串进行sha1加密,得到cardSign。


用php写一个满足此条件的签名函数:

function getCardSign($card){
        sort($card,SORT_STRING);
        $sign = sha1(implode($card));
        if (!$sign)
            return false;
        return $sign;
}

$card是一个数组,里面必须包含时间戳,卡券 api_ticket,如果是自定义的code或者指定openid的用户才能领取,需要把这些额外参数也传到$card中,经过字典排序sha1加密后就能得到卡券签名了.


好了,上面就是如何在自己的页面上让用户在微信浏览器中领取已经申请通过的卡券,转载请注明出处:猿资猿味

上一篇 下一篇

评论



家和万事兴:您好!请问use_custom_code设置成true了但是还是微信的code,这是为什么?
08月14日 14:04
恩波:回复 家和万事兴 use_custom_code设为true了,那么你就要在用户领取卡券的页面上注入你自定义的code号码了呀,比如你要卡券号为"123456",那么js里面code就是"123456",这个code必须填
08月14日 16:51
八荒:您好,如果可以的话我可以问您些问题么,这个js sdk真是把我弄晕了,打扰您了。
08月15日 22:32
恩波:回复 八荒 有什么问题请留言
08月16日 08:59
八荒:回复 恩波 恩恩,就是我想调用wx.addcard方法,但是CardList里面的信息是填的官方文档里面的内容,为什么点击按钮后不能调用呢,就是一点反应都没有
08月16日 10:17
恩波:回复 八荒 你说的CardList里面的信息是什么信息?是示例的代码,还是已经经过签名算法算过的正确信息?
08月16日 10:59
恩波:回复 八荒 cardList里两个参数cardId,cardExt,cardId是你通过接口或者平台后台手动申请的卡券id,cardExt包含的信息我文章里详细介绍过了,有签名算法
08月16日 11:14
八荒:回复 恩波 恩恩,那个code和openid在哪里可以查到呢,卡券是别人创建的,我也不知道当时创建时填了哪些东西
08月16日 11:19
恩波:回复 八荒 code,openid怎么会查呢?你没搞懂啊,我要指定的openid(abcdefg)领取指定的卡号123456,那么我就把code设为123456,openid设为abcdefg啊,这个是指定某个用户才能领啊
08月16日 11:26
八荒:回复 恩波 哦哦,谢谢您啦,我再试试
08月16日 11:29
八荒:回复 恩波 您好。。能不能麻烦您加我qq一下,我还是得不到那个批量添加卡券的界面。。我的qq 1281065768 多谢啊
08月16日 13:33
yacms:您好!请问,用其他微信号授权的js安全域名,可以发放有卡券权限的公众号卡券吗?就是不是相同的微信号
10月21日 20:51
柠檬:你好,添加卡券显示签名错误,是有什么问题呢
10月30日 00:08
olivia:您好,debug返回 config:ok,但是调addCard 方法得不到那个添加卡券到卡包的界面,大神能指点一下吗?
12月23日 10:52
toy boy:回复 olivia 你好,问一下,怎么把会员卡添加到卡包呀?
09月13日 15:14
美人如玉剑如虹:你好,问下我点击领取卡券的时候,跳到添加卡券的页面 按钮上为什么显示是参数错误呢...
08月01日 17:46
dk_fitter:你好,請問一下 籤名中的URL是什麼地址?
08月03日 18:48
安静着安静:请问如果是创建特殊票券呢是怎么个流程呢。?
09月02日 11:19
 Dummer.  :写的很清楚 非常好!
09月05日 10:54
toy boy:回复  Dummer.  你好,我把那三个安全加密凭证得到后,怎么把会员卡添加到卡包呢????
09月13日 15:10
toy boy:大神,我把那三个安全加密凭证得到后,怎么把会员卡添加到卡包呢????
09月13日 14:56
强大大:请问能不能一次同时领取多张
10月14日 12:59
Cookie:你好,请问微信官方的php sample里,把access_token写到文件里,用了:$this->set_php_file("access_token.php", json_encode($data)); 这个可以写到文件里么? 代码截选至: private function getAccessToken() { // access_token 应该全局存储与更新,以下代码以写入到文件中做示例 $data = json_decode($this->get_php_file("access_token.php")); if ($data->expire_time < time()) { // 如果是企业号用以下URL获取access_token // $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$this->appId&corpsecret=$this->appSecret"; $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret"; $res = json_decode($this->httpGet($url)); $access_token = $res->access_token; if ($access_token) { $data->expire_time = time() + 7000; $data->access_token = $access_token; $this->set_php_file("access_token.php", json_encode($data)); } } else { $access_token = $data->access_token; } return $access_token; }
11月01日 22:50
emerson:那个卡券的加密是不是要异步的?如果不需要的话,是不是使用jq的点击触发函数里面将addcard的api放进去?然后是不是会自动跳转到领取卡券的页面上的
01月11日 21:30
因微不凡:我在添加卡券(addCard接口)的时候不传code就能添加成功,传了code就提示“签名错误” 请问通过什么方法可以知道use_custom_code为true还是false?可以通过接口或者其他形式更改use_custom_code的类型吗?
03月20日 11:17
所谓爱人:可不可以加个好友QQ1217994113
04月01日 18:47
roly:您好,请问怎么查询当前用户卡券是否领取状态?
05月12日 16:20
roly:另外添加卡券接口的参数cardId: "xxxxxxxxxxxxxxxxxxxxxx", cardExt: '{"timestamp":"1426222398","signature":"fdd892770eb681e925f92acb9015c75107b2227a"}' 是通过自己服务获取以上参数 还是用js在html5页面直接生产签名参数?
05月12日 16:41
lwj:你好,我刚看了你发的这个帖子,不知道现在评论是否能看到。我现在在做这个功能,可以用。但我这还有个需求就是,可以推送多张,我在cardList里,把需要推送的卡券,都添加上了,微信端页面也显示正确,有个领取按钮,但可以领取多次,每次卡包里多一张,而且这张是列表上的第一张 。。请问,你有没有遇到过 推送多张的情况
05月15日 14:29
恩波:回复 lwj 时隔2年多了,目前微信卡券估计已经变了好多了,不好意思啊
06月01日 15:33

分享

我的公众号

恩波的公众号

最新加入

最新评论

dreamer: 求邀请码1079623171@qq.com 查看原文 06月20日 09:03
恩波: 时隔2年多了,目前微信卡券估计已经变了好多了,不好意思啊 查看原文 06月01日 15:33
lwj: 你好,我刚看了你发的这个帖子,不知道现在评论是否能看到。我现在在做这个功能,可以用。但我这还有个需求就是,可以推送多张,我在cardList里,把需要推送的卡券,都添加上了,微信端页面也显示正确,有个领取按钮,但可以领取多次,每次卡包里多一张,而且这张是列表上的第一张 。。请问,你有没有遇到过 推送多张的情况 查看原文 05月15日 14:29
roly: 另外添加卡券接口的参数cardId: "xxxxxxxxxxxxxxxxxxxxxx", cardExt: '{"timestamp":"1426222398","signature":"fdd892770eb681e925f92acb9015c75107b2227a"}' 是通过自己服务获取以上参数 还是用js在html5页面直接生产签名参数? 查看原文 05月12日 16:41
roly: 您好,请问怎么查询当前用户卡券是否领取状态? 查看原文 05月12日 16:20

赞助商