UNPKG

social

Version:

Multi-platform Social Client for Node

356 lines (315 loc) 11 kB
var util = require('util'), crypto = require('crypto'), sign = require('./sign'), urlparse = require('url').parse, querystring = require('querystring'), SocialBase = require('./base'); var oauth_site = 'openapi.tencentyun.com' , debug_oauth_site = '119.147.19.43'; var QQ = module.exports = function (appinfo, data, debug) { SocialBase.call(this); this.platform = 'qq'; this.appid = appinfo.key; this.secret = appinfo.secret; this.appinfo = appinfo; this.is_debug = debug; this.data = data; } util.inherits(QQ, SocialBase); QQ.prototype.api = require('./apiconfig').QQAPI; QQ.prototype.get = function(uri, query, callback) { return this.request('GET', false, uri, query, callback); } QQ.prototype.post = function(uri, param, callback) { var isHttps = uri == this.api.payment_request || uri == this.api.confirm_payment; return this.request('POST', isHttps, uri, param, callback); } QQ.prototype.request = function(method, isHttps, uri, params, callback) { var src = method.toUpperCase(); src += '&' + sign.encodeData(uri); params.openid = this.data.snsId; params.openkey = this.data.sessionKey; params.pf = this.data.pf; params.appid = this.appid; params.format = "json"; if( params.sig ) delete params.sig; var basestring = sign.sortQueryToString(params, '&', false); src += '&' + sign.encodeData(basestring); var sig = crypto.createHmac('sha1', this.secret + '&').update(src).digest('base64'); params.sig = sig; var url = (isHttps?"https://":"http://") +(this.is_debug ? debug_oauth_site : oauth_site) +uri; this._performSecureRequest(method, url, params, null, function(err, data, response) { if(err) return callback(err); if(data) { var obj = JSON.parse(data); if(obj.ret == 0) { callback(null, obj); } else { callback(url + ' returns:\n' + data +'\n parms:'+ JSON.stringify(params)); } } else { callback(null, data); } }); } /** * Override FilterResponse */ QQ.prototype.filterResponse = function(direct, reqCallback, err, data) { if( data ){ if( data.ret != "0" || data.ret != 0 ){ err = { ret: data.ret, msg: data.msg }; }else{ if(data.ret !== undefined ) delete data.ret; if(data.is_lost !== undefined ) delete data.is_lost; } } SocialBase.prototype.filterResponse.call(this, direct, reqCallback, err, data ); }; QQ.getAppEntryInfo = function (req) { var query = req.query; var openid = query['openid'], openkey = query['openkey']; if(openid && openkey) { var info = {}; for( var key in query ){ info[key] = query[key]; } info.platform = 'qq'; info.snsId = openid; info.added = true; info.sessionKey = openkey; return info; } } QQ.prototype.geneCommonUser = function (data) { if( !data.items ){ data.id = this.data.snsId; return _formatUser( data ); }else{ var items = data.items.map( _formatUser ); return items; } } function _formatUser( data ){ if( data.openid != undefined ){ data.id = data.openid; delete data.openid; } data.gender = data.gender == '男'; if( data.figureurl != undefined ){ data.imageUrl = data.imageUrlMiddle = data.imageUrlLarge = data.figureurl; var img = data.figureurl; var i = img.lastIndexOf('/'); // data.imageUrl = img.substring(0, i) + '/30'; data.imageUrl = data.imageUrlMiddle = img; data.imageUrlLarge = img.substring(0, i) + '/100'; delete data.figureurl; } if( data.nickname != undefined ){ data.name = data.nickname; delete data.nickname; } if( data.is_yellow_vip != undefined ){ data.isSpecial = !!data.is_yellow_vip; delete data.is_yellow_vip; } if( data.yellow_vip_level != undefined ){ data.specialType = Number(data.yellow_vip_level); delete data.yellow_vip_level; } return data; } // node-social common API QQ.prototype.account_info = function (param, callback) { param = param || {}; param["flag"] = "0"; SocialBase.prototype.account_info.call(this, param, callback ); }; QQ.prototype.users_info = function( param, callback ) { if( !param["fopenids"] ) return callback( null, [] ); SocialBase.prototype.users_info.call( this, param, callback ); }; // lack of appfriends info api, use two step to get all friends info QQ.prototype.appfriends_info = function(param, callback) { param = param || {}; var self = this; SocialBase.prototype.appfriends_ids.call(this, param, function(err, data){ if(err) return callback(err); var items = data.items.map(function(e) {return e.openid}); param['fopenids'] = items.join("_"); self.users_info(param, callback); }); }; QQ.prototype.friends_ids = function(param, callback) { this.appfriends_ids(param, callback); }; QQ.prototype.appfriends_ids = function (param, callback) { SocialBase.prototype.appfriends_ids.call(this, param, function(err, data){ if(err) return callback(err); var items = data.items.map(function(e) {return e.openid}); callback(null, items); }); } // ========= Payment Request ============ // QQ.prototype.gettoken_request = function( param, callback ){ // setup param if( !param ||!param.discountid){ return callback( new Error('lack of parameters') ); } // must have param.ts = String(Date.now() / 1000); param.pfkey = this.data.pfkey; param.tokentype = param.tokentype || "1"; param.zoneid = param.zoneid || "0"; param.version = 'v3'; SocialBase.prototype.gettoken_request.call( this, param, callback ); } /** * Payment start api * @param {Object} param Request parameters ( refer to http://wiki.open.qq.com/wiki/v3/pay/buy_goods ) * must have: { payitem, goodsmeta, goodsurl } * optional : { amt, amttype, appmode, zoneid, manyouid, present, paymode, cee_extend } * @param {Function} callback */ QQ.prototype.payment_request = function(param, callback ) { // setup param if( !param ||!param.payitem ||!param.goodsmeta ||!param.goodsurl){ return callback( new Error('lack of parameters') ); } // must have param.ts = String(Date.now() / 1000); param.pfkey = this.data.pfkey; // optional param.appmode = param.appmode || "2"; param.zoneid = param.zoneid || "0"; param.present = param.present || "0"; SocialBase.prototype.payment_request.call( this, param, callback ); }; var reg = /[^0-9a-zA-Z\!\*\(\)]/g; /** * When payment completed you should call this function to confirm finish * @param {Object} req the req of express ( the req from platform send to you with all payment info) * @param {Object} res the res of express * @param {Function} onSuccess called when sig check passed, do some db change and callback to start confirm * eg: function( callback ) {} * @param {Function} onFail called when sig check failed, just log it and callback to start confirm * eg: function( callback ) {} * @param {Function} onDelivery (Optional)if provide snsData, you should give this onDelivery function, it will be called * when confirm_payment finished */ QQ.prototype.payment_callback = function( req, res, onSuccess, onFail, onDelivery ) { var query = req.query, isSuccess; // special query value encode for qq payment var reqsig = decodeURIComponent( query.sig ); var queryData = {}; for( var key in query ){ if( key !== 'sig' ) queryData[key] = query[key].replace( reg, _specialReplaceDate ); } var method = req.route.method.toUpperCase(); var uri = req.path; var src = method + '&' + sign.encodeData(uri); var basestring = sign.sortQueryToString(queryData, '&', false); src += '&' + sign.encodeData(basestring); var sig = crypto.createHmac('sha1', this.secret + '&').update(src).digest('base64'); // propearing response to platform and start confirm delivery var responseFunc = function( ret ) { var msg; switch( ret ){ case '0': msg = "OK"; break; case '1': msg = "Game Server Error"; break; case '4': msg = "Parameter Error: (sig)"; break; } // send response to platform res.json({ ret: ret, msg: msg }); // if provide sns data, confirm_payment request will be called automatically if( this.data && onDelivery != null ){ // setup confirm Param then send confirm var confirmParam = {}; // res parameters confirmParam.provide_errno = ret; confirmParam.provide_errmsg = msg; // url paramters confirmParam.payitem = query.payitem; confirmParam.token_id = query.token; confirmParam.billno = query.billno; confirmParam.version = query.version; confirmParam.zoneid = query.zoneid; confirmParam.providetype = query.providetype || "0"; // payment values confirmParam.amt = query.amt || "0"; // unit: 0.1 Q point confirmParam.payamt_coins = query.payamt_coins || "0"; confirmParam.pubacct_payamt_coins = query.pubacct_payamt_coins || "0"; // call confirm_payment this.confirm_payment( confirmParam, onDelivery ); } } var retno; if( reqsig === sig ){ // call onSuccess // call customized onSuccess function var self = this; onSuccess(function (err, data){ if( err ){ responseFunc.call(self, '1'); }else{ responseFunc.call(self, '0'); } }); }else{ // call customized onFail function onFail( responseFunc.bind(this, '4' )); } } /** * Special parameter replace for payment * @param {String} char * @return {String} replaced char */ function _specialReplaceDate( char ){ return "%"+ char.charCodeAt(0).toString(16).toUpperCase(); } /** * Invoked by payment_callback, or you can call it directly * @param {Object} param Request parameters ( refer to http://wiki.open.qq.com/wiki/v3/pay/payment_callback ) * @param {Function} callback */ QQ.prototype.confirm_payment = function( param, callback ) { // setup param if( !param ||!param.payitem ||!param.token_id ||!param.billno ||!param.provide_errno){ return callback( { ret: 4, msg:'lack of parameters' } ); } // must have param.ts = String(Date.now() / 1000); // optional param.zoneid = param.zoneid || "0"; param.amt = param.amt || "0"; param.payamt_coins = param.payamt_coins || "0"; param.pubacct_payamt_coins = param.pubacct_payamt_coins || "0"; var self = this; SocialBase.prototype.confirm_payment.call( this, param , function(err, data){ if( err ){ // special ret code for retry var ret = err.ret; if( ret == 1062 || ret == 1099 ){ // 1062 is too early, 1099 is busy // Confirm again... self.confirm_payment( param, callback ); return; } } callback( err, data ); }); };