UNPKG

11.7 kBJavaScriptView Raw
1'use strict';
2
3var crypto = require('crypto');
4
5var util = require('./util');
6var wrapper = util.wrapper;
7
8// 错误码 - Ticket无效
9var INVALID_TICKET_CODE = -1;
10
11var Ticket = function (ticket, expireTime) {
12 if (!(this instanceof Ticket)) {
13 return new Ticket(ticket, expireTime);
14 }
15 this.ticket = ticket;
16 this.expireTime = expireTime;
17};
18
19Ticket.prototype.isValid = function () {
20 return !!this.ticket && (new Date().getTime()) < this.expireTime;
21};
22
23/**
24 * 多台服务器负载均衡时,ticketToken需要外部存储共享。
25 * 需要调用此registerTicketHandle来设置获取和保存的自定义方法。
26 *
27 * Examples:
28 * ```
29 * api.registerTicketHandle(getTicketToken, saveTicketToken);
30 * // getTicketToken
31 * function getTicketToken(type, callback) {
32 * settingModel.getItem(type, {key: 'weixin_ticketToken'}, function (err, setting) {
33 * if (err) return callback(err);
34 * callback(null, setting.value);
35 * });
36 * }
37 * // saveTicketToken
38 * function saveTicketToken(type, _ticketToken, callback) {
39 * settingModel.setItem(type, {key:'weixin_ticketToken', value: ticketToken}, function (err) {
40 * if (err) return callback(err);
41 * callback(null);
42 * });
43 * }
44 * ```
45 *
46 * @param {Function} getTicketToken 获取外部ticketToken的函数
47 * @param {Function} saveTicketToken 存储外部ticketToken的函数
48 */
49exports.registerTicketHandle = function (getTicketToken, saveTicketToken) {
50 if (!getTicketToken && !saveTicketToken) {
51 this.ticketStore = {};
52 }
53 this.getTicketToken = getTicketToken || function (type, callback) {
54 if (typeof type === 'function') {
55 callback = type;
56 type = 'jsapi';
57 }
58 callback(null, this.ticketStore[type]);
59 };
60
61 this.saveTicketToken = saveTicketToken || function (type, ticketToken, callback) {
62 // 向下兼容
63 if (typeof ticketToken === 'function') {
64 callback = ticketToken;
65 ticketToken = type;
66 type = 'jsapi';
67 }
68
69 this.ticketStore[type] = ticketToken;
70 if (process.env.NODE_ENV === 'production') {
71 console.warn('Dont save ticket in memory, when cluster or multi-computer!');
72 }
73 callback(null);
74 };
75};
76
77/**
78 * 获取js sdk所需的有效js ticket
79 *
80 * Callback:
81 * - `err`, 异常对象
82 * - `result`, 正常获取时的数据
83 *
84 * Result:
85 * - `errcode`, 0为成功
86 * - `errmsg`, 成功为'ok',错误则为详细错误信息
87 * - `ticket`, js sdk有效票据,如:bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA
88 * - `expires_in`, 有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket
89 *
90 * @param {Function} callback 回调函数
91 */
92exports.getTicket = function (type, callback) {
93 this.preRequest(this._getTicket, arguments);
94};
95
96exports._getTicket = function (type, callback) {
97 if (typeof type === 'function') {
98 callback = type;
99 type = 'jsapi';
100 }
101 var that = this;
102 var url = this.endpoint + '/cgi-bin/ticket/getticket?access_token=' + this.token.accessToken + '&type=' + type;
103 this.request(url, {dataType: 'json'}, wrapper(function(err, data) {
104 if (err) {
105 return callback(err);
106 }
107 // 过期时间,因网络延迟等,将实际过期时间提前10秒,以防止临界点
108 var expireTime = (new Date().getTime()) + (data.expires_in - 10) * 1000;
109 var ticket = new Ticket(data.ticket, expireTime);
110 that.saveTicketToken(type, ticket, function (err) {
111 if (err) {
112 return callback(err);
113 }
114 callback(err, ticket);
115 });
116 }));
117};
118
119/*!
120 * 生成随机字符串
121 */
122var createNonceStr = function () {
123 return Math.random().toString(36).substr(2, 15);
124};
125
126/*!
127 * 生成时间戳
128 */
129var createTimestamp = function () {
130 return parseInt(new Date().getTime() / 1000, 0) + '';
131};
132
133/*!
134 * 排序查询字符串
135 */
136var raw = function (args) {
137 var keys = Object.keys(args);
138 keys = keys.sort();
139 var newArgs = {};
140 keys.forEach(function (key) {
141 newArgs[key.toLowerCase()] = args[key];
142 });
143
144 var string = '';
145 var newKeys = Object.keys(newArgs);
146 for (var i = 0; i < newKeys.length; i++) {
147 var k = newKeys[i];
148 string += '&' + k + '=' + newArgs[k];
149 }
150 return string.substr(1);
151};
152
153/*!
154 * 签名算法
155 *
156 * @param {String} nonceStr 生成签名的随机串
157 * @param {String} jsapi_ticket 用于签名的jsapi_ticket
158 * @param {String} timestamp 时间戳
159 * @param {String} url 用于签名的url,注意必须与调用JSAPI时的页面URL完全一致
160 */
161var sign = function (nonceStr, jsapi_ticket, timestamp, url) {
162 var ret = {
163 jsapi_ticket: jsapi_ticket,
164 nonceStr: nonceStr,
165 timestamp: timestamp,
166 url: url
167 };
168 var string = raw(ret);
169 var shasum = crypto.createHash('sha1');
170 shasum.update(string);
171 return shasum.digest('hex');
172};
173
174/*!
175 * 卡券card_ext里的签名算法
176 *
177 * @name signCardExt
178 * @param {String} api_ticket 用于签名的临时票据,获取方式见2.获取api_ticket。
179 * @param {String} card_id 生成卡券时获得的card_id
180 * @param {String} timestamp 时间戳,商户生成从1970 年1 月1 日是微信卡券接口文档00:00:00 至今的秒数,即当前的时间,且最终需要转换为字符串形式;由商户生成后传入。
181 * @param {String} code 指定的卡券code 码,只能被领一次。use_custom_code 字段为true 的卡券必须填写,非自定义code 不必填写。
182 * @param {String} openid 指定领取者的openid,只有该用户能领取。bind_openid 字段为true 的卡券必须填写,非自定义code 不必填写。
183 * @param {String} balance 红包余额,以分为单位。红包类型(LUCKY_MONEY)必填、其他卡券类型不必填。
184 */
185var signCardExt = function(api_ticket, card_id, timestamp, code, openid, balance) {
186 var values = [api_ticket, card_id, timestamp, code || '', openid || '', balance || ''];
187 values.sort();
188
189 var string = values.join('');
190 var shasum = crypto.createHash('sha1');
191 shasum.update(string);
192 return shasum.digest('hex');
193};
194
195/*!
196 *
197 * 与api.preRequest相似,前置于需要js api ticket的方法
198 * @param {Function} method 需要封装的方法
199 * @param {Array} args 方法需要的参数
200 */
201var preRequestJSApi = function (method, args, retryed) {
202 var that = this;
203 var callback = args[args.length - 1];
204 // 调用用户传入的获取ticket的异步方法,获得ticket之后使用(并缓存它)。
205 that.getTicketToken('jsapi', function (err, cache) {
206 if (err) {
207 return callback(err);
208 }
209 var ticket;
210 // 有ticket并且ticket有效直接调用
211 if (cache && (ticket = new Ticket(cache.ticket, cache.expireTime)).isValid()) {
212 // 暂时保存ticket
213 that.jsTicket = ticket;
214 if (!retryed) {
215 var retryHandle = function (err, data, res) {
216 // 重试
217 if (data && data.errcode && data.errcode === INVALID_TICKET_CODE) {
218 return preRequestJSApi.call(that, method, args, true);
219 }
220 callback(err, data, res);
221 };
222 // 替换callback
223 var newargs = Array.prototype.slice.call(args, 0, -1);
224 newargs.push(retryHandle);
225 method.apply(that, newargs);
226 } else {
227 method.apply(that, args);
228 }
229 } else {
230 // 从微信端获取ticket
231 that.getTicket(function (err, ticket) {
232 // 如遇错误,通过回调函数传出
233 if (err) {
234 return callback(err);
235 }
236 // 暂时保存ticket
237 that.jsTicket = ticket;
238 method.apply(that, args);
239 });
240 }
241 });
242};
243
244/*!
245 *
246 * 与api.preRequest相似,前置于需要js wx_card ticket的方法
247 * @param {Function} method 需要封装的方法
248 * @param {Array} args 方法需要的参数
249 */
250var preRequestWxCardApi = function(method, args, retryed) {
251 var that = this;
252 var callback = args[args.length - 1];
253
254 that.getTicketToken('wx_card', function (err, cache) {
255 if (err) {
256 return callback(err);
257 }
258 var ticket;
259 // 有ticket并且ticket有效直接调用
260 if (cache && (ticket = new Ticket(cache.ticket, cache.expireTime)).isValid()) {
261 // 暂时保存ticket
262 that.wxCardTicket = ticket;
263 if (!retryed) {
264 var retryHandle = function (err, data, res) {
265 // 重试
266 if (data && data.errcode && data.errcode === INVALID_TICKET_CODE) {
267 return preRequestWxCardApi.call(that, method, args, true);
268 }
269 callback(err, data, res);
270 };
271 // 替换callback
272 var newargs = Array.prototype.slice.call(args, 0, -1);
273 newargs.push(retryHandle);
274 method.apply(that, newargs);
275 } else {
276 method.apply(that, args);
277 }
278 } else {
279 // 从微信端获取ticket
280 that.getTicket('wx_card', function (err, ticket) {
281 // 如遇错误,通过回调函数传出
282 if (err) {
283 return callback(err);
284 }
285 // 暂时保存ticket
286 that.wxCardTicket = ticket;
287 method.apply(that, args);
288 });
289 }
290 });
291};
292
293/**
294 * 获取微信JS SDK Config的所需参数
295 *
296 * Examples:
297 * ```
298 * var param = {
299 * debug: false,
300 * jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'],
301 * url: 'http://www.xxx.com'
302 * };
303 * api.getJsConfig(param, callback);
304 * ```
305 *
306 * Callback:
307 * - `err`, 调用失败时得到的异常
308 * - `result`, 调用正常时得到的js sdk config所需参数
309 *
310 * @param {Object} param 参数
311 * @param {Function} callback 回调函数
312 */
313exports.getJsConfig = function (param, callback) {
314 preRequestJSApi.call(this, this._getJsConfig, arguments);
315};
316exports._getJsConfig = function (param, callback) {
317 var that = this;
318 var nonceStr = createNonceStr();
319 var jsAPITicket = this.jsTicket.ticket;
320 var timestamp = createTimestamp();
321 var signature = sign(nonceStr, jsAPITicket, timestamp, param.url);
322 var result = {
323 debug: param.debug,
324 appId: that.appid,
325 timestamp: timestamp,
326 nonceStr: nonceStr,
327 signature: signature,
328 jsApiList: param.jsApiList
329 };
330
331 // 判断beta参数是否存在,微信硬件开发用
332 // beta: true
333 // 开启内测接口调用,注入wx.invoke方法
334 if (param.beta) {
335 result.beta = param.beta;
336 }
337 callback(null, result);
338};
339
340/**
341 * 获取微信JS SDK Config的所需参数
342 *
343 * Examples:
344 * ```
345 * var param = {
346 * card_id: 'p-hXXXXXXX',
347 * code: '1234',
348 * openid: '111111',
349 * balance: 100
350 * };
351 * api.getCardExt(param, callback);
352 * ```
353 *
354 * Callback:
355 * - `err`, 调用失败时得到的异常
356 * - `result`, 调用正常时得到的card_ext对象,包含所需参数
357 *
358 * @name getCardExt
359 * @param {Object} param 参数
360 * @param {Function} callback 回调函数
361 */
362exports.getCardExt = function (param, callback) {
363 preRequestWxCardApi.call(this, this._getCardExt, arguments);
364};
365
366exports._getCardExt = function (param, callback) {
367 var apiTicket = this.wxCardTicket.ticket;
368 var timestamp = createTimestamp();
369 var signature = signCardExt(apiTicket, param.card_id, timestamp, param.code, param.openid, param.balance);
370 var result = {
371 timestamp: timestamp,
372 signature: signature
373 };
374
375 result.code = param.code || '';
376 result.openid = param.openid || '';
377
378 if (param.balance) {
379 result.balance = param.balance;
380 }
381 callback(null, result);
382};
383
384/**
385 * 获取最新的js api ticket
386 *
387 * Examples:
388 * ```
389 * api.getLatestTicket(callback);
390 * ```
391 * Callback:
392 *
393 * - `err`, 获取js api ticket出现异常时的异常对象
394 * - `ticket`, 获取的ticket
395 *
396 * @param {Function} callback 回调函数
397 */
398exports.getLatestTicket = function (callback) {
399 preRequestJSApi.call(this, this._getLatestTicket, arguments);
400};
401exports._getLatestTicket = function (callback) {
402 callback(null, this.jsTicket);
403};