├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── MIT-License ├── Makefile ├── README.md ├── figures ├── api.graffle ├── wechat.graffle └── wechat.png ├── index.js ├── lib ├── api_card.js ├── api_common.js ├── api_custom_service.js ├── api_datacube.js ├── api_device.js ├── api_feedback.js ├── api_group.js ├── api_ip.js ├── api_js.js ├── api_mass_send.js ├── api_material.js ├── api_media.js ├── api_menu.js ├── api_menu_custom.js ├── api_message.js ├── api_payment.js ├── api_poi.js ├── api_qrcode.js ├── api_quota.js ├── api_semantic.js ├── api_shakearound.js ├── api_shop_common.js ├── api_shop_express.js ├── api_shop_goods.js ├── api_shop_group.js ├── api_shop_order.js ├── api_shop_shelf.js ├── api_shop_stock.js ├── api_tag.js ├── api_template.js ├── api_url.js ├── api_user.js └── util.js ├── package.json └── test ├── api_card.test.js ├── api_common.test.js ├── api_custom_service.test.js ├── api_customer.test.js ├── api_feedback.test.js ├── api_group.test.js ├── api_ip.test.js ├── api_mass_send.test.js ├── api_media.test.js ├── api_menu.test.js ├── api_payment.test.js ├── api_poi.test.js ├── api_qrcode.test.js ├── api_semantic.test.js ├── api_shop_common.test.js ├── api_shop_express.test.js ├── api_shop_goods.test.js ├── api_shop_group.test.js ├── api_shop_order.test.js ├── api_shop_shelf.test.js ├── api_shop_stock.test.js ├── api_tag.test.js ├── api_template.test.js ├── api_url.test.js ├── api_user.test.js ├── config.js └── fixture ├── image.jpg ├── menu.json ├── movie.mp4 ├── pic.jpg └── test.mp3 /.eslintignore: -------------------------------------------------------------------------------- 1 | *.debug.js 2 | *.min.js 3 | node_modules/* 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 4 | 2, 5 | 2 6 | ], 7 | "quotes": [ 8 | 2, 9 | "single" 10 | ], 11 | "linebreak-style": [ 12 | 2, 13 | "unix" 14 | ], 15 | "semi": [2, "always"], 16 | "strict": [2, "global"], 17 | "curly": 2, 18 | "eqeqeq": 2, 19 | "no-eval": 2, 20 | "guard-for-in": 2, 21 | "no-caller": 2, 22 | "no-else-return": 2, 23 | "no-eq-null": 2, 24 | "no-extend-native": 2, 25 | "no-extra-bind": 2, 26 | "no-floating-decimal": 2, 27 | "no-implied-eval": 2, 28 | "no-labels": 2, 29 | "no-with": 2, 30 | "no-loop-func": 1, 31 | "no-native-reassign": 2, 32 | "no-redeclare": [2, {"builtinGlobals": true}], 33 | "no-delete-var": 2, 34 | "no-shadow-restricted-names": 2, 35 | "no-undef-init": 2, 36 | "no-use-before-define": 2, 37 | "no-unused-vars": [2, {"args": "none"}], 38 | "no-undef": 2, 39 | "callback-return": [2, ["callback", "cb", "next"]], 40 | "global-require": 0, 41 | "no-console": 0, 42 | "require-yield": 0 43 | }, 44 | "env": { 45 | "es6": true, 46 | "node": true, 47 | "browser": true 48 | }, 49 | "globals": { 50 | "describe": true, 51 | "it": true, 52 | "before": true, 53 | "after": true 54 | }, 55 | "extends": "eslint:recommended" 56 | } 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | example 3 | .DS_Store 4 | coverage 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "6" 5 | - "4" 6 | - "0.12" 7 | script: make test-coveralls 8 | -------------------------------------------------------------------------------- /MIT-License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Jackson Tian 2 | http://weibo.com/shyvo 3 | 4 | The MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.js 2 | REPORTER = spec 3 | TIMEOUT = 20000 4 | ISTANBUL = ./node_modules/.bin/istanbul 5 | MOCHA = ./node_modules/mocha/bin/_mocha 6 | COVERALLS = ./node_modules/coveralls/bin/coveralls.js 7 | 8 | all:test 9 | 10 | lint: 11 | @eslint --fix lib test index.js 12 | 13 | test: 14 | @NODE_ENV=test $(MOCHA) -R $(REPORTER) -t $(TIMEOUT) \ 15 | $(MOCHA_OPTS) \ 16 | $(TESTS) 17 | 18 | test-cov: 19 | @$(ISTANBUL) cover --report html $(MOCHA) -- -t $(TIMEOUT) -R spec $(TESTS) 20 | 21 | test-coveralls: 22 | @$(ISTANBUL) cover --report lcovonly $(MOCHA) -- -t $(TIMEOUT) -R spec $(TESTS) 23 | @echo TRAVIS_JOB_ID $(TRAVIS_JOB_ID) 24 | @cat ./coverage/lcov.info | $(COVERALLS) && rm -rf ./coverage 25 | 26 | test-all: test test-coveralls 27 | 28 | .PHONY: test 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wechat API 2 | =========== 3 | 微信公共平台API。 4 | 5 | ## 模块状态 6 | - [![NPM version](https://badge.fury.io/js/wechat-api.png)](http://badge.fury.io/js/wechat) 7 | - [![Build Status](https://travis-ci.org/node-webot/wechat-api.png?branch=master)](https://travis-ci.org/node-webot/wechat-api) 8 | - [![Dependencies Status](https://david-dm.org/node-webot/wechat-api.png)](https://david-dm.org/node-webot/wechat-api) 9 | - [![Coverage Status](https://coveralls.io/repos/node-webot/wechat-api/badge.png)](https://coveralls.io/r/node-webot/wechat-api) 10 | 11 | ## 功能列表 12 | - 发送客服消息(文本、图片、语音、视频、音乐、图文) 13 | - 菜单操作(查询、创建、删除) 14 | - 二维码(创建临时、永久二维码,查看二维码URL) 15 | - 分组操作(查询、创建、修改、移动用户到分组) 16 | - 用户信息(查询用户基本信息、获取关注者列表) 17 | - 媒体文件(上传、获取) 18 | - 群发消息(文本、图片、语音、视频、图文) 19 | - 客服记录(查询客服记录,查看客服、查看在线客服) 20 | - 群发消息 21 | - 公众号支付(发货通知、订单查询) 22 | - 微信小店(商品管理、库存管理、邮费模板管理、分组管理、货架管理、订单管理、功能接口) 23 | - 模版消息 24 | - 网址缩短 25 | - 语义查询 26 | - 数据分析 27 | - JSSDK服务端支持 28 | - 素材管理 29 | - 摇一摇周边 30 | 31 | 详细参见[API文档](http://doxmate.cool/node-webot/wechat-api/api.html) 32 | 33 | 企业版本请前往: 34 | 35 | ## Installation 36 | 37 | ```sh 38 | $ npm install wechat-api 39 | ``` 40 | 41 | ## Usage 42 | 43 | ```js 44 | var WechatAPI = require('wechat-api'); 45 | 46 | var api = new WechatAPI(appid, appsecret); 47 | api.updateRemark('open_id', 'remarked', function (err, data, res) { 48 | // TODO 49 | }); 50 | ``` 51 | 52 | ### patch()扩展 53 | 54 | 当微信官方文档已更新,但本库未来得及更新,而又想用新的微信 api 时,可调用 patch 方法来扩展新功能。 55 | ```js 56 | var WechatAPI = require('wechat-api'); 57 | var api = new WechatAPI(appid, appsecret); 58 | 59 | // 扩展新api : updateInfo 60 | // 第一个参数为扩展的新方法名,第二个参数为此 api 调用的微信的 apiurl 地址,会自动加上 token 61 | WechatAPI.patch("updateInfo", "https://api.weixin.qq.com/card/membercard/updateuser"); 62 | 63 | 64 | // 调用刚扩展的方法,与其它 api 接口方法一样。 65 | api.updateInfo(jsonInfo, function (err, data, res) { 66 | // TODO 67 | }); 68 | ``` 69 | #### 覆盖已有的接口 70 | 当要扩展的新接口名称已在 API 内定义,会抛出异常: 71 | `wechat-api already has a prototype named [uploadLogo]` 72 | 如果知道这个异常的含义且依然想定义这个接口名称,应该给第三个参数传入`true`: 73 | ```js 74 | WechatAPI.patch("uploadLogo", "https://api.weixin.qq.com/card/membercard/updateuser", true); 75 | ``` 76 | 执行后控制台会输出警告,并覆盖原来`uploadLogo`这个接口。 77 | 78 | > **提示** 当有新的微信接口开放或发现有用但 wechat-api 尚未加入开发的接口时,请在本库中相应的 js 文件中增加对新接口定义并提交 PullRequest 以便大家都能使用这个新接口。如果没有开发能力,请提交 issue. 79 | 80 | > **警告** 这个覆盖不只会覆盖 api 中的微信接口,所有定义给 api 的方法/成员变量 等都有可能被覆盖,请谨慎请用 81 | 82 | ### 多进程 83 | 当多进程时,token需要全局维护,以下为保存token的接口。 84 | ``` 85 | var WechatAPI = require('wechat-api'); 86 | var api = new WechatAPI('appid', 'secret', function (callback) { 87 | // 传入一个获取全局token的方法 88 | fs.readFile('access_token.txt', 'utf8', function (err, txt) { 89 | if (err) {return callback(err);} 90 | callback(null, JSON.parse(txt)); 91 | }); 92 | }, function (token, callback) { 93 | // 请将token存储到全局,跨进程、跨机器级别的全局,比如写到数据库、redis等 94 | // 这样才能在cluster模式及多机情况下使用,以下为写入到文件的示例 95 | fs.writeFile('access_token.txt', JSON.stringify(token), callback); 96 | }); 97 | ``` 98 | 99 | ## Show cases 100 | ### Node.js API自动回复 101 | 102 | ![Node.js API自动回复机器人](http://nodeapi.diveintonode.org/assets/qrcode.jpg) 103 | 104 | 欢迎关注。 105 | 106 | 代码: 107 | 108 | 你可以在[CloudFoundry](http://www.cloudfoundry.com/)、[appfog](https://www.appfog.com/)、[BAE](http://developer.baidu.com/wiki/index.php?title=docs/cplat/rt/node.js)等搭建自己的机器人。 109 | 110 | ## 详细API 111 | 原始API文档请参见:[消息接口指南](http://mp.weixin.qq.com/wiki/index.php?title=消息接口指南)。 112 | 113 | 114 | ## License 115 | The MIT license. 116 | 117 | ## 交流群 118 | QQ群:157964097,使用疑问,开发,贡献代码请加群。 119 | 120 | ## 感谢 121 | 感谢以下贡献者: 122 | 123 | ``` 124 | 125 | project : wechat-api 126 | repo age : 1 year, 9 months 127 | active : 97 days 128 | commits : 211 129 | files : 72 130 | authors : 131 | 136 Jackson Tian 64.5% 132 | 10 tedyyu 4.7% 133 | 9 greenkeeperio-bot 4.3% 134 | 7 Limjoe 3.3% 135 | 6 Guan Bo 2.8% 136 | 5 SunLn 2.4% 137 | 4 shuhankuang 1.9% 138 | 4 Lou Lin 1.9% 139 | 3 minxianlong 1.4% 140 | 2 Colt Xie 0.9% 141 | 2 xuming314 0.9% 142 | 1 brucewar 0.5% 143 | 1 dan 0.5% 144 | 1 ifeiteng 0.5% 145 | 1 kai_hao 0.5% 146 | 1 liuxiaodong 0.5% 147 | 1 looping 0.5% 148 | 1 simonyan 0.5% 149 | 1 wuxq 0.5% 150 | 1 wxhuang 0.5% 151 | 1 xumian.wei 0.5% 152 | 1 yelo 0.5% 153 | 1 BeeSui 0.5% 154 | 1 yuyuan 0.5% 155 | 1 Eric 0.5% 156 | 1 Ezios 0.5% 157 | 1 JaydanHuang 0.5% 158 | 1 Lin@Cloud+ 0.5% 159 | 1 Monson Shao 0.5% 160 | 1 Qun Lin 0.5% 161 | 1 Silver Lao 0.5% 162 | 1 Table 0.5% 163 | 1 Wang Yitong 0.5% 164 | 1 Will 0.5% 165 | 166 | ``` 167 | 168 | ## 捐赠 169 | 如果您觉得Wechat对您有帮助,欢迎请作者一杯咖啡 170 | 171 | ![捐赠wechat](https://cloud.githubusercontent.com/assets/327019/2941591/2b9e5e58-d9a7-11e3-9e80-c25aba0a48a1.png) 172 | 173 | 或者[![](http://img.shields.io/gratipay/JacksonTian.svg)](https://www.gittip.com/JacksonTian/) 174 | -------------------------------------------------------------------------------- /figures/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-webot/wechat-api/005bc10d6e8ade175df31ce3da131ffa8d669f5b/figures/wechat.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var API = require('./lib/api_common'); 4 | // 接口调用频次限制 5 | API.mixin(require('./lib/api_quota')); 6 | // 自定义菜单接口 7 | API.mixin(require('./lib/api_menu')); 8 | // 个性化菜单接口 9 | API.mixin(require('./lib/api_menu_custom')); 10 | // 分组管理 11 | API.mixin(require('./lib/api_group')); 12 | // 标签管理 13 | API.mixin(require('./lib/api_tag')); 14 | // 用户信息 15 | API.mixin(require('./lib/api_user')); 16 | // 二维码 17 | API.mixin(require('./lib/api_qrcode')); 18 | // 媒体管理(上传、下载) 19 | API.mixin(require('./lib/api_media')); 20 | // 永久素材管理接口 21 | API.mixin(require('./lib/api_material')); 22 | // 客服消息 23 | API.mixin(require('./lib/api_message')); 24 | // 模板消息 25 | API.mixin(require('./lib/api_template')); 26 | // 获取客服聊天记录 27 | API.mixin(require('./lib/api_custom_service')); 28 | // 高级群发接口 29 | API.mixin(require('./lib/api_mass_send')); 30 | // 微信小店商品管理接口 31 | API.mixin(require('./lib/api_shop_goods')); 32 | // 微信小店库存管理接口 33 | API.mixin(require('./lib/api_shop_stock')); 34 | // 微信小店邮费模版管理接口 35 | API.mixin(require('./lib/api_shop_express')); 36 | // 微信小店分组管理接口 37 | API.mixin(require('./lib/api_shop_group')); 38 | // 微信小店货架管理接口 39 | API.mixin(require('./lib/api_shop_shelf')); 40 | // 微信小店订单管理接口 41 | API.mixin(require('./lib/api_shop_order')); 42 | // 微信小店功能管理接口 43 | API.mixin(require('./lib/api_shop_common')); 44 | // 支付接口 45 | API.mixin(require('./lib/api_payment')); 46 | // 用户维权系统接口 47 | API.mixin(require('./lib/api_feedback')); 48 | // 短网址接口 49 | API.mixin(require('./lib/api_url')); 50 | // 语义查询接口 51 | API.mixin(require('./lib/api_semantic')); 52 | // 获取微信服务器IP地址 53 | API.mixin(require('./lib/api_ip')); 54 | // 图文消息数据分析接口 55 | API.mixin(require('./lib/api_datacube')); 56 | // js sdk接口 57 | API.mixin(require('./lib/api_js')); 58 | // 卡券接口 59 | API.mixin(require('./lib/api_card')); 60 | // 设备接口 61 | API.mixin(require('./lib/api_device')); 62 | // 摇一摇周边接口 63 | API.mixin(require('./lib/api_shakearound')); 64 | // 门店管理接口 65 | API.mixin(require('./lib/api_poi')); 66 | module.exports = API; 67 | -------------------------------------------------------------------------------- /lib/api_common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 本文件用于wechat API,基础文件,主要用于Token的处理和mixin机制 4 | var urllib = require('urllib'); 5 | var util = require('./util'); 6 | var extend = require('util')._extend; 7 | var wrapper = util.wrapper; 8 | 9 | var AccessToken = function (accessToken, expireTime) { 10 | if (!(this instanceof AccessToken)) { 11 | return new AccessToken(accessToken, expireTime); 12 | } 13 | this.accessToken = accessToken; 14 | this.expireTime = expireTime; 15 | }; 16 | 17 | /*! 18 | * 检查AccessToken是否有效,检查规则为当前时间和过期时间进行对比 19 | * 20 | * Examples: 21 | * ``` 22 | * token.isValid(); 23 | * ``` 24 | */ 25 | AccessToken.prototype.isValid = function () { 26 | return !!this.accessToken && (new Date().getTime()) < this.expireTime; 27 | }; 28 | 29 | /** 30 | * 根据appid和appsecret创建API的构造函数 31 | * 如需跨进程跨机器进行操作Wechat API(依赖access token),access token需要进行全局维护 32 | * 使用策略如下: 33 | * 34 | * 1. 调用用户传入的获取token的异步方法,获得token之后使用 35 | * 2. 使用appid/appsecret获取token。并调用用户传入的保存token方法保存 36 | * 37 | * Tips: 38 | * 39 | * - 如果跨机器运行wechat模块,需要注意同步机器之间的系统时间。 40 | * 41 | * Examples: 42 | * ``` 43 | * var API = require('wechat-api'); 44 | * var api = new API('appid', 'secret'); 45 | * ``` 46 | * 以上即可满足单进程使用。 47 | * 当多进程时,token需要全局维护,以下为保存token的接口。 48 | * ``` 49 | * var api = new API('appid', 'secret', function (callback) { 50 | * // 传入一个获取全局token的方法 51 | * fs.readFile('access_token.txt', 'utf8', function (err, txt) { 52 | * if (err) {return callback(err);} 53 | * callback(null, JSON.parse(txt)); 54 | * }); 55 | * }, function (token, callback) { 56 | * // 请将token存储到全局,跨进程、跨机器级别的全局,比如写到数据库、redis等 57 | * // 这样才能在cluster模式及多机情况下使用,以下为写入到文件的示例 58 | * fs.writeFile('access_token.txt', JSON.stringify(token), callback); 59 | * }); 60 | * ``` 61 | * @param {String} appid 在公众平台上申请得到的appid 62 | * @param {String} appsecret 在公众平台上申请得到的app secret 63 | * @param {Function} getToken 可选的。获取全局token对象的方法,多进程模式部署时需在意 64 | * @param {Function} saveToken 可选的。保存全局token对象的方法,多进程模式部署时需在意 65 | */ 66 | var API = function (appid, appsecret, getToken, saveToken) { 67 | this.appid = appid; 68 | this.appsecret = appsecret; 69 | this.getToken = getToken || function (callback) { 70 | callback(null, this.store); 71 | }; 72 | this.saveToken = saveToken || function (token, callback) { 73 | this.store = token; 74 | if (process.env.NODE_ENV === 'production') { 75 | console.warn('Don\'t save token in memory, when cluster or multi-computer!'); 76 | } 77 | callback(null); 78 | }; 79 | this.endpoint = 'https://api.weixin.qq.com'; 80 | this.mpPrefix = 'https://mp.weixin.qq.com/cgi-bin/'; 81 | this.fileServerPrefix = 'http://file.api.weixin.qq.com/cgi-bin/'; 82 | this.defaults = {}; 83 | // set default js ticket handle 84 | this.registerTicketHandle(); 85 | }; 86 | 87 | /** 88 | * 用于设置接入点 89 | * 90 | * - 通用域名(api.weixin.qq.com),使用该域名将访问官方指定就近的接入点; 91 | * - 上海域名(sh.api.weixin.qq.com),使用该域名将访问上海的接入点; 92 | * - 深圳域名(sz.api.weixin.qq.com),使用该域名将访问深圳的接入点; 93 | * - 香港域名(hk.api.weixin.qq.com),使用该域名将访问香港的接入点。 94 | * 95 | * Examples: 96 | * ``` 97 | * api.setEndpoint('api.weixin.qq.com'); 98 | * ``` 99 | * @param {String} domain 域名,默认为api.weixin.qq.com 100 | */ 101 | API.prototype.setEndpoint = function (domain) { 102 | this.endpoint = 'https://' + domain; 103 | }; 104 | 105 | /** 106 | * 用于设置urllib的默认options 107 | * 108 | * Examples: 109 | * ``` 110 | * api.setOpts({timeout: 15000}); 111 | * ``` 112 | * @param {Object} opts 默认选项 113 | */ 114 | API.prototype.setOpts = function (opts) { 115 | this.defaults = opts; 116 | }; 117 | 118 | /** 119 | * 设置urllib的hook 120 | * 121 | * Examples: 122 | * ``` 123 | * api.setHook(function (options) { 124 | * // options 125 | * }); 126 | * ``` 127 | * @param {Function} beforeRequest 需要封装的方法 128 | */ 129 | API.prototype.request = function (url, opts, callback) { 130 | var options = {}; 131 | extend(options, this.defaults); 132 | if (typeof opts === 'function') { 133 | callback = opts; 134 | opts = {}; 135 | } 136 | for (var key in opts) { 137 | if (key !== 'headers') { 138 | options[key] = opts[key]; 139 | } else { 140 | if (opts.headers) { 141 | options.headers = options.headers || {}; 142 | extend(options.headers, opts.headers); 143 | } 144 | } 145 | } 146 | urllib.request(url, options, callback); 147 | }; 148 | 149 | /*! 150 | * 根据创建API时传入的appid和appsecret获取access token 151 | * 进行后续所有API调用时,需要先获取access token 152 | * 详细请看: 153 | * 154 | * 应用开发者无需直接调用本API。 155 | * 156 | * Examples: 157 | * ``` 158 | * api.getAccessToken(callback); 159 | * ``` 160 | * Callback: 161 | * 162 | * - `err`, 获取access token出现异常时的异常对象 163 | * - `result`, 成功时得到的响应结果 164 | * 165 | * Result: 166 | * ``` 167 | * {"access_token": "ACCESS_TOKEN","expires_in": 7200} 168 | * ``` 169 | * @param {Function} callback 回调函数 170 | */ 171 | API.prototype.getAccessToken = function (callback) { 172 | var that = this; 173 | var url = this.endpoint + '/cgi-bin/token?grant_type=client_credential&appid=' + this.appid + '&secret=' + this.appsecret; 174 | this.request(url, {dataType: 'json'}, wrapper(function (err, data) { 175 | if (err) { 176 | return callback(err); 177 | } 178 | // 过期时间,因网络延迟等,将实际过期时间提前10秒,以防止临界点 179 | var expireTime = (new Date().getTime()) + (data.expires_in - 10) * 1000; 180 | var token = AccessToken(data.access_token, expireTime); 181 | that.saveToken(token, function (err) { 182 | if (err) { 183 | return callback(err); 184 | } 185 | callback(err, token); 186 | }); 187 | })); 188 | return this; 189 | }; 190 | 191 | /*! 192 | * 需要access token的接口调用如果采用preRequest进行封装后,就可以直接调用。 193 | * 无需依赖getAccessToken为前置调用。 194 | * 应用开发者无需直接调用此API。 195 | * 196 | * Examples: 197 | * ``` 198 | * api.preRequest(method, arguments); 199 | * ``` 200 | * @param {Function} method 需要封装的方法 201 | * @param {Array} args 方法需要的参数 202 | */ 203 | API.prototype.preRequest = function (method, args, retryed) { 204 | var that = this; 205 | var callback = args[args.length - 1]; 206 | // 调用用户传入的获取token的异步方法,获得token之后使用(并缓存它)。 207 | that.getToken(function (err, token) { 208 | if (err) { 209 | return callback(err); 210 | } 211 | var accessToken; 212 | // 有token并且token有效直接调用 213 | if (token && (accessToken = AccessToken(token.accessToken, token.expireTime)).isValid()) { 214 | // 暂时保存token 215 | that.token = accessToken; 216 | if (!retryed) { 217 | var retryHandle = function (err, data, res) { 218 | // 40001 重试 219 | if (data && data.errcode && data.errcode === 40001) { 220 | return that.preRequest(method, args, true); 221 | } 222 | callback(err, data, res); 223 | }; 224 | // 替换callback 225 | var newargs = Array.prototype.slice.call(args, 0, -1); 226 | newargs.push(retryHandle); 227 | method.apply(that, newargs); 228 | } else { 229 | method.apply(that, args); 230 | } 231 | } else { 232 | // 使用appid/appsecret获取token 233 | that.getAccessToken(function (err, token) { 234 | // 如遇错误,通过回调函数传出 235 | if (err) { 236 | return callback(err); 237 | } 238 | // 暂时保存token 239 | that.token = token; 240 | method.apply(that, args); 241 | }); 242 | } 243 | }); 244 | }; 245 | 246 | /** 247 | * 获取最新的token 248 | * 249 | * Examples: 250 | * ``` 251 | * api.getLatestToken(callback); 252 | * ``` 253 | * Callback: 254 | * 255 | * - `err`, 获取access token出现异常时的异常对象 256 | * - `token`, 获取的token 257 | * 258 | * @param {Function} method 需要封装的方法 259 | * @param {Array} args 方法需要的参数 260 | */ 261 | API.prototype.getLatestToken = function (callback) { 262 | var that = this; 263 | // 调用用户传入的获取token的异步方法,获得token之后使用(并缓存它)。 264 | that.getToken(function (err, token) { 265 | if (err) { 266 | return callback(err); 267 | } 268 | var accessToken; 269 | // 有token并且token有效直接调用 270 | if (token && (accessToken = AccessToken(token.accessToken, token.expireTime)).isValid()) { 271 | return callback(null, accessToken); 272 | } 273 | // 使用appid/appsecret获取token 274 | that.getAccessToken(callback); 275 | }); 276 | }; 277 | 278 | /** 279 | * 用于支持对象合并。将对象合并到API.prototype上,使得能够支持扩展 280 | * Examples: 281 | * ``` 282 | * // 媒体管理(上传、下载) 283 | * API.mixin(require('./lib/api_media')); 284 | * ``` 285 | * @param {Object} obj 要合并的对象 286 | */ 287 | API.mixin = function (obj) { 288 | for (var key in obj) { 289 | if (API.prototype.hasOwnProperty(key)) { 290 | throw new Error('Don\'t allow override existed prototype method. method: '+ key); 291 | } 292 | API.prototype[key] = obj[key]; 293 | } 294 | }; 295 | 296 | /** 297 | * 用于扩展API. 298 | * 作用是:当微信的官方 API 添加新功能,而wechat-api没有来得及升级时,用这个方法快速添加此功能。 299 | * 当 api 升级后应该用 API 内提供的方法。 300 | * Examples: 301 | * ``` 302 | * // 为 API 添加一个 createQr 方法。 303 | * API.ext("createQr", "https://api.weixin.qq.com/card/qrcode/create"); 304 | * ``` 305 | * Usage: 306 | * ``` 307 | * // 调用这个 createQr 方法。 308 | * api.createQr({'card_id': ’dkjeuDfsfeu3242w3dnjlq23i'}, callback); 309 | * ``` 310 | * @param {String} functionName 用户调用的方法名 311 | * @param {String} apiUrl 此 API 的 url 地址 312 | * @param {Bool} override 如果填写 true 则覆盖原来 api 已有的方法, false 或不传,则抛错。 313 | */ 314 | API.patch = function (functionName, apiUrl, override) { 315 | if (typeof apiUrl !== 'string') { 316 | throw new Error('The second argument expect a type of string as the request url of wechat'); 317 | } 318 | 319 | if (typeof functionName !== 'string') { 320 | throw new Error('The first argument expect a type of string as the name of new function'); 321 | } 322 | 323 | if (API.prototype[functionName] || API.prototype['_' + functionName] ) { 324 | if (override !== true) { 325 | throw new Error('wechat-api already has a prototype named ['+ functionName + '], use "true" as third param to override it or change your new function name.'); 326 | } else { 327 | console.warn('wechat-api already has a prototype named ['+ functionName + '], will override the orignal one.'); 328 | } 329 | } 330 | 331 | util.make(API.prototype, functionName, function (info, callback) { 332 | var hasMark = apiUrl.indexOf('?') >= 0; 333 | var url = apiUrl + (hasMark ? '&access_token=': '?access_token=') + this.token.accessToken; 334 | this.request(url, util.postJSON(info), wrapper(callback)); 335 | }); 336 | }; 337 | 338 | API.AccessToken = AccessToken; 339 | 340 | module.exports = API; 341 | -------------------------------------------------------------------------------- /lib/api_datacube.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | var methods = [ 8 | // 用户分析数据接口 9 | 'getUserSummary', // 获取用户增减数据 10 | 'getUserCumulate', // 获取累计用户数据 11 | // 图文分析数据接口 12 | 'getArticleSummary', // 获取图文群发每日数据 13 | 'getArticleTotal', // 获取图文群发总数据 14 | 'getUserRead', // 获取图文统计数据 15 | 'getUserReadHour', // 获取图文统计分时数据 16 | 'getUserShare', // 获取图文分享转发数据 17 | 'getUserShareHour', // 获取图文分享转发分时数据 18 | // 消息分析数据接口 19 | 'getUpstreamMsg', //获取消息发送概况数据 20 | 'getUpstreamMsgHour', // 获取消息分送分时数据 21 | 'getUpstreamMsgWeek', // 获取消息发送周数据 22 | 'getUpstreamMsgMonth', // 获取消息发送月数据 23 | 'getUpstreamMsgDist', // 获取消息发送分布数据 24 | 'getUpstreamMsgDistWeek', // 获取消息发送分布周数据 25 | 'getUpstreamMsgDistMonth', // 获取消息发送分布月数据 26 | // 接口分析数据接口 27 | 'getInterfaceSummary', // 获取接口分析数据 28 | 'getInterfaceSummaryHour' // 获取接口分析分时数据 29 | ]; 30 | 31 | /** 32 | * 公众平台官网数据统计模块 33 | * 详情请见: 34 | * Examples: 35 | * ``` 36 | * // 用户分析数据接口 37 | * api.getUserSummary(startDate, endDate, callback); // 获取用户增减数据 38 | * api.getUserCumulate(startDate, endDate, callback); // 获取累计用户数据 39 | * // 图文分析数据接口 40 | * api.getArticleSummary(startDate, endDate, callback); // 获取图文群发每日数据 41 | * api.getArticleTotal(startDate, endDate, callback); // 获取图文群发总数据 42 | * api.getUserRead(startDate, endDate, callback); // 获取图文统计数据 43 | * api.getUserReadHour(startDate, endDate, callback); // 获取图文统计分时数据 44 | * api.getUserShare(startDate, endDate, callback); // 获取图文分享转发数据 45 | * api.getUserShareHour(startDate, endDate, callback); // 获取图文分享转发分时数据 46 | * // 消息分析数据接口 47 | * api.getUpstreamMsg(startDate, endDate, callback); // 获取消息发送概况数据 48 | * api.getUpstreamMsgHour(startDate, endDate, callback); // 获取消息分送分时数据 49 | * api.getUpstreamMsgWeek(startDate, endDate, callback); // 获取消息发送周数据 50 | * api.getUpstreamMsgMonth(startDate, endDate, callback); // 获取消息发送月数据 51 | * api.getUpstreamMsgDist(startDate, endDate, callback); // 获取消息发送分布数据 52 | * api.getUpstreamMsgDistWeek(startDate, endDate, callback); // 获取消息发送分布周数据 53 | * api.getUpstreamMsgDistMonth(startDate, endDate, callback); // 获取消息发送分布月数据 54 | * // 接口分析数据接口 55 | * api.getInterfaceSummary(startDate, endDate, callback); // 获取接口分析数据 56 | * api.getInterfaceSummaryHour(startDate, endDate, callback); // 获取接口分析分时数据 57 | * ``` 58 | * Callback: 59 | * 60 | * - `err`, 调用失败时得到的异常 61 | * - `result`, 调用正常时得到的对象 62 | * 63 | * Result: 64 | * ``` 65 | * { 66 | * "list":[...] // 详细请参见 67 | * } 68 | * ``` 69 | * @param {String} startDate 起始日期,格式为2014-12-08 70 | * @param {String} endDate 结束日期,格式为2014-12-08 71 | * @param {Function} callback 回调函数 72 | */ 73 | methods.forEach(function (method) { 74 | exports[method] = function (begin, end, callback) { 75 | this.preRequest(this['_' + method], arguments); 76 | }; 77 | }); 78 | 79 | /*! 80 | * 获取分析数据的未封装版本 81 | */ 82 | methods.forEach(function (method) { 83 | exports['_' + method] = function (begin, end, callback) { 84 | var data = { 85 | begin_date: begin, 86 | end_date: end 87 | }; 88 | var url = this.endpoint + '/datacube/' + method.toLowerCase() + '?access_token=' + this.token.accessToken; 89 | this.request(url, postJSON(data), wrapper(callback)); 90 | }; 91 | }); 92 | -------------------------------------------------------------------------------- /lib/api_device.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | var make = util.make; 7 | 8 | make(exports, 'transferMessage', function (deviceType, deviceId, openid, content, callback) { 9 | // https://api.weixin.qq.com/device/transmsg?access_token=ACCESS_TOKEN 10 | var url = this.endpoint + '/device/transmsg?access_token=' + this.token.accessToken; 11 | var info = { 12 | 'device_type': deviceType, 13 | 'device_id': deviceId, 14 | 'open_id': openid, 15 | 'content': new Buffer(content).toString('base64') 16 | }; 17 | this.request(url, postJSON(info), wrapper(callback)); 18 | }); 19 | 20 | make(exports, 'transferStatus', function (deviceType, deviceId, openid, status, callback) { 21 | // https://api.weixin.qq.com/device/transmsg?access_token=ACCESS_TOKEN 22 | var url = this.endpoint + '/device/transmsg?access_token=' + this.token.accessToken; 23 | var info = { 24 | 'device_type': deviceType, 25 | 'device_id': deviceId, 26 | 'open_id': openid, 27 | 'msg_type': '2', 28 | 'device_status': status 29 | }; 30 | this.request(url, postJSON(info), wrapper(callback)); 31 | }); 32 | 33 | make(exports, 'createDeviceQRCode', function (deviceIds, callback) { 34 | // https://api.weixin.qq.com/device/create_qrcode?access_token=ACCESS_TOKEN 35 | var url = this.endpoint + '/device/create_qrcode?access_token=' + this.token.accessToken; 36 | var info = { 37 | 'device_num': deviceIds.length, 38 | 'device_id_list': deviceIds 39 | }; 40 | this.request(url, postJSON(info), wrapper(callback)); 41 | }); 42 | 43 | make(exports, 'authorizeDevices', function (devices, optype, productid, callback) { 44 | // https://api.weixin.qq.com/device/authorize_device?access_token=ACCESS_TOKEN 45 | var url = this.endpoint + '/device/authorize_device?access_token=' + this.token.accessToken; 46 | var data = { 47 | 'device_num': devices.length, 48 | 'device_list': devices, 49 | 'op_type': optype 50 | }; 51 | if (typeof productid !== 'function') { 52 | data.product_id = productid; 53 | } else { 54 | callback = productid; 55 | } 56 | this.request(url, postJSON(data), wrapper(callback)); 57 | }); 58 | 59 | //第三方公众账号通过设备id从公众平台批量获取设备二维码。 60 | make(exports, 'getDeviceQRCode', function (product_id, callback) { 61 | // https://api.weixin.qq.com/device/create_qrcode?access_token=ACCESS_TOKEN 62 | var url = this.endpoint + '/device/getqrcode?access_token=' + this.token.accessToken + '&product_id=' + product_id; 63 | this.request(url, {dataType: 'json'}, wrapper(callback)); 64 | }); 65 | 66 | make(exports, 'bindDevice', function (deviceId, openid, ticket, callback) { 67 | // https://api.weixin.qq.com/device/bind?access_token=ACCESS_TOKEN 68 | var url = this.endpoint + '/device/bind?access_token=' + this.token.accessToken; 69 | var data = { 70 | ticket: ticket, 71 | device_id: deviceId, 72 | openid: openid 73 | }; 74 | this.request(url, postJSON(data), wrapper(callback)); 75 | }); 76 | 77 | make(exports, 'unbindDevice', function (deviceId, openid, ticket, callback) { 78 | // https://api.weixin.qq.com/device/unbind?access_token=ACCESS_TOKEN 79 | var url = this.endpoint + '/device/unbind?access_token=' + this.token.accessToken; 80 | var data = { 81 | ticket: ticket, 82 | device_id: deviceId, 83 | openid: openid 84 | }; 85 | this.request(url, postJSON(data), wrapper(callback)); 86 | }); 87 | 88 | 89 | make(exports, 'compelBindDevice', function (deviceId, openid, callback) { 90 | // https://api.weixin.qq.com/device/compel_bind?access_token=ACCESS_TOKEN 91 | var url = this.endpoint + '/device/compel_bind?access_token=' + this.token.accessToken; 92 | var data = { 93 | device_id: deviceId, 94 | openid: openid 95 | }; 96 | this.request(url, postJSON(data), wrapper(callback)); 97 | }); 98 | 99 | make(exports, 'compelUnbindDevice', function (deviceId, openid, callback) { 100 | // https://api.weixin.qq.com/device/compel_unbind?access_token=ACCESS_TOKEN 101 | var url = this.endpoint + '/device/compel_unbind?access_token=' + this.token.accessToken; 102 | var data = { 103 | device_id: deviceId, 104 | openid: openid 105 | }; 106 | this.request(url, postJSON(data), wrapper(callback)); 107 | }); 108 | 109 | make(exports, 'getDeviceStatus', function (deviceId, callback) { 110 | // https://api.weixin.qq.com/device/get_stat?access_token=ACCESS_TOKEN&device_id=DEVICE_ID 111 | var url = this.endpoint + '/device/get_stat?access_token=' + this.token.accessToken + '&device_id=' + deviceId; 112 | this.request(url, {dataType: 'json'}, wrapper(callback)); 113 | }); 114 | 115 | make(exports, 'verifyDeviceQRCode', function (ticket, callback) { 116 | // https://api.weixin.qq.com/device/verify_qrcode?access_token=ACCESS_TOKEN 117 | var url = this.endpoint + '/device/verify_qrcode?access_token=' + this.token.accessToken; 118 | var data = { 119 | ticket: ticket 120 | }; 121 | this.request(url, postJSON(data), wrapper(callback)); 122 | }); 123 | 124 | make(exports, 'getOpenID', function (deviceId, deviceType, callback) { 125 | // https://api.weixin.qq.com/device/get_openid?access_token=ACCESS_TOKEN&device_type=DEVICE_TYPE&device_id=DEVICE_ID 126 | var url = this.endpoint + '/device/get_openid?access_token=' + this.token.accessToken + '&device_id=' + deviceId + '&device_type=' + deviceType; 127 | this.request(url, {dataType: 'json'}, wrapper(callback)); 128 | }); 129 | 130 | make(exports, 'getBindDevice', function (openid, callback) { 131 | // https://api.weixin.qq.com/device/get_bind_device?access_token=ACCESS_TOKEN&openid=OPENID 132 | var url = this.endpoint + '/device/get_bind_device?access_token=' + this.token.accessToken + '&openid=' + openid; 133 | this.request(url, {dataType: 'json'}, wrapper(callback)); 134 | }); 135 | -------------------------------------------------------------------------------- /lib/api_feedback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | 6 | /** 7 | * 标记客户的投诉处理状态 8 | * Examples: 9 | * ``` 10 | * api.updateFeedback(openid, feedbackId, callback); 11 | * ``` 12 | * Callback: 13 | * 14 | * - `err`, 调用失败时得到的异常 15 | * - `result`, 调用正常时得到的对象 16 | * 17 | * Result: 18 | * ``` 19 | * { 20 | * "errcode": 0, 21 | * "errmsg": "success" 22 | * } 23 | * ``` 24 | * @param {String} openid 用户ID 25 | * @param {String} feedbackId 投诉ID 26 | * @param {Function} callback 回调函数 27 | */ 28 | exports.updateFeedback = function (openid, feedbackId, callback) { 29 | this.preRequest(this._updateFeedback, arguments); 30 | }; 31 | 32 | exports._updateFeedback = function (openid, feedbackId, callback) { 33 | var feedbackUrl = this.endpoint + '/payfeedback/update'; 34 | // https://api.weixin.qq.com/payfeedback/update?access_token=xxxxx&openid=XXXX&feedbackid=xxxx 35 | var data = { 36 | 'access_token': this.token.accessToken, 37 | 'openid': openid, 38 | 'feedbackid': feedbackId 39 | }; 40 | var opts = { 41 | dataType: 'json', 42 | type: 'GET', 43 | data: data, 44 | headers: { 45 | 'Content-Type': 'application/json' 46 | } 47 | }; 48 | this.request(feedbackUrl, opts, wrapper(callback)); 49 | }; 50 | -------------------------------------------------------------------------------- /lib/api_group.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | var make = util.make; 7 | 8 | /** 9 | * 获取分组列表 10 | * 详情请见: 11 | * Examples: 12 | * ``` 13 | * api.getGroups(callback); 14 | * ``` 15 | * Callback: 16 | * 17 | * - `err`, 调用失败时得到的异常 18 | * - `result`, 调用正常时得到的对象 19 | * 20 | * Result: 21 | * ``` 22 | * { 23 | * "groups": [ 24 | * {"id": 0, "name": "未分组", "count": 72596}, 25 | * {"id": 1, "name": "黑名单", "count": 36} 26 | * ] 27 | * } 28 | * ``` 29 | * @param {Function} callback 回调函数 30 | */ 31 | make(exports, 'getGroups', function (callback) { 32 | // https://api.weixin.qq.com/cgi-bin/groups/get?access_token=ACCESS_TOKEN 33 | var url = this.endpoint + '/cgi-bin/groups/get?access_token=' + this.token.accessToken; 34 | this.request(url, {dataType: 'json'}, wrapper(callback)); 35 | }); 36 | 37 | /** 38 | * 查询用户在哪个分组 39 | * 详情请见: 40 | * Examples: 41 | * ``` 42 | * api.getWhichGroup(openid, callback); 43 | * ``` 44 | * Callback: 45 | * 46 | * - `err`, 调用失败时得到的异常 47 | * - `result`, 调用正常时得到的对象 48 | * 49 | * Result: 50 | * ``` 51 | * { 52 | * "groupid": 102 53 | * } 54 | * ``` 55 | * @param {String} openid Open ID 56 | * @param {Function} callback 回调函数 57 | */ 58 | make(exports, 'getWhichGroup', function (openid, callback) { 59 | // https://api.weixin.qq.com/cgi-bin/groups/getid?access_token=ACCESS_TOKEN 60 | var url = this.endpoint + '/cgi-bin/groups/getid?access_token=' + this.token.accessToken; 61 | var data = { 62 | 'openid': openid 63 | }; 64 | this.request(url, postJSON(data), wrapper(callback)); 65 | }); 66 | 67 | /** 68 | * 创建分组 69 | * 详情请见: 70 | * Examples: 71 | * ``` 72 | * api.createGroup('groupname', callback); 73 | * ``` 74 | * Callback: 75 | * 76 | * - `err`, 调用失败时得到的异常 77 | * - `result`, 调用正常时得到的对象 78 | * 79 | * Result: 80 | * ``` 81 | * {"group": {"id": 107, "name": "test"}} 82 | * ``` 83 | * @param {String} name 分组名字 84 | * @param {Function} callback 回调函数 85 | */ 86 | make(exports, 'createGroup', function (name, callback) { 87 | // https://api.weixin.qq.com/cgi-bin/groups/create?access_token=ACCESS_TOKEN 88 | // POST数据格式:json 89 | // POST数据例子:{"group":{"name":"test"}} 90 | var url = this.endpoint + '/cgi-bin/groups/create?access_token=' + this.token.accessToken; 91 | var data = { 92 | 'group': {'name': name} 93 | }; 94 | this.request(url, postJSON(data), wrapper(callback)); 95 | }); 96 | 97 | /** 98 | * 更新分组名字 99 | * 详情请见: 100 | * Examples: 101 | * ``` 102 | * api.updateGroup(107, 'new groupname', callback); 103 | * ``` 104 | * Callback: 105 | * 106 | * - `err`, 调用失败时得到的异常 107 | * - `result`, 调用正常时得到的对象 108 | * 109 | * Result: 110 | * ``` 111 | * {"errcode": 0, "errmsg": "ok"} 112 | * ``` 113 | * @param {Number} id 分组ID 114 | * @param {String} name 新的分组名字 115 | * @param {Function} callback 回调函数 116 | */ 117 | make(exports, 'updateGroup', function (id, name, callback) { 118 | // http请求方式: POST(请使用https协议) 119 | // https://api.weixin.qq.com/cgi-bin/groups/update?access_token=ACCESS_TOKEN 120 | // POST数据格式:json 121 | // POST数据例子:{"group":{"id":108,"name":"test2_modify2"}} 122 | var url = this.endpoint + '/cgi-bin/groups/update?access_token=' + this.token.accessToken; 123 | var data = { 124 | 'group': {'id': id, 'name': name} 125 | }; 126 | this.request(url, postJSON(data), wrapper(callback)); 127 | }); 128 | 129 | /** 130 | * 移动用户进分组 131 | * 详情请见: 132 | * Examples: 133 | * ``` 134 | * api.moveUserToGroup(openid, groupId, callback); 135 | * ``` 136 | * Callback: 137 | * 138 | * - `err`, 调用失败时得到的异常 139 | * - `result`, 调用正常时得到的对象 140 | * 141 | * Result: 142 | * ``` 143 | * {"errcode": 0, "errmsg": "ok"} 144 | * ``` 145 | * @param {String} openid 用户的openid 146 | * @param {Number} groupId 分组ID 147 | * @param {Function} callback 回调函数 148 | */ 149 | make(exports, 'moveUserToGroup', function (openid, groupId, callback) { 150 | // http请求方式: POST(请使用https协议) 151 | // https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=ACCESS_TOKEN 152 | // POST数据格式:json 153 | // POST数据例子:{"openid":"oDF3iYx0ro3_7jD4HFRDfrjdCM58","to_groupid":108} 154 | var url = this.endpoint + '/cgi-bin/groups/members/update?access_token=' + this.token.accessToken; 155 | var data = { 156 | 'openid': openid, 157 | 'to_groupid': groupId 158 | }; 159 | this.request(url, postJSON(data), wrapper(callback)); 160 | }); 161 | 162 | /** 163 | * 删除分组 164 | * 详情请见: 165 | * Examples: 166 | * ``` 167 | * api.removeGroup(groupId, callback); 168 | * ``` 169 | * Callback: 170 | * 171 | * - `err`, 调用失败时得到的异常 172 | * - `result`, 调用正常时得到的对象 173 | * 174 | * Result: 175 | * ``` 176 | * {"errcode": 0, "errmsg": "ok"} 177 | * ``` 178 | * @param {Number} groupId 分组ID 179 | * @param {Function} callback 回调函数 180 | */ 181 | make(exports, 'removeGroup', function (groupId, callback) { 182 | var url = this.endpoint + '/cgi-bin/groups/delete?access_token=' + this.token.accessToken; 183 | var data = { 184 | 'group': { id: groupId} 185 | }; 186 | this.request(url, postJSON(data), wrapper(callback)); 187 | }); 188 | -------------------------------------------------------------------------------- /lib/api_ip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var make = util.make; 6 | 7 | /** 8 | * 获取微信服务器IP地址 9 | * 详情请见: 10 | * Examples: 11 | * ``` 12 | * api.getIp(callback); 13 | * ``` 14 | * Callback: 15 | * 16 | * - `err`, 调用失败时得到的异常 17 | * - `result`, 调用正常时得到的对象 18 | * 19 | * Result: 20 | * ``` 21 | * { 22 | * "ip_list":["127.0.0.1","127.0.0.1"] 23 | * } 24 | * ``` 25 | * @param {Function} callback 回调函数 26 | */ 27 | make(exports, 'getIp', function (callback) { 28 | // https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN 29 | var url = this.endpoint + '/cgi-bin/getcallbackip?access_token=' + this.token.accessToken; 30 | this.request(url, {dataType: 'json'}, wrapper(callback)); 31 | }); 32 | -------------------------------------------------------------------------------- /lib/api_media.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | 6 | var formstream = require('formstream'); 7 | 8 | var util = require('./util'); 9 | var wrapper = util.wrapper; 10 | 11 | /** 12 | * 新增临时素材,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb) 13 | * 详情请见: 14 | * Examples: 15 | * ``` 16 | * api.uploadMedia('filepath', type, callback); 17 | * ``` 18 | * Callback: 19 | * 20 | * - `err`, 调用失败时得到的异常 21 | * - `result`, 调用正常时得到的对象 22 | * 23 | * Result: 24 | * ``` 25 | * {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789} 26 | * ``` 27 | * Shortcut: 28 | * 29 | * - `exports.uploadImage(filepath, callback);` 30 | * - `exports.uploadVoice(filepath, callback);` 31 | * - `exports.uploadVideo(filepath, callback);` 32 | * - `exports.uploadThumb(filepath, callback);` 33 | * 34 | * @param {String} filepath 文件路径 35 | * @param {String} type 媒体类型,可用值有image、voice、video、thumb 36 | * @param {Function} callback 回调函数 37 | */ 38 | exports.uploadMedia = function (filepath, type, callback) { 39 | this.preRequest(this._uploadMedia, arguments); 40 | }; 41 | 42 | /*! 43 | * 上传多媒体文件的未封装版本 44 | */ 45 | exports._uploadMedia = function (filepath, type, callback) { 46 | var that = this; 47 | fs.stat(filepath, function (err, stat) { 48 | if (err) { 49 | return callback(err); 50 | } 51 | var form = formstream(); 52 | form.file('media', filepath, path.basename(filepath), stat.size); 53 | var url = that.endpoint + '/cgi-bin/media/upload?access_token=' + that.token.accessToken + '&type=' + type; 54 | var opts = { 55 | dataType: 'json', 56 | type: 'POST', 57 | timeout: 60000, // 60秒超时 58 | headers: form.headers(), 59 | stream: form 60 | }; 61 | that.request(url, opts, wrapper(callback)); 62 | }); 63 | }; 64 | 65 | /** 66 | * 流式新增临时素材,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb) 67 | * 拓展自uploadMedia,实现上游上传的流数据重定向到微信服务器,省去自己服务器的文件缓存。 68 | * Examples: 69 | * ``` 70 | * api.uploadMediaStream(req, type, callback); 71 | * ``` 72 | * Callback: 73 | * 74 | * - `err`, 调用失败时得到的异常 75 | * - `result`, 调用正常时得到的对象 76 | * 77 | * Result: 78 | * ``` 79 | * {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789} 80 | * ``` 81 | * Shortcut: 82 | * 83 | * - `exports.uploadImageStream(req, callback);` 84 | * - `exports.uploadVoiceStream(req, callback);` 85 | * - `exports.uploadVideoStream(req, callback);` 86 | * - `exports.uploadThumbStream(req, callback);` 87 | * 88 | * @param {String} req 上游Stream对象,必须包含headers属性;例如expressjs中request对象。 89 | * @param {String} type 媒体类型,可用值有image、voice、video、thumb 90 | * @param {Function} callback 回调函数 91 | */ 92 | exports.uploadMediaStream = function (req, type, callback) { 93 | this.preRequest(this._uploadMediaStream, arguments); 94 | }; 95 | 96 | /*! 97 | * 流式上传多媒体文件的未封装版本 98 | */ 99 | exports._uploadMediaStream = function (req, type, callback) { 100 | var that = this; 101 | var url = that.endpoint + '/cgi-bin/media/upload?access_token=' + that.token.accessToken + '&type=' + type; 102 | var opts = { 103 | dataType: 'json', 104 | type: 'POST', 105 | timeout: 60000, // 60秒超时 106 | headers: req.headers, 107 | stream: req 108 | }; 109 | delete opts.headers.host; 110 | that.request(url, opts, callback); 111 | }; 112 | 113 | ['image', 'voice', 'video', 'thumb'].forEach(function (type) { 114 | var method = 'upload' + type[0].toUpperCase() + type.substring(1); 115 | exports[method] = function (filepath, callback) { 116 | this.uploadMedia(filepath, type, callback); 117 | }; 118 | exports[method+'Stream'] = function (req, callback) { 119 | this.uploadMediaStream(req, type, callback); 120 | }; 121 | }); 122 | 123 | /** 124 | * 获取临时素材 125 | * 详情请见: 126 | * Examples: 127 | * ``` 128 | * api.getMedia('media_id', callback); 129 | * ``` 130 | * Callback: 131 | * 132 | * - `err`, 调用失败时得到的异常 133 | * - `result`, 调用正常时得到的文件Buffer对象 134 | * - `res`, HTTP响应对象 135 | * 136 | * @param {String} mediaId 媒体文件的ID 137 | * @param {Function} callback 回调函数 138 | */ 139 | exports.getMedia = function (mediaId, callback) { 140 | this.preRequest(this._getMedia, arguments); 141 | }; 142 | 143 | /*! 144 | * 获取临时素材的未封装版本 145 | */ 146 | exports._getMedia = function (mediaId, callback) { 147 | var url = this.endpoint + '/cgi-bin/media/get?access_token=' + this.token.accessToken + '&media_id=' + mediaId; 148 | var opts = { 149 | timeout: 60000 // 60秒超时 150 | }; 151 | this.request(url, opts, wrapper(function (err, data, res) { 152 | // handle some err 153 | if (err) { 154 | return callback(err); 155 | } 156 | var contentType = res.headers['content-type']; 157 | if (contentType === 'application/json' || contentType === 'text/plain') { 158 | var ret; 159 | try { 160 | ret = JSON.parse(data); 161 | if (ret.errcode) { 162 | err = new Error(ret.errmsg); 163 | err.name = 'WeChatAPIError'; 164 | } 165 | } catch (ex) { 166 | callback(ex, data, res); 167 | return; 168 | } 169 | return callback(err, ret, res); 170 | } 171 | // 输出Buffer对象 172 | callback(null, data, res); 173 | })); 174 | }; 175 | 176 | 177 | /** 178 | * 上传图文消息内的图片获取URL 179 | * 详情请见: 180 | * Examples: 181 | * ``` 182 | * api.uploadImage('filepath'); 183 | * ``` 184 | * Callback: 185 | * 186 | * - `err`, 调用失败时得到的异常 187 | * - `result`, 调用正常时得到的对象 188 | * 189 | * Result: 190 | * ``` 191 | * {"url": "http://mmbiz.qpic.cn/mmbiz/gLO17UPS6FS2xsypf378iaNhWacZ1G1UplZYWEYfwvuU6Ont96b1roYsCNFwaRrSaKTPCUdBK9DgEHicsKwWCBRQ/0"} 192 | * ``` 193 | * 194 | * @param {String} filepath 图片文件路径 195 | * @param {Function} callback 回调函数 196 | */ 197 | exports.uploadImage = function (filepath, callback) { 198 | this.preRequest(this._uploadImage, arguments); 199 | }; 200 | 201 | /*! 202 | * 上传图片未封装版本 203 | */ 204 | exports._uploadImage = function (filepath, callback) { 205 | var that = this; 206 | fs.stat(filepath, function (err, stat) { 207 | if (err) { 208 | return callback(err); 209 | } 210 | var form = formstream(); 211 | form.file('media', filepath, path.basename(filepath), stat.size); 212 | var url = that.endpoint + '/cgi-bin/media/uploadimg?access_token=' + that.token.accessToken; 213 | var opts = { 214 | dataType: 'json', 215 | type: 'POST', 216 | timeout: 60000, // 60秒超时 217 | headers: form.headers(), 218 | stream: form 219 | }; 220 | that.request(url, opts, wrapper(callback)); 221 | }); 222 | }; 223 | 224 | /** 225 | * 上传来自上游管道的图文消息内的图片,并获取URL。 226 | * 拓展于uploadImage,用于客户端直接上传文件管道重定向到微信服务器,不经过自身缓存服务器文件。 227 | * Examples: 228 | * ``` 229 | * api.uploadImageStream(req, callback); 230 | * ``` 231 | * Callback: 232 | * 233 | * - `err`, 调用失败时得到的异常 234 | * - `result`, 调用正常时得到的对象 235 | * 236 | * Result: 237 | * ``` 238 | * {"url": "http://mmbiz.qpic.cn/mmbiz/gLO17UPS6FS2xsypf378iaNhWacZ1G1UplZYWEYfwvuU6Ont96b1roYsCNFwaRrSaKTPCUdBK9DgEHicsKwWCBRQ/0"} 239 | * ``` 240 | * 241 | * @param {Object} req 上游Stream对象,必须包含headers属性;例如expressjs中request对象。 242 | * @param {Function} callback 回调函数 243 | */ 244 | exports.uploadImageStream = function (req, callback) { 245 | this.preRequest(this._uploadImageStream, arguments); 246 | }; 247 | 248 | /*! 249 | * 上传来自上游管道的图文消息内的图片未封装版本 250 | */ 251 | exports._uploadImageStream = function (req, callback) { 252 | var that = this; 253 | var url = that.endpoint + '/cgi-bin/media/uploadimg?access_token=' + that.token.accessToken; 254 | var opts = { 255 | dataType: 'json', 256 | type: 'POST', 257 | timeout: 60000, // 60秒超时 258 | headers: req.headers, 259 | stream: req 260 | }; 261 | delete opts.headers.host; 262 | that.request(url, opts, callback); 263 | }; 264 | -------------------------------------------------------------------------------- /lib/api_menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 创建自定义菜单 9 | * 详细请看:http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html 10 | * 11 | * Menu: 12 | * ``` 13 | * { 14 | * "button":[ 15 | * { 16 | * "type":"click", 17 | * "name":"今日歌曲", 18 | * "key":"V1001_TODAY_MUSIC" 19 | * }, 20 | * { 21 | * "name":"菜单", 22 | * "sub_button":[ 23 | * { 24 | * "type":"view", 25 | * "name":"搜索", 26 | * "url":"http://www.soso.com/" 27 | * }, 28 | * { 29 | * "type":"click", 30 | * "name":"赞一下我们", 31 | * "key":"V1001_GOOD" 32 | * }] 33 | * }] 34 | * } 35 | * ] 36 | * } 37 | * ``` 38 | * Examples: 39 | * ``` 40 | * api.createMenu(menu, callback); 41 | * ``` 42 | * Callback: 43 | * 44 | * - `err`, 调用失败时得到的异常 45 | * - `result`, 调用正常时得到的对象 46 | * 47 | * Result: 48 | * ``` 49 | * {"errcode":0,"errmsg":"ok"} 50 | * ``` 51 | * @param {Object} menu 菜单对象 52 | * @param {Function} callback 回调函数 53 | */ 54 | exports.createMenu = function (menu, callback) { 55 | this.preRequest(this._createMenu, arguments); 56 | }; 57 | 58 | /*! 59 | * 创建自定义菜单的未封装版本 60 | */ 61 | exports._createMenu = function (menu, callback) { 62 | var url = this.endpoint + '/cgi-bin/menu/create?access_token=' + this.token.accessToken; 63 | this.request(url, postJSON(menu), wrapper(callback)); 64 | }; 65 | 66 | /** 67 | * 获取菜单 68 | * 详细请看: 69 | * 70 | * Examples: 71 | * ``` 72 | * api.getMenu(callback); 73 | * ``` 74 | * Callback: 75 | * 76 | * - `err`, 调用失败时得到的异常 77 | * - `result`, 调用正常时得到的对象 78 | * 79 | * Result: (注意:如果有个性化菜单被设置,返回的结果会具有更多信息,请参考微信文档) 80 | * ``` 81 | * // 结果示例 82 | * { 83 | * "menu": { 84 | * "button":[ 85 | * {"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":[]}, 86 | * {"type":"click","name":"歌手简介","key":"V1001_TODAY_SINGER","sub_button":[]}, 87 | * {"name":"菜单","sub_button":[ 88 | * {"type":"view","name":"搜索","url":"http://www.soso.com/","sub_button":[]}, 89 | * {"type":"view","name":"视频","url":"http://v.qq.com/","sub_button":[]}, 90 | * {"type":"click","name":"赞一下我们","key":"V1001_GOOD","sub_button":[]}] 91 | * } 92 | * ] 93 | * } 94 | * } 95 | * ``` 96 | * @param {Function} callback 回调函数 97 | */ 98 | exports.getMenu = function (callback) { 99 | this.preRequest(this._getMenu, arguments); 100 | }; 101 | 102 | /*! 103 | * 获取自定义菜单的未封装版本 104 | */ 105 | exports._getMenu = function (callback) { 106 | var url = this.endpoint + '/cgi-bin/menu/get?access_token=' + this.token.accessToken; 107 | this.request(url, {dataType: 'json'}, wrapper(callback)); 108 | }; 109 | 110 | /** 111 | * 删除自定义菜单 112 | * 详细请看: 113 | * Examples: 114 | * ``` 115 | * api.removeMenu(callback); 116 | * ``` 117 | * Callback: 118 | * 119 | * - `err`, 调用失败时得到的异常 120 | * - `result`, 调用正常时得到的对象 121 | * 122 | * Result: 123 | * ``` 124 | * {"errcode":0,"errmsg":"ok"} 125 | * ``` 126 | * @param {Function} callback 回调函数 127 | */ 128 | exports.removeMenu = function (callback) { 129 | this.preRequest(this._removeMenu, arguments); 130 | }; 131 | 132 | /*! 133 | * 删除自定义菜单的未封装版本 134 | */ 135 | exports._removeMenu = function (callback) { 136 | var url = this.endpoint + '/cgi-bin/menu/delete?access_token=' + this.token.accessToken; 137 | this.request(url, {dataType: 'json'}, wrapper(callback)); 138 | }; 139 | 140 | 141 | /** 142 | * 获取自定义菜单配置 143 | * 详细请看: 144 | * Examples: 145 | * ``` 146 | * api.getMenuConfig(callback); 147 | * ``` 148 | * Callback: 149 | * 150 | * - `err`, 调用失败时得到的异常 151 | * - `result`, 调用正常时得到的对象 152 | * 153 | * Result: 154 | * ``` 155 | * {"errcode":0,"errmsg":"ok"} 156 | * ``` 157 | * @param {Function} callback 回调函数 158 | */ 159 | exports.getMenuConfig = function (callback) { 160 | this.preRequest(this._getMenuConfig, arguments); 161 | }; 162 | 163 | /*! 164 | * 获取自定义菜单配置的未封装版本 165 | */ 166 | exports._getMenuConfig = function (callback) { 167 | var url = this.endpoint + '/cgi-bin/get_current_selfmenu_info?access_token=' + this.token.accessToken; 168 | this.request(url, {dataType: 'json'}, wrapper(callback)); 169 | }; 170 | -------------------------------------------------------------------------------- /lib/api_menu_custom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 创建个性化菜单 9 | * 详细请看: 10 | * 11 | * Menu: 12 | * ``` 13 | * { 14 | * "button":[ 15 | * { 16 | * "type":"click", 17 | * "name":"今日歌曲", 18 | * "key":"V1001_TODAY_MUSIC" 19 | * }, 20 | * { 21 | * "name":"菜单", 22 | * "sub_button":[ 23 | * { 24 | * "type":"view", 25 | * "name":"搜索", 26 | * "url":"http://www.soso.com/" 27 | * }, 28 | * { 29 | * "type":"click", 30 | * "name":"赞一下我们", 31 | * "key":"V1001_GOOD" 32 | * }] 33 | * }] 34 | * } 35 | * ], 36 | * "matchrule":{ 37 | * "group_id":"2", 38 | * "sex":"1", // 男1,女2 39 | * "country":"中国", 40 | * "province":"广东", 41 | * "city":"广州", 42 | * "client_platform_type":"2" // IOS(1), Android(2),Others(3) 43 | * } 44 | * } 45 | * ``` 46 | * Examples: 47 | * ``` 48 | * api.createCustomMenu(menu, callback); 49 | * ``` 50 | * Callback: 51 | * 52 | * - `err`, 调用失败时得到的异常 53 | * - `result`, 调用正常时得到的对象 54 | * 55 | * Result: 56 | * ``` 57 | * { menuid: 401550287 } 58 | * 59 | * ``` 60 | * @param {Object} menu 菜单对象 61 | * @param {Function} callback 回调函数 62 | */ 63 | exports.createCustomMenu = function (menu, callback) { 64 | this.preRequest(this._createCustomMenu, arguments); 65 | }; 66 | 67 | /*! 68 | * 创建个性化菜单的未封装版本 69 | */ 70 | exports._createCustomMenu = function (menu, callback) { 71 | var url = this.endpoint + '/cgi-bin/menu/addconditional?access_token=' + this.token.accessToken; 72 | this.request(url, postJSON(menu), wrapper(callback)); 73 | }; 74 | 75 | 76 | /** 77 | * 删除个性化菜单 78 | * 详细请看: 79 | * 80 | * Examples: 81 | * ``` 82 | * api.removeCustomMenu(menu_id,callback); 83 | * ``` 84 | * Callback: 85 | * 86 | * - `err`, 调用失败时得到的异常 87 | * - `result`, 调用正常时得到的对象 88 | * 89 | * Result: 90 | * ``` 91 | * {"errcode":0,"errmsg":"ok"} 92 | * ``` 93 | * 94 | * @param {String} menu_id, 可以通过自定义菜单查询接口获取 95 | * @param {Function} callback 回调函数 96 | */ 97 | exports.removeCustomMenu = function (menu_id,callback) { 98 | this.preRequest(this._removeCustomMenu, arguments); 99 | }; 100 | 101 | /*! 102 | * 删除个性化菜单的未封装版本 103 | */ 104 | exports._removeCustomMenu = function (menu_id,callback) { 105 | var url = this.endpoint + '/cgi-bin/menu/delconditional?access_token=' + this.token.accessToken; 106 | this.request(url, postJSON({ 107 | 'menuid' : menu_id 108 | }), wrapper(callback)); 109 | }; 110 | 111 | 112 | 113 | /** 114 | * 测试个性化菜单 115 | * 详细请看: 116 | * 117 | * Examples: 118 | * ``` 119 | * api.testCustomMenu(user_id,callback); 120 | * ``` 121 | * Callback: 122 | * 123 | * - `err`, 调用失败时得到的异常 124 | * - `result`, 调用正常时得到的对象 125 | * 126 | * Result: 127 | * ``` 128 | * { 129 | * "button": [ 130 | * { 131 | * "type": "view", 132 | * "name": "tx", 133 | * "url": "http://www.qq.com/", 134 | * "sub_button": [ ] 135 | * }, 136 | * { 137 | * "type": "view", 138 | * "name": "tx", 139 | * "url": "http://www.qq.com/", 140 | * "sub_button": [ ] 141 | * }, 142 | * { 143 | * "type": "view", 144 | * "name": "tx", 145 | * "url": "http://www.qq.com/", 146 | * "sub_button": [ ] 147 | * } 148 | * ] 149 | * } 150 | * ``` 151 | * 152 | * @param {String} user_id, 可以是粉丝的OpenID,也可以是粉丝的微信号。 153 | * @param {Function} callback 回调函数 154 | */ 155 | exports.testCustomMenu = function (user_id, callback) { 156 | this.preRequest(this._testCustomMenu, arguments); 157 | }; 158 | 159 | 160 | /*! 161 | * 测试个性化菜单的未封装版本 162 | */ 163 | exports._testCustomMenu = function (user_id,callback) { 164 | var url = this.endpoint + '/cgi-bin/menu/trymatch?access_token=' + this.token.accessToken; 165 | this.request(url, postJSON({ 166 | 'user_id' : user_id 167 | }), wrapper(callback)); 168 | }; 169 | -------------------------------------------------------------------------------- /lib/api_payment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 微信公众号支付: 发货通知 9 | * 详情请见: 接口文档订单发货通知 10 | * 11 | * Data: 12 | * ``` 13 | * { 14 | * "appid" : "wwwwb4f85f3a797777", 15 | * "openid" : "oX99MDgNcgwnz3zFN3DNmo8uwa-w", 16 | * "transid" : "111112222233333", 17 | * "out_trade_no" : "555666uuu", 18 | * "deliver_timestamp" : "1369745073", 19 | * "deliver_status" : "1", 20 | * "deliver_msg" : "ok", 21 | * "app_signature" : "53cca9d47b883bd4a5c85a9300df3da0cb48565c", 22 | * "sign_method" : "sha1" 23 | * } 24 | * ``` 25 | * Examples: 26 | * ``` 27 | * api.deliverNotify(data, callback); 28 | * ``` 29 | * Callback: 30 | * 31 | * - `err`, 调用失败时得到的异常 32 | * - `result`, 调用正常时得到的对象 33 | * 34 | * Result: 35 | * ``` 36 | * {"errcode":0, "errmsg":"ok"} 37 | * ``` 38 | * 39 | * @param {Object} package package对象 40 | * @param {Function} callback 回调函数 41 | */ 42 | exports.deliverNotify = function (data, callback) { 43 | this.preRequest(this._deliverNotify, arguments); 44 | }; 45 | 46 | /*! 47 | * 发货通知的未封装版本 48 | */ 49 | exports._deliverNotify = function (data, callback) { 50 | var url = this.endpoint + '/pay/delivernotify?access_token=' + this.token.accessToken; 51 | this.request(url, postJSON(data), wrapper(callback)); 52 | }; 53 | 54 | /** 55 | * 微信公众号支付: 订单查询 56 | * 详情请见: 接口文档订单查询部分 57 | * 58 | * Package: 59 | * ``` 60 | * { 61 | * "appid" : "wwwwb4f85f3a797777", 62 | * "package" : "out_trade_no=11122&partner=1900090055&sign=4e8d0df3da0c3d0df38f", 63 | * "timestamp" : "1369745073", 64 | * "app_signature" : "53cca9d47b883bd4a5c85a9300df3da0cb48565c", 65 | * "sign_method" : "sha1" 66 | * } 67 | * ``` 68 | * Examples: 69 | * ``` 70 | * api.orderQuery(query, callback); 71 | * ``` 72 | * Callback: 73 | * 74 | * - `err`, 调用失败时得到的异常 75 | * - `result`, 调用正常时得到的对象 76 | * 77 | * Result: 78 | * ``` 79 | * { 80 | * "errcode":0, 81 | * "errmsg":"ok", 82 | * "order_info": { 83 | * "ret_code":0, 84 | * "ret_msg":"", 85 | * "input_charset":"GBK", 86 | * "trade_state":"0", 87 | * "trade_mode":"1", 88 | * "partner":"1900000109", 89 | * "bank_type":"CMB_FP", 90 | * "bank_billno":"207029722724", 91 | * "total_fee":"1", 92 | * "fee_type":"1", 93 | * "transaction_id":"1900000109201307020305773741", 94 | * "out_trade_no":"2986872580246457300", 95 | * "is_split":"false", 96 | * "is_refund":"false", 97 | * "attach":"", 98 | * "time_end":"20130702175943", 99 | * "transport_fee":"0", 100 | * "product_fee":"1", 101 | * "discount":"0", 102 | * "rmb_total_fee":"" 103 | * } 104 | * } 105 | * ``` 106 | * 107 | * @param {Object} query query对象 108 | * @param {Function} callback 回调函数 109 | */ 110 | exports.orderQuery = function (query, callback) { 111 | this.preRequest(this._orderQuery, arguments); 112 | }; 113 | 114 | /*! 115 | * 发货通知的未封装版本 116 | */ 117 | exports._orderQuery = function (query, callback) { 118 | var url = this.endpoint + '/pay/orderquery?access_token=' + this.token.accessToken; 119 | this.request(url, postJSON(query), wrapper(callback)); 120 | }; 121 | -------------------------------------------------------------------------------- /lib/api_poi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 微信门店接口文档请参考:http://mp.weixin.qq.com/wiki/16/8f182af4d8dcea02c56506306bdb2f4c.html 4 | var util = require('./util'); 5 | var wrapper = util.wrapper; 6 | var postJSON = util.postJSON; 7 | var make = util.make; 8 | 9 | /** 10 | * 创建门店 11 | * 12 | * Tips: 13 | * - 创建门店接口调用成功后不会实时返回poi_id。 14 | * - 成功创建后,门店信息会经过审核,审核通过后方可使用并获取poi_id。 15 | * - 图片photo_url必须为上传图片接口(api.uploadLogo,参见卡券接口)生成的url。 16 | * - 门店类目categories请参考微信公众号后台的门店管理部分。 17 | * 18 | * Poi: 19 | * ``` 20 | * { 21 | * "sid": "5794560", 22 | * "business_name": "肯打鸡", 23 | * "branch_name": "东方路店", 24 | * "province": "上海市", 25 | * "city": "上海市", 26 | * "district": "浦东新区", 27 | * "address": "东方路88号", 28 | * "telephone": "021-5794560", 29 | * "categories": ["美食,快餐小吃"], 30 | * "offset_type": 1, 31 | * "longitude": 125.5794560, 32 | * "latitude": 45.5794560, 33 | * "photo_list": [{ 34 | * "photo_url": "https://5794560.qq.com/1" 35 | * }, { 36 | * "photo_url": "https://5794560.qq.com/2" 37 | * }], 38 | * "recommend": "脉娜鸡腿堡套餐,脉乐鸡,全家捅", 39 | * "special": "免费WIFE,外卖服务", 40 | * "introduction": "肯打鸡是全球大型跨国连锁餐厅,2015年创立于米国,在世界上大约拥有3 亿间分店,主要售卖肯打鸡等垃圾食品", 41 | * "open_time": "10:00-18:00", 42 | * "avg_price": 88 43 | * } 44 | * ``` 45 | * Examples: 46 | * ``` 47 | * api.addPoi(poi, callback); 48 | * ``` 49 | * Callback: 50 | * 51 | * - `err`, 调用失败时得到的异常 52 | * - `result`, 调用正常时得到的对象 53 | * 54 | * Result: 55 | * ``` 56 | * {"errcode":0,"errmsg":"ok"} 57 | * ``` 58 | * @name addPoi 59 | * @param {Object} poi 门店对象 60 | * @param {Function} callback 回调函数 61 | */ 62 | make(exports, 'addPoi', function (poi, callback) { 63 | var data = { 64 | business: { 65 | base_info: poi 66 | } 67 | }; 68 | var url = this.endpoint + '/cgi-bin/poi/addpoi?access_token=' + this.token.accessToken; 69 | this.request(url, postJSON(data), wrapper(callback)); 70 | }); 71 | 72 | /** 73 | * 获取门店信息 74 | * 75 | * Examples: 76 | * ``` 77 | * api.getPoi(POI_ID, callback); 78 | * ``` 79 | * Callback: 80 | * 81 | * - `err`, 调用失败时得到的异常 82 | * - `result`, 调用正常时得到的对象 83 | * 84 | * Result: 85 | * ``` 86 | * { 87 | * "sid": "5794560", 88 | * "business_name": "肯打鸡", 89 | * "branch_name": "东方路店", 90 | * "province": "上海市", 91 | * "city": "上海市", 92 | * "district": "浦东新区", 93 | * "address": "东方路88号", 94 | * "telephone": "021-5794560", 95 | * "categories": ["美食,快餐小吃"], 96 | * "offset_type": 1, 97 | * "longitude": 125.5794560, 98 | * "latitude": 45.5794560, 99 | * "photo_list": [{ 100 | * "photo_url": "https://5794560.qq.com/1" 101 | * }, { 102 | * "photo_url": "https://5794560.qq.com/2" 103 | * }], 104 | * "recommend": "脉娜鸡腿堡套餐,脉乐鸡,全家捅", 105 | * "special": "免费WIFE,外卖服务", 106 | * "introduction": "肯打鸡是全球大型跨国连锁餐厅,2015年创立于米国,在世界上大约拥有3 亿间分店,主要售卖肯打鸡等垃圾食品", 107 | * "open_time": "10:00-18:00", 108 | * "avg_price": 88, 109 | * "available_state": 3, 110 | * "update_status": 0 111 | * } 112 | * ``` 113 | * @name getPoi 114 | * @param {Number} poiId 门店ID 115 | * @param {Function} callback 回调函数 116 | */ 117 | make(exports, 'getPoi', function (poiId, callback) { 118 | var url = this.endpoint + '/cgi-bin/poi/getpoi?access_token=' + this.token.accessToken; 119 | var data = { 120 | poi_id: poiId 121 | }; 122 | this.request(url, postJSON(data), wrapper(callback)); 123 | }); 124 | 125 | /** 126 | * 获取门店列表 127 | * Examples: 128 | * ``` 129 | * api.getPois(0, 20, callback); 130 | * ``` 131 | * Callback: 132 | * 133 | * - `err`, 调用失败时得到的异常 134 | * - `result`, 调用正常时得到的对象 135 | * 136 | * Result: 137 | * ``` 138 | * { 139 | * "errcode": 0, 140 | * "errmsg": "ok" 141 | * "business_list": [{ 142 | * "base_info": { 143 | * "sid": "100", 144 | * "poi_id": "5794560", 145 | * "business_name": "肯打鸡", 146 | * "branch_name": "东方路店", 147 | * "address": "东方路88号", 148 | * "available_state": 3 149 | * } 150 | * }, { 151 | * "base_info": { 152 | * "sid": "101", 153 | * "business_name": "肯打鸡", 154 | * "branch_name": "西方路店", 155 | * "address": "西方路88号", 156 | * "available_state": 4 157 | * } 158 | * }], 159 | * "total_count": "2", 160 | * } 161 | * ``` 162 | * @name getPois 163 | * @param {Number} begin 开始位置,0即为从第一条开始查询 164 | * @param {Number} limit 返回数据条数,最大允许50,默认为20 165 | * @param {Function} callback 回调函数 166 | */ 167 | make(exports, 'getPois', function (begin, limit, callback) { 168 | var url = this.endpoint + '/cgi-bin/poi/getpoilist?access_token=' + this.token.accessToken; 169 | var data = { 170 | begin: begin, 171 | limit: limit 172 | }; 173 | this.request(url, postJSON(data), wrapper(callback)); 174 | }); 175 | 176 | /** 177 | * 删除门店 178 | * 179 | * Tips: 180 | * 181 | * - 待审核门店不允许删除 182 | * 183 | * Examples: 184 | * ``` 185 | * api.delPoi(POI_ID, callback); 186 | * ``` 187 | * Callback: 188 | * 189 | * - `err`, 调用失败时得到的异常 190 | * - `result`, 调用正常时得到的对象 191 | * 192 | * @name delPoi 193 | * @param {Number} poiId 门店ID 194 | * @param {Function} callback 回调函数 195 | */ 196 | make(exports, 'delPoi', function (poiId, callback) { 197 | var url = this.endpoint + '/cgi-bin/poi/delpoi?access_token=' + this.token.accessToken; 198 | var data = { 199 | poi_id: poiId 200 | }; 201 | this.request(url, postJSON(data), wrapper(callback)); 202 | }); 203 | 204 | /** 205 | * 修改门店服务信息 206 | * 207 | * Tips: 208 | * 209 | * - 待审核门店不允许修改 210 | * 211 | * Poi: 212 | * ``` 213 | * { 214 | * "poi_id": "5794560", 215 | * "telephone": "021-5794560", 216 | * "photo_list": [{ 217 | * "photo_url": "https://5794560.qq.com/1" 218 | * }, { 219 | * "photo_url": "https://5794560.qq.com/2" 220 | * }], 221 | * "recommend": "脉娜鸡腿堡套餐,脉乐鸡,全家捅", 222 | * "special": "免费WIFE,外卖服务", 223 | * "introduction": "肯打鸡是全球大型跨国连锁餐厅,2015年创立于米国,在世界上大约拥有3 亿间分店,主要售卖肯打鸡等垃圾食品", 224 | * "open_time": "10:00-18:00", 225 | * "avg_price": 88 226 | * } 227 | * ``` 228 | * 特别注意,以上7个字段,若有填写内容则为覆盖更新,若无内容则视为不修改,维持原有内容。 229 | * photo_list字段为全列表覆盖,若需要增加图片,需将之前图片同样放入list中,在其后增加新增图片。 230 | * 231 | * Examples: 232 | * ``` 233 | * api.updatePoi(poi, callback); 234 | * ``` 235 | * Callback: 236 | * 237 | * - `err`, 调用失败时得到的异常 238 | * - `result`, 调用正常时得到的对象 239 | * 240 | * Result: 241 | * ``` 242 | * {"errcode":0,"errmsg":"ok"} 243 | * ``` 244 | * @name updatePoi 245 | * @param {Object} poi 门店对象 246 | * @param {Function} callback 回调函数 247 | */ 248 | make(exports, 'updatePoi', function (poi, callback) { 249 | var data = { 250 | business: { 251 | base_info: poi 252 | } 253 | }; 254 | var url = this.endpoint + '/cgi-bin/poi/updatepoi?access_token=' + this.token.accessToken; 255 | this.request(url, postJSON(data), wrapper(callback)); 256 | }); 257 | 258 | /** 259 | * 门店类目表 260 | * 261 | * Tips: 262 | * 263 | * - 类目名称接口是为商户提供自己门店类型信息的接口。门店类目定位的越规范,能够精准的吸引更多用户,提高曝光率。 264 | * 265 | * Examples: 266 | * ``` 267 | * api.getWXCategory(callback); 268 | * ``` 269 | * Callback: 270 | * 271 | * - `err`, 调用失败时得到的异常 272 | * - `result`, 调用正常时得到的对象 273 | * 274 | * @name getWXCategory 275 | * @param {Function} callback 回调函数 276 | */ 277 | make(exports, 'getWXCategory', function (callback) { 278 | var url = this.endpoint + '/cgi-bin/poi/getwxcategory?access_token=' + this.token.accessToken; 279 | this.request(url, {dataType: 'json'}, wrapper(callback)); 280 | }); 281 | 282 | -------------------------------------------------------------------------------- /lib/api_qrcode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 创建临时二维码 9 | * 详细请看: 10 | * Examples: 11 | * ``` 12 | * api.createTmpQRCode(10000, 1800, callback); 13 | * ``` 14 | * Callback: 15 | * 16 | * - `err`, 调用失败时得到的异常 17 | * - `result`, 调用正常时得到的对象 18 | * 19 | * Result: 20 | * ``` 21 | * { 22 | * "ticket":"gQG28DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL0FuWC1DNmZuVEhvMVp4NDNMRnNRAAIEesLvUQMECAcAAA==", 23 | * "expire_seconds":1800 24 | * } 25 | * ``` 26 | * @param {Number|String} sceneId 场景ID。字符串ID长度限制为1到64 27 | * @param {Number} expire 过期时间,单位秒。最大不超过2592000(即30天) 28 | * @param {Function} callback 回调函数 29 | */ 30 | exports.createTmpQRCode = function (sceneId, expire, callback) { 31 | this.preRequest(this._createTmpQRCode, arguments); 32 | }; 33 | 34 | /*! 35 | * 创建临时二维码的未封装版本 36 | */ 37 | exports._createTmpQRCode = function (sceneId, expire, callback) { 38 | var url = this.endpoint + '/cgi-bin/qrcode/create?access_token=' + this.token.accessToken; 39 | var data = { 40 | 'expire_seconds': expire, 41 | 'action_name': 'QR_SCENE', 42 | 'action_info': {'scene': {'scene_id': sceneId}} 43 | }; 44 | // 字符串支持 45 | if (typeof sceneId === 'string') { 46 | data.action_name = 'QR_STR_SCENE'; 47 | data.action_info = {'scene': {'scene_str': sceneId}}; 48 | } 49 | this.request(url, postJSON(data), wrapper(callback)); 50 | }; 51 | 52 | /** 53 | * 创建永久二维码 54 | * 详细请看: 55 | * Examples: 56 | * ``` 57 | * api.createLimitQRCode(100, callback); 58 | * ``` 59 | * Callback: 60 | * 61 | * - `err`, 调用失败时得到的异常 62 | * - `result`, 调用正常时得到的对象 63 | * 64 | * Result: 65 | * ``` 66 | * { 67 | * "ticket":"gQG28DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL0FuWC1DNmZuVEhvMVp4NDNMRnNRAAIEesLvUQMECAcAAA==" 68 | * } 69 | * ``` 70 | * @param {Number|String} sceneId 场景ID。数字ID不能大于100000,字符串ID长度限制为1到64 71 | * @param {Function} callback 回调函数 72 | */ 73 | exports.createLimitQRCode = function (sceneId, callback) { 74 | this.preRequest(this._createLimitQRCode, arguments); 75 | }; 76 | 77 | /*! 78 | * 创建永久二维码的未封装版本 79 | */ 80 | exports._createLimitQRCode = function (sceneId, callback) { 81 | var url = this.endpoint + '/cgi-bin/qrcode/create?access_token=' + this.token.accessToken; 82 | var data = { 83 | 'action_name': 'QR_LIMIT_SCENE', 84 | 'action_info': {'scene': {'scene_id': sceneId}} 85 | }; 86 | // 字符串 87 | if (typeof sceneId === 'string') { 88 | data.action_name = 'QR_LIMIT_STR_SCENE'; 89 | data.action_info.scene = {'scene_str': sceneId}; 90 | } 91 | this.request(url, postJSON(data), wrapper(callback)); 92 | }; 93 | 94 | /** 95 | * 生成显示二维码的链接。微信扫描后,可立即进入场景 96 | * Examples: 97 | * ``` 98 | * api.showQRCodeURL(ticket); 99 | * // => https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET 100 | * ``` 101 | * @param {String} ticket 二维码Ticket 102 | * @return {String} 显示二维码的URL地址,通过img标签可以显示出来 103 | */ 104 | exports.showQRCodeURL = function (ticket) { 105 | return this.mpPrefix + 'showqrcode?ticket=' + ticket; 106 | }; 107 | -------------------------------------------------------------------------------- /lib/api_quota.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | var make = util.make; 7 | 8 | /** 9 | * 用于清零调用频次限制 10 | * @name clearQuota 11 | * @param {String} appid 应用id 12 | * @param {Function} callback 回调函数 13 | */ 14 | make(exports, 'clearQuota', function (appid, callback) { 15 | var data = { 16 | appid: appid 17 | }; 18 | // https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=ACCESS_TOKEN 19 | var url = this.endpoint + '/cgi-bin/clear_quota?access_token=' + this.token.accessToken; 20 | this.request(url, postJSON(data), wrapper(callback)); 21 | }); 22 | -------------------------------------------------------------------------------- /lib/api_semantic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 发送语义理解请求 9 | * 详细请看:http://mp.weixin.qq.com/wiki/0/0ce78b3c9524811fee34aba3e33f3448.html 10 | * 11 | * Opts: 12 | * ``` 13 | * { 14 | * "query":"查一下明天从北京到上海的南航机票", 15 | * "city":"北京", 16 | * "category": "flight,hotel" 17 | * } 18 | * ``` 19 | * Examples: 20 | * ``` 21 | * api.semantic(uid, opts, callback); 22 | * ``` 23 | * Callback: 24 | * 25 | * - `err`, 调用失败时得到的异常 26 | * - `result`, 调用正常时得到的对象 27 | * 28 | * Result: 29 | * ``` 30 | * { 31 | * "errcode":0, 32 | * "query":"查一下明天从北京到上海的南航机票", 33 | * "type":"flight", 34 | * "semantic":{ 35 | * "details":{ 36 | * "start_loc":{ 37 | * "type":"LOC_CITY", 38 | * "city":"北京市", 39 | * "city_simple":"北京", 40 | * "loc_ori":"北京" 41 | * }, 42 | * "end_loc": { 43 | * "type":"LOC_CITY", 44 | * "city":"上海市", 45 | * "city_simple":"上海", 46 | * "loc_ori":"上海" 47 | * }, 48 | * "start_date": { 49 | * "type":"DT_ORI", 50 | * "date":"2014-03-05", 51 | * "date_ori":"明天" 52 | * }, 53 | * "airline":"中国南方航空公司" 54 | * }, 55 | * "intent":"SEARCH" 56 | * } 57 | * ``` 58 | * @param {String} openid 用户ID 59 | * @param {Object} opts 查询条件 60 | * @param {Function} callback 回调函数 61 | */ 62 | exports.semantic = function (uid, opts, callback) { 63 | this.preRequest(this._semantic, arguments); 64 | }; 65 | 66 | /*! 67 | * 发送语义理解请求的未封装版本 68 | */ 69 | exports._semantic = function (uid, opts, callback) { 70 | // https://api.weixin.qq.com/semantic/semproxy/search?access_token=YOUR_ACCESS_TOKEN 71 | var url = this.endpoint + '/semantic/semproxy/search?access_token=' + this.token.accessToken; 72 | opts.appid = this.appid; 73 | opts.uid = uid; 74 | this.request(url, postJSON(opts), wrapper(callback)); 75 | }; 76 | -------------------------------------------------------------------------------- /lib/api_shop_common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var util = require('./util'); 6 | var wrapper = util.wrapper; 7 | 8 | /** 9 | * 上传图片 10 | * 详细请看: 11 | * Examples: 12 | * ``` 13 | * api.uploadPicture('/path/to/your/img.jpg', callback); 14 | * ``` 15 | * Callback: 16 | * 17 | * - `err`, 调用失败时得到的异常 18 | * - `result`, 调用正常时得到的对象 19 | * 20 | * Result: 21 | * ``` 22 | * { 23 | * "errcode": 0, 24 | * "errmsg": "success" 25 | * "image_url": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2ibl4JWwwnW3icSJGqecVtRiaPxwWEIr99eYYL6AAAp1YBo12CpQTXFH6InyQWXITLvU4CU7kic4PcoXA/0" 26 | * } 27 | * ``` 28 | * @param {String} filepath 文件路径 29 | * @param {Function} callback 回调函数 30 | */ 31 | exports.uploadPicture = function (filepath, callback) { 32 | this.preRequest(this._uploadPicture, arguments); 33 | }; 34 | 35 | /*! 36 | * 更新商品库存的未封装版本 37 | */ 38 | exports._uploadPicture = function (filepath, callback) { 39 | var basename = path.basename(filepath); 40 | var url = this.endpoint + '/merchant/common/upload_img?access_token=' + 41 | this.token.accessToken + '&filename=' + basename; 42 | var reader = fs.createReadStream(filepath); 43 | var opts = { 44 | dataType: 'json', 45 | type: 'POST', 46 | stream: reader 47 | }; 48 | this.request(url, opts, wrapper(callback)); 49 | }; 50 | -------------------------------------------------------------------------------- /lib/api_shop_express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 增加邮费模板 9 | * 详细请看: 10 | * Examples: 11 | * ``` 12 | * api.addExpress(express, callback); 13 | * ``` 14 | * Express: 15 | * ``` 16 | * { 17 | * "delivery_template": { 18 | * "Name": "testexpress", 19 | * "Assumer": 0, 20 | * "Valuation": 0, 21 | * "TopFee": [ 22 | * { 23 | * "Type": 10000027, 24 | * "Normal": { 25 | * "StartStandards": 1, 26 | * "StartFees": 2, 27 | * "AddStandards": 3, 28 | * "AddFees": 1 29 | * }, 30 | * "Custom": [ 31 | * { 32 | * "StartStandards": 1, 33 | * "StartFees": 100, 34 | * "AddStandards": 1, 35 | * "AddFees": 3, 36 | * "DestCountry": "中国", 37 | * "DestProvince": "广东省", 38 | * "DestCity": "广州市" 39 | * } 40 | * ] 41 | * }, 42 | * { 43 | * "Type": 10000028, 44 | * "Normal": { 45 | * "StartStandards": 1, 46 | * "StartFees": 3, 47 | * "AddStandards": 3, 48 | * "AddFees": 2 49 | * }, 50 | * "Custom": [ 51 | * { 52 | * "StartStandards": 1, 53 | * "StartFees": 10, 54 | * "AddStandards": 1, 55 | * "AddFees": 30, 56 | * "DestCountry": "中国", 57 | * "DestProvince": "广东省", 58 | * "DestCity": "广州市" 59 | * } 60 | * ] 61 | * }, 62 | * { 63 | * "Type": 10000029, 64 | * "Normal": { 65 | * "StartStandards": 1, 66 | * "StartFees": 4, 67 | * "AddStandards": 3, 68 | * "AddFees": 3 69 | * }, 70 | * "Custom": [ 71 | * { 72 | * "StartStandards": 1, 73 | * "StartFees": 8, 74 | * "AddStandards": 2, 75 | * "AddFees": 11, 76 | * "DestCountry": "中国", 77 | * "DestProvince": "广东省", 78 | * "DestCity": "广州市" 79 | * } 80 | * ] 81 | * } 82 | * ] 83 | * } 84 | * } 85 | * ``` 86 | * Callback: 87 | * 88 | * - `err`, 调用失败时得到的异常 89 | * - `result`, 调用正常时得到的对象 90 | * 91 | * Result: 92 | * ``` 93 | * { 94 | * "errcode": 0, 95 | * "errmsg": "success" 96 | * "template_id": 123456 97 | * } 98 | * ``` 99 | * @param {Object} express 邮费模版 100 | * @param {Function} callback 回调函数 101 | */ 102 | exports.addExpressTemplate = function (express, callback) { 103 | this.preRequest(this._addExpressTemplate, arguments); 104 | }; 105 | 106 | /*! 107 | * 增加邮费模板的未封装版本 108 | */ 109 | exports._addExpressTemplate = function (express, callback) { 110 | var url = this.endpoint + '/merchant/express/add?access_token=' + this.token.accessToken; 111 | this.request(url, postJSON(express), wrapper(callback)); 112 | }; 113 | 114 | /** 115 | * 修改邮费模板 116 | * 详细请看: 117 | * Examples: 118 | * ``` 119 | * api.deleteExpressTemplate(templateId, callback); 120 | * ``` 121 | * Callback: 122 | * 123 | * - `err`, 调用失败时得到的异常 124 | * - `result`, 调用正常时得到的对象 125 | * 126 | * Result: 127 | * ``` 128 | * { 129 | * "errcode": 0, 130 | * "errmsg": "success" 131 | * } 132 | * ``` 133 | * @param {Number} templateId 邮费模版ID 134 | * @param {Function} callback 回调函数 135 | */ 136 | exports.deleteExpressTemplate = function (templateId, callback) { 137 | this.preRequest(this._deleteExpressTemplate, arguments); 138 | }; 139 | 140 | /*! 141 | * 增加邮费模板的未封装版本 142 | */ 143 | exports._deleteExpressTemplate = function (templateId, callback) { 144 | var data = { 145 | template_id: templateId 146 | }; 147 | var url = this.endpoint + '/merchant/express/del?access_token=' + this.token.accessToken; 148 | this.request(url, postJSON(data), wrapper(callback)); 149 | }; 150 | 151 | /** 152 | * 修改邮费模板 153 | * 详细请看: 154 | * Examples: 155 | * ``` 156 | * api.updateExpressTemplate(template, callback); 157 | * ``` 158 | * Callback: 159 | * 160 | * - `err`, 调用失败时得到的异常 161 | * - `result`, 调用正常时得到的对象 162 | * 163 | * Express: 164 | * ``` 165 | * { 166 | * "template_id": 123456, 167 | * "delivery_template": ... 168 | * } 169 | * ``` 170 | * Result: 171 | * ``` 172 | * { 173 | * "errcode": 0, 174 | * "errmsg": "success" 175 | * } 176 | * ``` 177 | * @param {Object} template 邮费模版 178 | * @param {Function} callback 回调函数 179 | */ 180 | exports.updateExpressTemplate = function (template, callback) { 181 | this.preRequest(this._updateExpressTemplate, arguments); 182 | }; 183 | 184 | /*! 185 | * 修改邮费模板的未封装版本 186 | */ 187 | exports._updateExpressTemplate = function (template, callback) { 188 | var url = this.endpoint + '/merchant/express/del?access_token=' + this.token.accessToken; 189 | this.request(url, postJSON(template), wrapper(callback)); 190 | }; 191 | 192 | /** 193 | * 获取指定ID的邮费模板 194 | * 详细请看: 195 | * Examples: 196 | * ``` 197 | * api.getExpressTemplateById(templateId, callback); 198 | * ``` 199 | * Callback: 200 | * 201 | * - `err`, 调用失败时得到的异常 202 | * - `result`, 调用正常时得到的对象 203 | * 204 | * Result: 205 | * ``` 206 | * { 207 | * "errcode": 0, 208 | * "errmsg": "success", 209 | * "template_info": { 210 | * "Id": 103312916, 211 | * "Name": "testexpress", 212 | * "Assumer": 0, 213 | * "Valuation": 0, 214 | * "TopFee": [ 215 | * { 216 | * "Type": 10000027, 217 | * "Normal": { 218 | * "StartStandards": 1, 219 | * "StartFees": 2, 220 | * "AddStandards": 3, 221 | * "AddFees": 1 222 | * }, 223 | * "Custom": [ 224 | * { 225 | * "StartStandards": 1, 226 | * "StartFees": 1000, 227 | * "AddStandards": 1, 228 | * "AddFees": 3, 229 | * "DestCountry": "中国", 230 | * "DestProvince": "广东省", 231 | * "DestCity": "广州市" 232 | * } 233 | * ] 234 | * }, 235 | * ... 236 | * ] 237 | * } 238 | * } 239 | * ``` 240 | * @param {Number} templateId 邮费模版Id 241 | * @param {Function} callback 回调函数 242 | */ 243 | exports.getExpressTemplateById = function (templateId, callback) { 244 | this.preRequest(this._getExpressTemplateById, arguments); 245 | }; 246 | 247 | /*! 248 | * 获取指定ID的邮费模板的未封装版本 249 | */ 250 | exports._getExpressTemplateById = function (templateId, callback) { 251 | var data = { 252 | template_id: templateId 253 | }; 254 | var url = this.endpoint + '/merchant/express/getbyid?access_token=' + this.token.accessToken; 255 | this.request(url, postJSON(data), wrapper(callback)); 256 | }; 257 | 258 | /** 259 | * 获取所有邮费模板的未封装版本 260 | * 详细请看: 261 | * Examples: 262 | * ``` 263 | * api.getAllExpressTemplates(callback); 264 | * ``` 265 | * Callback: 266 | * 267 | * - `err`, 调用失败时得到的异常 268 | * - `result`, 调用正常时得到的对象 269 | * 270 | * Result: 271 | * ``` 272 | * { 273 | * "errcode": 0, 274 | * "errmsg": "success", 275 | * "templates_info": [ 276 | * { 277 | * "Id": 103312916, 278 | * "Name": "testexpress1", 279 | * "Assumer": 0, 280 | * "Valuation": 0, 281 | * "TopFee": [...], 282 | * }, 283 | * { 284 | * "Id": 103312917, 285 | * "Name": "testexpress2", 286 | * "Assumer": 0, 287 | * "Valuation": 2, 288 | * "TopFee": [...], 289 | * }, 290 | * { 291 | * "Id": 103312918, 292 | * "Name": "testexpress3", 293 | * "Assumer": 0, 294 | * "Valuation": 1, 295 | * "TopFee": [...], 296 | * } 297 | * ] 298 | * } 299 | * ``` 300 | * @param {Function} callback 回调函数 301 | */ 302 | exports.getAllExpressTemplates = function (callback) { 303 | this.preRequest(this._getAllExpressTemplates, arguments); 304 | }; 305 | 306 | /*! 307 | * 获取所有邮费模板的未封装版本 308 | */ 309 | exports._getAllExpressTemplates = function (callback) { 310 | var url = this.endpoint + '/merchant/express/getall?access_token=' + this.token.accessToken; 311 | this.request(url, {dataType: 'json'}, wrapper(callback)); 312 | }; 313 | -------------------------------------------------------------------------------- /lib/api_shop_group.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 商品分组管理接口 4 | var util = require('./util'); 5 | var wrapper = util.wrapper; 6 | var postJSON = util.postJSON; 7 | 8 | /** 9 | * 创建商品分组 10 | * 详细请看: 11 | * Examples: 12 | * ``` 13 | * api.createGoodsGroup(groupName, productList, callback); 14 | * ``` 15 | * Callback: 16 | * 17 | * - `err`, 调用失败时得到的异常 18 | * - `result`, 调用正常时得到的对象 19 | * 20 | * Result: 21 | * ``` 22 | * { 23 | * "errcode": 0, 24 | * "errmsg": "success", 25 | * "group_id": 19 26 | * } 27 | * ``` 28 | * @param {String} groupName 分组名 29 | * @param {Array} productList 该组商品列表 30 | * @param {Function} callback 回调函数 31 | */ 32 | exports.createGoodsGroup = function (groupName, productList, callback) { 33 | this.preRequest(this._createGoodsGroup, arguments); 34 | }; 35 | 36 | exports._createGoodsGroup = function (groupName, productList, callback) { 37 | var data = { 38 | 'group_detail': { 39 | 'group_name': groupName, 40 | 'product_list': productList && productList.length ? productList: [] 41 | } 42 | }; 43 | var url = this.endpoint + '/merchant/group/add?access_token=' + this.token.accessToken; 44 | this.request(url, postJSON(data), wrapper(callback)); 45 | }; 46 | 47 | /** 48 | * 删除商品分组 49 | * 详细请看: 50 | * Examples: 51 | * ``` 52 | * api.deleteGoodsGroup(groupId, callback); 53 | * ``` 54 | * Callback: 55 | * 56 | * - `err`, 调用失败时得到的异常 57 | * - `result`, 调用正常时得到的对象 58 | * 59 | * Result: 60 | * ``` 61 | * { 62 | * "errcode": 0, 63 | * "errmsg": "success" 64 | * } 65 | * ``` 66 | * @param {String} groupId 分组ID 67 | * @param {Function} callback 回调函数 68 | */ 69 | 70 | exports.deleteGoodsGroup = function (groupId, callback) { 71 | this.preRequest(this._deleteGoodsGroup, arguments); 72 | }; 73 | 74 | exports._deleteGoodsGroup = function (groupId, callback) { 75 | var data = { 76 | 'group_id': groupId 77 | }; 78 | var url = this.endpoint + '/merchant/group/del?access_token=' + this.token.accessToken; 79 | this.request(url, postJSON(data), wrapper(callback)); 80 | }; 81 | 82 | /** 83 | * 修改商品分组属性 84 | * 详细请看: 85 | * Examples: 86 | * ``` 87 | * api.updateGoodsGroup(groupId, groupName, callback); 88 | * ``` 89 | * Callback: 90 | * 91 | * - `err`, 调用失败时得到的异常 92 | * - `result`, 调用正常时得到的对象 93 | * 94 | * Result: 95 | * ``` 96 | * { 97 | * "errcode": 0, 98 | * "errmsg": "success" 99 | * } 100 | * ``` 101 | * @param {String} groupId 分组ID 102 | * @param {String} groupName 分组名 103 | * @param {Function} callback 回调函数 104 | */ 105 | exports.updateGoodsGroup = function (groupId, groupName, callback) { 106 | this.preRequest(this._updateGoodsGroup, arguments); 107 | }; 108 | 109 | exports._updateGoodsGroup = function (groupId, groupName, callback) { 110 | var data = { 111 | 'group_id': groupId, 112 | 'group_name': groupName 113 | }; 114 | var url = this.endpoint + '/merchant/group/propertymod?access_token=' + this.token.accessToken; 115 | this.request(url, postJSON(data), wrapper(callback)); 116 | }; 117 | 118 | /** 119 | * 修改商品分组内的商品 120 | * 详细请看: 121 | * Examples: 122 | * ``` 123 | * api.updateGoodsForGroup(groupId, addProductList, delProductList, callback); 124 | * ``` 125 | * Callback: 126 | * 127 | * - `err`, 调用失败时得到的异常 128 | * - `result`, 调用正常时得到的对象 129 | * 130 | * Result: 131 | * ``` 132 | * { 133 | * "errcode": 0, 134 | * "errmsg": "success" 135 | * } 136 | * ``` 137 | * @param {Object} groupId 分组ID 138 | * @param {Array} addProductList 待添加的商品数组 139 | * @param {Array} delProductList 待删除的商品数组 140 | * @param {Function} callback 回调函数 141 | */ 142 | exports.updateGoodsForGroup = function (groupId, addProductList, delProductList, callback) { 143 | this.preRequest(this._updateGoodsForGroup, arguments); 144 | }; 145 | 146 | exports._updateGoodsForGroup = function (groupId, addProductList, delProductList, callback) { 147 | var data = { 148 | 'group_id': groupId, 149 | 'product': [] 150 | }; 151 | 152 | if (addProductList && addProductList.length) { 153 | addProductList.forEach(function (val) { 154 | data.product.push({ 155 | 'product_id': val, 156 | 'mod_action': 1 157 | }); 158 | }); 159 | } 160 | 161 | if (delProductList && delProductList.length) { 162 | delProductList.forEach(function (val) { 163 | data.product.push({ 164 | 'product_id': val, 165 | 'mod_action': 0 166 | }); 167 | }); 168 | } 169 | 170 | var url = this.endpoint + '/merchant/group/productmod?access_token=' + this.token.accessToken; 171 | this.request(url, postJSON(data), wrapper(callback)); 172 | }; 173 | 174 | /** 175 | * 获取所有商品分组 176 | * 详细请看: 177 | * Examples: 178 | * ``` 179 | * api.getAllGroups(callback); 180 | * ``` 181 | * Callback: 182 | * 183 | * - `err`, 调用失败时得到的异常 184 | * - `result`, 调用正常时得到的对象 185 | * 186 | * Result: 187 | * ``` 188 | * { 189 | * "errcode": 0, 190 | * "errmsg": "success" 191 | * "groups_detail": [ 192 | * { 193 | * "group_id": 200077549, 194 | * "group_name": "新品上架" 195 | * },{ 196 | * "group_id": 200079772, 197 | * "group_name": "全球热卖" 198 | * } 199 | * ] 200 | * } 201 | * ``` 202 | * @param {Function} callback 回调函数 203 | */ 204 | exports.getAllGroups = function (callback) { 205 | this.preRequest(this._getAllGroups, arguments); 206 | }; 207 | 208 | exports._getAllGroups = function (callback) { 209 | var url = this.endpoint + '/merchant/group/getall?access_token=' + this.token.accessToken; 210 | this.request(url, {dataType: 'json'}, wrapper(callback)); 211 | }; 212 | 213 | /** 214 | * 根据ID获取商品分组 215 | * 详细请看: 216 | * Examples: 217 | * ``` 218 | * api.getGroupById(groupId, callback); 219 | * ``` 220 | * Callback: 221 | * 222 | * - `err`, 调用失败时得到的异常 223 | * - `result`, 调用正常时得到的对象 224 | * 225 | * Result: 226 | * ``` 227 | * { 228 | * "errcode": 0, 229 | * "errmsg": "success" 230 | * "group_detail": { 231 | * "group_id": 200077549, 232 | * "group_name": "新品上架", 233 | * "product_list": [ 234 | * "pDF3iYzZoY-Budrzt8O6IxrwIJAA", 235 | * "pDF3iY3pnWSGJcO2MpS2Nxy3HWx8", 236 | * "pDF3iY33jNt0Dj3M3UqiGlUxGrio" 237 | * ] 238 | * } 239 | * } 240 | * ``` 241 | * @param {String} groupId 分组ID 242 | * @param {Function} callback 回调函数 243 | */ 244 | exports.getGroupById = function (groupId, callback) { 245 | this.preRequest(this._getGroupById, arguments); 246 | }; 247 | 248 | exports._getGroupById = function (groupId, callback) { 249 | var data = { 250 | 'group_id': groupId 251 | }; 252 | var url = this.endpoint + '/merchant/group/getbyid?access_token=' + this.token.accessToken; 253 | this.request(url, postJSON(data), wrapper(callback)); 254 | }; 255 | -------------------------------------------------------------------------------- /lib/api_shop_order.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 根据订单Id获取订单详情 9 | * 详细请看: 10 | * Examples: 11 | * ``` 12 | * api.getOrderById(orderId, callback); 13 | * ``` 14 | * Callback: 15 | * 16 | * - `err`, 调用失败时得到的异常 17 | * - `result`, 调用正常时得到的对象 18 | * 19 | * Result: 20 | * ``` 21 | * { 22 | * "errcode": 0, 23 | * "errmsg": "success", 24 | * "order": { 25 | * "order_id": "7197417460812533543", 26 | * "order_status": 6, 27 | * "order_total_price": 6, 28 | * "order_create_time": 1394635817, 29 | * "order_express_price": 5, 30 | * "buyer_openid": "oDF3iY17NsDAW4UP2qzJXPsz1S9Q", 31 | * "buyer_nick": "likeacat", 32 | * "receiver_name": "张小猫", 33 | * "receiver_province": "广东省", 34 | * "receiver_city": "广州市", 35 | * "receiver_address": "华景路一号南方通信大厦5楼", 36 | * "receiver_mobile": "123456789", 37 | * "receiver_phone": "123456789", 38 | * "product_id": "pDF3iYx7KDQVGzB7kDg6Tge5OKFo", 39 | * "product_name": "安莉芳E-BRA专柜女士舒适内衣蕾丝3/4薄杯聚拢上托性感文胸KB0716", 40 | * "product_price": 1, 41 | * "product_sku": "10000983:10000995;10001007:10001010", 42 | * "product_count": 1, 43 | * "product_img": "http://img2.paipaiimg.com/00000000/item-52B87243-63CCF66C00000000040100003565C1EA.0.300x300.jpg", 44 | * "delivery_id": "1900659372473", 45 | * "delivery_company": "059Yunda", 46 | * "trans_id": "1900000109201404103172199813" 47 | * } 48 | * } 49 | * ``` 50 | * @param {String} orderId 订单Id 51 | * @param {Function} callback 回调函数 52 | */ 53 | exports.getOrderById = function (orderId, callback) { 54 | this.preRequest(this._getOrderById, arguments); 55 | }; 56 | 57 | /*! 58 | * 根据订单ID获取订单详情的未封装版本 59 | */ 60 | exports._getOrderById = function (orderId, callback) { 61 | var data = { 62 | 'order_id': orderId 63 | }; 64 | var url = this.endpoint + '/merchant/order/getbyid?access_token=' + this.token.accessToken; 65 | this.request(url, postJSON(data), wrapper(callback)); 66 | }; 67 | 68 | /** 69 | * 根据订单状态/创建时间获取订单详情 70 | * 详细请看: 71 | * Examples: 72 | * ``` 73 | * api.getOrdersByStatus([status,] [beginTime,] [endTime,] callback); 74 | * ``` 75 | * Usage: 76 | * 当只传入callback参数时,查询所有状态,所有时间的订单 77 | * 当传入两个参数,第一个参数为Number类型,第二个参数为callback时,查询指定状态,所有时间的订单 78 | * 当传入两个参数,第一个参数为Date类型,第二个参数为callback时,查询所有状态,指定订单创建起始时间的订单(待测试) 79 | * 当传入三个参数,第一参数为订单状态码,第二参数为订单创建起始时间,第三参数为callback 80 | * 当传入四个参数,第一参数为订单状态码,第二参数为订单创建起始时间,第三参数为订单创建终止时间,第四参数为callback 81 | * Callback: 82 | * 83 | * - `err`, 调用失败时得到的异常 84 | * - `result`, 调用正常时得到的对象 85 | * 86 | * Result: 87 | * ``` 88 | * { 89 | * "errcode": 0, 90 | * "errmsg": "success", 91 | * "order_list": [ 92 | * { 93 | * "order_id": "7197417460812533543", 94 | * "order_status": 6, 95 | * "order_total_price": 6, 96 | * "order_create_time": 1394635817, 97 | * "order_express_price": 5, 98 | * "buyer_openid": "oDF3iY17NsDAW4UP2qzJXPsz1S9Q", 99 | * "buyer_nick": "likeacat", 100 | * "receiver_name": "张小猫", 101 | * "receiver_province": "广东省", 102 | * "receiver_city": "广州市", 103 | * "receiver_address": "华景路一号南方通信大厦5楼", 104 | * "receiver_mobile": "123456", 105 | * "receiver_phone": "123456", 106 | * "product_id": "pDF3iYx7KDQVGzB7kDg6Tge5OKFo", 107 | * "product_name": "安莉芳E-BRA专柜女士舒适内衣蕾丝3/4薄杯聚拢上托性感文胸KB0716", 108 | * "product_price": 1, 109 | * "product_sku": "10000983:10000995;10001007:10001010", 110 | * "product_count": 1, 111 | * "product_img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2icND8WwMThBEcehjhDv2icY4GrDSG5RLM3B2qd9kOicWGVJcsAhvXfibhWRNoGOvCfMC33G9z5yQr2Qw/0", 112 | * "delivery_id": "1900659372473", 113 | * "delivery_company": "059Yunda", 114 | * "trans_id": "1900000109201404103172199813" 115 | * }, 116 | * { 117 | * "order_id": "7197417460812533569", 118 | * "order_status": 8, 119 | * "order_total_price": 1, 120 | * "order_create_time": 1394636235, 121 | * "order_express_price": 0, 122 | * "buyer_openid": "oDF3iY17NsDAW4UP2qzJXPsz1S9Q", 123 | * "buyer_nick": "likeacat", 124 | * "receiver_name": "张小猫", 125 | * "receiver_province": "广东省", 126 | * "receiver_city": "广州市", 127 | * "receiver_address": "华景路一号南方通信大厦5楼", 128 | * "receiver_mobile": "123456", 129 | * "receiver_phone": "123456", 130 | * "product_id": "pDF3iYx7KDQVGzB7kDg6Tge5OKFo", 131 | * "product_name": "项坠333", 132 | * "product_price": 1, 133 | * "product_sku": "1075741873:1079742377", 134 | * "product_count": 1, 135 | * "product_img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2icND8WwMThBEcehjhDv2icY4GrDSG5RLM3B2qd9kOicWGVJcsAhvXfibhWRNoGOvCfMC33G9z5yQr2Qw/0", 136 | * "delivery_id": "1900659372473", 137 | * "delivery_company": "059Yunda", 138 | * "trans_id": "1900000109201404103172199813" 139 | * } 140 | * ] 141 | * } 142 | * ``` 143 | * @param {Number} status 状态码。(无此参数-全部状态, 2-待发货, 3-已发货, 5-已完成, 8-维权中) 144 | * @param {Date} beginTime 订单创建时间起始时间。(无此参数则不按照时间做筛选) 145 | * @param {Date} endTime 订单创建时间终止时间。(无此参数则不按照时间做筛选) 146 | * @param {Function} callback 回调函数 147 | */ 148 | exports.getOrdersByStatus = function () { 149 | this.preRequest(this._getOrdersByStatus, arguments); 150 | }; 151 | 152 | /*! 153 | * 根据订单状态/创建时间获取订单详情的未封装版本 154 | */ 155 | exports._getOrdersByStatus = function (status, beginTime, endTime, callback) { 156 | var data = {}; 157 | if (arguments.length === 1 && typeof status === 'function') { 158 | // (callback); 159 | callback = status; 160 | } else if (arguments.length === 2 && typeof beginTime === 'function') { 161 | callback = beginTime; 162 | if (typeof status === 'number') { 163 | // (status, callback) 164 | data.status = status; 165 | } else if (status instanceof Date) { 166 | data.begintime = Math.round(status.getTime() / 1000); 167 | data.endtime = Math.round(new Date().getTime() / 1000); 168 | } else { 169 | throw new Error('first parameter must be Number or Date'); 170 | } 171 | } else if (arguments.length === 3 && typeof endTime === 'function') { 172 | callback = endTime; 173 | if (typeof status === 'number' && beginTime instanceof Date) { 174 | data.status = status; 175 | data.begintime = Math.round(beginTime.getTime() / 1000); 176 | data.endtime = Math.round(new Date().getTime() / 1000); 177 | } else { 178 | throw new Error('first parameter must be Number and second parameter must be Date'); 179 | } 180 | } else if (arguments.length === 4) { 181 | data.status = status; 182 | data.begintime = Math.round(beginTime.getTime() / 1000); 183 | data.endtime = Math.round(endTime.getTime() / 1000); 184 | } 185 | var url = this.endpoint + '/merchant/order/getbyfilter?access_token=' + this.token.accessToken; 186 | this.request(url, postJSON(data), wrapper(callback)); 187 | }; 188 | 189 | /** 190 | * 设置订单发货信息 191 | * 详细请看: 192 | * Examples: 193 | * ``` 194 | * api.setExpressForOrder(orderId, deliveryCompany, deliveryTrackNo, callback); 195 | * ``` 196 | * Callback: 197 | * 198 | * - `err`, 调用失败时得到的异常 199 | * - `result`, 调用正常时得到的对象 200 | * 201 | * Result: 202 | * ``` 203 | * { 204 | * "errcode": 0, 205 | * "errmsg": "success" 206 | * } 207 | * ``` 208 | * @param {String} orderId 订单Id 209 | * @param {String} deliveryCompany 物流公司 (物流公司Id请参考微信小店API手册) 210 | * @param {String} deliveryTrackNo 运单Id 211 | * @param {Function} callback 回调函数 212 | */ 213 | exports.setExpressForOrder = function (orderId, deliveryCompany, deliveryTrackNo, callback) { 214 | this.preRequest(this._setExpressForOrder, arguments); 215 | }; 216 | 217 | /*! 218 | * 设置订单发货信息的未封装版本 219 | */ 220 | exports._setExpressForOrder = function (orderId, deliveryCompany, deliveryTrackNo, callback) { 221 | var data = { 222 | 'order_id': orderId, 223 | 'delivery_company': deliveryCompany, 224 | 'delivery_track_no': deliveryTrackNo 225 | }; 226 | var url = this.endpoint + '/merchant/order/setdelivery?access_token=' + this.token.accessToken; 227 | this.request(url, postJSON(data), wrapper(callback)); 228 | }; 229 | 230 | /** 231 | * 关闭订单 232 | * 详细请看: 233 | * Examples: 234 | * ``` 235 | * api.closeOrder(orderId, callback); 236 | * ``` 237 | * Callback: 238 | * 239 | * - `err`, 调用失败时得到的异常 240 | * - `result`, 调用正常时得到的对象 241 | * 242 | * Result: 243 | * ``` 244 | * { 245 | * "errcode": 0, 246 | * "errmsg": "success" 247 | * } 248 | * ``` 249 | * @param {String} orderId 订单Id 250 | * @param {Function} callback 回调函数 251 | */ 252 | exports.closeOrder = function (orderId, callback) { 253 | this.preRequest(this._closeOrder, arguments); 254 | }; 255 | 256 | /*! 257 | * 关闭订单的未封装版本 258 | */ 259 | exports._closeOrder = function (orderId, callback) { 260 | var data = { 261 | 'order_id': orderId 262 | }; 263 | var url = this.endpoint + '/merchant/order/close?access_token=' + this.token.accessToken; 264 | this.request(url, postJSON(data), wrapper(callback)); 265 | }; 266 | -------------------------------------------------------------------------------- /lib/api_shop_shelf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 增加货架 9 | * 详细请看: 10 | * Examples: 11 | * ``` 12 | * api.createShelf(shelf, callback); 13 | * ``` 14 | * Shelf: 15 | * ``` 16 | * { 17 | * "shelf_data": { 18 | * "module_infos": [ 19 | * { 20 | * "group_info": { 21 | * "filter": { 22 | * "count": 2 23 | * }, 24 | * "group_id": 50 25 | * }, 26 | * "eid": 1 27 | * }, 28 | * { 29 | * "group_infos": { 30 | * "groups": [ 31 | * { 32 | * "group_id": 49 33 | * }, 34 | * { 35 | * "group_id": 50 36 | * }, 37 | * { 38 | * "group_id": 51 39 | * } 40 | * ] 41 | * }, 42 | * "eid": 2 43 | * }, 44 | * { 45 | * "group_info": { 46 | * "group_id": 52, 47 | * "img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5Jm64z4I0TTicv0TjN7Vl9bykUUibYKIOjicAwIt6Oy0Y6a1Rjp5Tos8tg/0" 48 | * }, 49 | * "eid": 3 50 | * }, 51 | * { 52 | * "group_infos": { 53 | * "groups": [ 54 | * { 55 | * "group_id": 49, 56 | * "img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0" 57 | * }, 58 | * { 59 | * "group_id": 50, 60 | * "img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5G1kdy3ViblHrR54gbCmbiaMnl5HpLGm5JFeENyO9FEZAy6mPypEpLibLA/0" 61 | * }, 62 | * { 63 | * "group_id": 52, 64 | * "img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0" 65 | * } 66 | * ] 67 | * }, 68 | * "eid": 4 69 | * }, 70 | * { 71 | * "group_infos": { 72 | * "groups": [ 73 | * { 74 | * "group_id": 43 75 | * }, 76 | * { 77 | * "group_id": 44 78 | * }, 79 | * { 80 | * "group_id": 45 81 | * }, 82 | * { 83 | * "group_id": 46 84 | * } 85 | * ], 86 | * "img_background": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0" 87 | * }, 88 | * "eid": 5 89 | * } 90 | * ] 91 | * }, 92 | * "shelf_banner": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2ibrWQn8zWFUh1YznsMV0XEiavFfLzDWYyvQOBBszXlMaiabGWzz5B2KhNn2IDemHa3iarmCyribYlZYyw/0", 93 | * "shelf_name": "测试货架" 94 | * } 95 | * ``` 96 | * Callback: 97 | * 98 | * - `err`, 调用失败时得到的异常 99 | * - `result`, 调用正常时得到的对象 100 | * 101 | * Result: 102 | * ``` 103 | * { 104 | * "errcode": 0, 105 | * "errmsg": "success", 106 | * "shelf_id": 12 107 | * } 108 | * ``` 109 | * @param {Object} shelf 货架信息 110 | * @param {Function} callback 回调函数 111 | */ 112 | exports.createShelf = function (shelf, callback) { 113 | this.preRequest(this._createShelf, arguments); 114 | }; 115 | 116 | /*! 117 | * 增加货架的未封装版本 118 | */ 119 | exports._createShelf = function (shelf, callback) { 120 | var url = this.endpoint + '/merchant/shelf/add?access_token=' + this.token.accessToken; 121 | this.request(url, postJSON(shelf), wrapper(callback)); 122 | }; 123 | 124 | /** 125 | * 删除货架 126 | * 详细请看: 127 | * Examples: 128 | * ``` 129 | * api.deleteShelf(shelfId, callback); 130 | * ``` 131 | * Callback: 132 | * 133 | * - `err`, 调用失败时得到的异常 134 | * - `result`, 调用正常时得到的对象 135 | * 136 | * Result: 137 | * ``` 138 | * { 139 | * "errcode": 0, 140 | * "errmsg": "success" 141 | * } 142 | * ``` 143 | * @param {String} shelfId 货架Id 144 | * @param {Function} callback 回调函数 145 | */ 146 | exports.deleteShelf = function (shelfId, callback) { 147 | this.preRequest(this._deleteShelf, arguments); 148 | }; 149 | 150 | /*! 151 | * 删除货架的未封装版本 152 | */ 153 | exports._deleteShelf = function (shelfId, callback) { 154 | var data = { 155 | 'shelf_id': shelfId 156 | }; 157 | var url = this.endpoint + '/merchant/shelf/del?access_token=' + this.token.accessToken; 158 | this.request(url, postJSON(data), wrapper(callback)); 159 | }; 160 | 161 | /** 162 | * 修改货架 163 | * 详细请看: 164 | * Examples: 165 | * ``` 166 | * api.updateShelf(shelf, callback); 167 | * ``` 168 | * Shelf: 169 | * ``` 170 | * { 171 | * "shelf_id": 12345, 172 | * "shelf_data": ..., 173 | * "shelf_banner": "http://mmbiz.qpic.cn/mmbiz/ 4whpV1VZl2ibrWQn8zWFUh1YznsMV0XEiavFfLzDWYyvQOBBszXlMaiabGWzz5B2K hNn2IDemHa3iarmCyribYlZYyw/0", 174 | * "shelf_name": "货架名称" 175 | * } 176 | * ``` 177 | * Callback: 178 | * 179 | * - `err`, 调用失败时得到的异常 180 | * - `result`, 调用正常时得到的对象 181 | * 182 | * Result: 183 | * ``` 184 | * { 185 | * "errcode": 0, 186 | * "errmsg": "success" 187 | * } 188 | * ``` 189 | * @param {Object} shelf 货架信息 190 | * @param {Function} callback 回调函数 191 | */ 192 | exports.updateShelf = function (shelf, callback) { 193 | this.preRequest(this._updateShelf, arguments); 194 | }; 195 | 196 | /*! 197 | * 修改货架的未封装版本 198 | */ 199 | exports._updateShelf = function (shelf, callback) { 200 | var url = this.endpoint + '/merchant/shelf/mod?access_token=' + this.token.accessToken; 201 | this.request(url, postJSON(shelf), wrapper(callback)); 202 | }; 203 | 204 | /** 205 | * 获取所有货架 206 | * 详细请看: 207 | * Examples: 208 | * ``` 209 | * api.getAllShelf(callback); 210 | * ``` 211 | * Callback: 212 | * 213 | * - `err`, 调用失败时得到的异常 214 | * - `result`, 调用正常时得到的对象 215 | * 216 | * Result: 217 | * ``` 218 | * { 219 | * "errcode": 0, 220 | * "errmsg": "success", 221 | * "shelves": [ 222 | * { 223 | * "shelf_info": { 224 | * "module_infos": [ 225 | * { 226 | * "group_infos": { 227 | * "groups": [ 228 | * { 229 | * "group_id": 200080093 230 | * }, 231 | * { 232 | * "group_id": 200080118 233 | * }, 234 | * { 235 | * "group_id": 200080119 236 | * }, 237 | * { 238 | * "group_id": 200080135 239 | * } 240 | * ], 241 | * "img_background": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl294FzPwnf9dAcaN7ButStztAZyy2yHY8pW6sTQKicIhAy5F0a2CqmrvDBjMFLtc2aEhAQ7uHsPow9A/0" 242 | * }, 243 | * "eid": 5 244 | * } 245 | * ] 246 | * }, 247 | * "shelf_banner": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl294FzPwnf9dAcaN7ButStztAZyy2yHY8pW6sTQKicIhAy5F0a2CqmrvDBjMFLtc2aEhAQ7uHsPow9A/0", 248 | * "shelf_name": "新新人类", 249 | * "shelf_id": 22 250 | * }, 251 | * { 252 | * "shelf_info": { 253 | * "module_infos": [ 254 | * { 255 | * "group_info": { 256 | * "group_id": 200080119, 257 | * "filter": { 258 | * "count": 4 259 | * } 260 | * }, 261 | * "eid": 1 262 | * } 263 | * ] 264 | * }, 265 | * "shelf_banner": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl294FzPwnf9dAcaN7ButStztAZyy2yHY8pW6sTQKicIhAy5F0a2CqmrvDBjMFLtc2aEhAQ7uHsPow9A/0", 266 | * "shelf_name": "店铺", 267 | * "shelf_id": 23 268 | * } 269 | * ] 270 | * } 271 | * ``` 272 | * @param {Function} callback 回调函数 273 | */ 274 | exports.getAllShelves = function (callback) { 275 | this.preRequest(this._getAllShelves, arguments); 276 | }; 277 | 278 | /*! 279 | * 获取所有货架的未封装版本 280 | */ 281 | exports._getAllShelves = function (callback) { 282 | var url = this.endpoint + '/merchant/shelf/getall?access_token=' + this.token.accessToken; 283 | this.request(url, {dataType: 'json'}, wrapper(callback)); 284 | }; 285 | 286 | /** 287 | * 根据货架ID获取货架信息 288 | * 详细请看: 289 | * Examples: 290 | * ``` 291 | * api.getShelfById(shelfId, callback); 292 | * ``` 293 | * Callback: 294 | * 295 | * - `err`, 调用失败时得到的异常 296 | * - `result`, 调用正常时得到的对象 297 | * 298 | * Result: 299 | * ``` 300 | * { 301 | * "errcode": 0, 302 | * "errmsg": "success", 303 | * "shelf_info": { 304 | * "module_infos": [...] 305 | * }, 306 | * "shelf_banner": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2ibp2DgDXiaic6WdflMpNdInS8qUia2BztlPu1gPlCDLZXEjia2qBdjoLiaCGUno9zbs1UyoqnaTJJGeEew/0", 307 | * "shelf_name": "新建货架", 308 | * "shelf_id": 97 309 | * } 310 | * ``` 311 | * @param {String} shelfId 货架Id 312 | * @param {Function} callback 回调函数 313 | */ 314 | exports.getShelfById = function (shelfId, callback) { 315 | this.preRequest(this._getShelfById, arguments); 316 | }; 317 | 318 | /*! 319 | * 根据货架ID获取货架信息的未封装版本 320 | */ 321 | exports._getShelfById = function (shelfId, callback) { 322 | var data = { 323 | 'shelf_id': shelfId 324 | }; 325 | var url = this.endpoint + '/merchant/shelf/getbyid?access_token=' + this.token.accessToken; 326 | this.request(url, postJSON(data), wrapper(callback)); 327 | }; 328 | -------------------------------------------------------------------------------- /lib/api_shop_stock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 库存管理接口 4 | var util = require('./util'); 5 | var wrapper = util.wrapper; 6 | var postJSON = util.postJSON; 7 | 8 | /** 9 | * 增加库存 10 | * 详细请看: 11 | * Examples: 12 | * ``` 13 | * api.updateStock(10, productId, sku, callback); // 增加10件库存 14 | * api.updateStock(-10, productId, sku, callback); // 减少10件库存 15 | * ``` 16 | * Callback: 17 | * 18 | * - `err`, 调用失败时得到的异常 19 | * - `result`, 调用正常时得到的对象 20 | * 21 | * Result: 22 | * ``` 23 | * { 24 | * "errcode": 0, 25 | * "errmsg": "success" 26 | * } 27 | * ``` 28 | * @param {Number} number 增加或者删除的数量 29 | * @param {String} productId 商品ID 30 | * @param {String} sku SKU信息 31 | * @param {Function} callback 回调函数 32 | */ 33 | exports.updateStock = function (number, productId, sku, callback) { 34 | this.preRequest(this._updateStock, arguments); 35 | }; 36 | 37 | /*! 38 | * 更新商品库存的未封装版本 39 | */ 40 | exports._updateStock = function (number, productId, sku, callback) { 41 | var url; 42 | if (number > 0) { 43 | url = this.endpoint + '/merchant/stock/add?access_token=' + this.token.accessToken; 44 | } else { 45 | url = this.endpoint + '/merchant/stock/reduce?access_token=' + this.token.accessToken; 46 | } 47 | var data = { 48 | 'product_id': productId, 49 | 'sku_info': sku, 50 | 'quantity': Math.abs(number) 51 | }; 52 | this.request(url, postJSON(data), wrapper(callback)); 53 | }; 54 | -------------------------------------------------------------------------------- /lib/api_tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | /** 7 | * 创建标签 8 | * 详情请见: 9 | * Examples: 10 | * ``` 11 | * api.createTag(callback); 12 | * ``` 13 | * Callback: 14 | * 15 | * - `err`, 调用失败时得到的异常 16 | * - `result`, 调用正常时得到的对象 17 | * 18 | * Result: 19 | * ``` 20 | * { 21 | * "tag": [ 22 | * "id": 134, // 标签id 23 | * "name": "广东" 24 | * ] 25 | * } 26 | * ``` 27 | * @param {String} name tag name 28 | * @param {Function} callback 回调函数 29 | */ 30 | exports.createTag = function (name, callback) { 31 | this.preRequest(this._createTag, arguments); 32 | }; 33 | 34 | /*! 35 | * 创建标签的未封装版本 36 | */ 37 | exports._createTag = function (name, callback) { 38 | // https://api.weixin.qq.com/cgi-bin/tags/create?access_token=ACCESS_TOKEN 39 | var url = this.endpoint + '/cgi-bin/tags/create?access_token=' + this.token.accessToken; 40 | var data = { 41 | 'tag': { 42 | 'name': name 43 | } 44 | }; 45 | this.request(url, postJSON(data), wrapper(callback)); 46 | }; 47 | 48 | /** 49 | * 获取公众号已创建的标签 50 | * 详情请见: 51 | * Examples: 52 | * ``` 53 | * api.getTags(callback); 54 | * ``` 55 | * Callback: 56 | * 57 | * - `err`, 调用失败时得到的异常 58 | * - `result`, 调用正常时得到的对象 59 | * 60 | * Result: 61 | * ``` 62 | * { 63 | * "tags":[{ 64 | * 'id':1, 65 | * 'name': '黑名单', 66 | * 'count': 0 // 此标签下粉丝数 67 | * },{ 68 | * 'id':2, 69 | * 'name': '星标组', 70 | * 'count':0 71 | * }] 72 | * } 73 | * ``` 74 | * @param {String} openid Open ID 75 | * @param {Function} callback 回调函数 76 | */ 77 | exports.getTags = function (callback) { 78 | this.preRequest(this._getTags, arguments); 79 | }; 80 | 81 | /*! 82 | * 获取公众号已创建的标签的未封装版本 83 | */ 84 | exports._getTags = function (callback) { 85 | // https://api.weixin.qq.com/cgi-bin/tags/get?access_token=ACCESS_TOKEN 86 | var url = this.endpoint + '/cgi-bin/tags/get?access_token=' + this.token.accessToken; 87 | this.request(url, {dataType: 'json'}, wrapper(callback)); 88 | }; 89 | 90 | /** 91 | * 编辑标签 92 | * 详情请见: 93 | * Examples: 94 | * ``` 95 | * api.editTag(id, name, callback); 96 | * ``` 97 | * Callback: 98 | * 99 | * - `err`, 调用失败时得到的异常 100 | * - `result`, 调用正常时得到的对象 101 | * 102 | * Result: 103 | * ``` 104 | * {"errcode": 0, "errmsg": "ok"}} 105 | * ``` 106 | * @param {Number} id 标签ID 107 | * @param {String} name 标签新名字 108 | * @param {Function} callback 回调函数 109 | */ 110 | exports.editTag = function (id, name, callback) { 111 | this.preRequest(this._editTag, arguments); 112 | }; 113 | 114 | /*! 115 | * 编辑标签的未封装版本 116 | */ 117 | exports._editTag = function (id, name, callback) { 118 | // https://api.weixin.qq.com/cgi-bin/tags/update?access_token=ACCESS_TOKEN 119 | // POST数据格式:json 120 | // POST数据例子:{"tag":{"id":134, "name":"test"}} 121 | var url = this.endpoint + '/cgi-bin/tags/update?access_token=' + this.token.accessToken; 122 | var data = { 123 | 'tag': { 124 | 'id': id, 125 | 'name': name 126 | } 127 | }; 128 | this.request(url, postJSON(data), wrapper(callback)); 129 | }; 130 | 131 | /** 132 | * 删除标签 133 | * 详情请见: 134 | * Examples: 135 | * ``` 136 | * api.deleteTag(id, callback); 137 | * ``` 138 | * Callback: 139 | * 140 | * - `err`, 调用失败时得到的异常 141 | * - `result`, 调用正常时得到的对象 142 | * 143 | * Result: 144 | * ``` 145 | * {"errcode": 0, "errmsg": "ok"} 146 | * ``` 147 | * @param {Number} id 标签ID 148 | * @param {Function} callback 回调函数 149 | */ 150 | exports.deleteTag = function (id, callback) { 151 | this.preRequest(this._deleteTag, arguments); 152 | }; 153 | 154 | /*! 155 | * 删除标签的未封装版本 156 | */ 157 | exports._deleteTag = function (id, callback) { 158 | // http请求方式: POST(请使用https协议) 159 | // https://api.weixin.qq.com/cgi-bin/tags/delete?access_token=ACCESS_TOKEN 160 | // POST数据格式:json 161 | // POST数据例子:{"tag":{"id":108}} 162 | var url = this.endpoint + '/cgi-bin/tags/delete?access_token=' + this.token.accessToken; 163 | var data = { 164 | 'tag': {'id': id} 165 | }; 166 | this.request(url, postJSON(data), wrapper(callback)); 167 | }; 168 | 169 | /** 170 | * 获取标签下粉丝列表 171 | * 详情请见: 172 | * Examples: 173 | * ``` 174 | * api.getTagUsers(tagid, openid, callback); 175 | * ``` 176 | * Callback: 177 | * 178 | * - `err`, 调用失败时得到的异常 179 | * - `result`, 调用正常时得到的对象 180 | * 181 | * Result: 182 | * ``` 183 | * { 184 | * "count": 2, 185 | * "data":{ 186 | * "openid":[ 187 | * ... 188 | * ] 189 | * }, 190 | * "next_openid": "..." 191 | * } 192 | * ``` 193 | * @param {Number} tagId 标签ID 194 | * @param {String} openid 分页起始openid 195 | * @param {Function} callback 回调函数 196 | */ 197 | exports.getTagUsers = function (tagId, openid, callback) { 198 | this.preRequest(this._getTagUsers, arguments); 199 | }; 200 | 201 | /*! 202 | * 获取标签下粉丝列表的未封装版本 203 | */ 204 | exports._getTagUsers = function (tagId, openid, callback) { 205 | // http请求方式: POST(请使用https协议) 206 | // https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=ACCESS_TOKEN 207 | // POST数据格式:json 208 | // POST数据例子:{"tagid":108, "next_openid":"oDF3iYx0ro3_7jD4HFRDfrjdCM58"} 209 | var url = this.endpoint + '/cgi-bin/user/tag/get?access_token=' + this.token.accessToken; 210 | var data = { 211 | 'tagid': tagId, 212 | 'next_openid': openid 213 | }; 214 | this.request(url, postJSON(data), wrapper(callback)); 215 | }; 216 | 217 | /** 218 | * 批量为用户打标签 219 | * 详情请见: 220 | * Examples: 221 | * ``` 222 | * api.memberBatchtagging(tagId, openList, callback); 223 | * ``` 224 | * Callback: 225 | * 226 | * - `err`, 调用失败时得到的异常 227 | * - `result`, 调用正常时得到的对象 228 | * 229 | * Result: 230 | * ``` 231 | * {"errcode": 0, "errmsg": "ok"} 232 | * ``` 233 | * @param {Number} tagId 标签ID 234 | * @param {Array} openList 用户openids 235 | * @param {Function} callback 回调函数 236 | */ 237 | exports.membersBatchtagging = function (tagId, openList, callback) { 238 | this.preRequest(this._membersBatchtagging, arguments); 239 | }; 240 | 241 | /*! 242 | * 批量为用户打标签的未封装版本 243 | */ 244 | exports._membersBatchtagging = function (tagId, openList, callback) { 245 | // https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging?access_token=ACCESS_TOKEN 246 | var url = this.endpoint + '/cgi-bin/tags/members/batchtagging?access_token=' + this.token.accessToken; 247 | var data = { 248 | 'openid_list':openList, 249 | 'tagid': tagId 250 | }; 251 | this.request(url, postJSON(data), wrapper(callback)); 252 | }; 253 | 254 | /** 255 | * 批量为用户取消标签 256 | * 详情请见: 257 | * Examples: 258 | * ``` 259 | * api.memberBatchuntagging(tagId, openList, callback); 260 | * ``` 261 | * Callback: 262 | * 263 | * - `err`, 调用失败时得到的异常 264 | * - `result`, 调用正常时得到的对象 265 | * 266 | * Result: 267 | * ``` 268 | * {"errcode": 0, "errmsg": "ok"} 269 | * ``` 270 | * @param {Number} tagId 标签ID 271 | * @param {Array} openList 用户openids 272 | * @param {Function} callback 回调函数 273 | */ 274 | exports.membersBatchuntagging = function (tagId, openList, callback) { 275 | this.preRequest(this._membersBatchuntagging, arguments); 276 | }; 277 | 278 | /*! 279 | * 批量为用户取消标签的未封装版本 280 | */ 281 | exports._membersBatchuntagging = function (tagId, openList, callback) { 282 | // https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging?access_token=ACCESS_TOKEN 283 | var url = this.endpoint + '/cgi-bin/tags/members/batchuntagging?access_token=' + this.token.accessToken; 284 | var data = { 285 | 'openid_list':openList, 286 | 'tagid': tagId 287 | }; 288 | this.request(url, postJSON(data), wrapper(callback)); 289 | }; 290 | 291 | /** 292 | * 获取用户身上的标签列表 293 | * 详情请见: 294 | * Examples: 295 | * ``` 296 | * api.getUserTags(openid, callback); 297 | * ``` 298 | * Callback: 299 | * 300 | * - `err`, 调用失败时得到的异常 301 | * - `result`, 调用正常时得到的对象 302 | * 303 | * Result: 304 | * ``` 305 | * {"tagid_list": [134, 2]} 306 | * ``` 307 | * @param {openid} 用户openid 308 | * @param {Function} callback 回调函数 309 | */ 310 | exports.getUserTags = function (openid, callback) { 311 | this.preRequest(this._getUserTags, arguments); 312 | }; 313 | 314 | /*! 315 | * 获取用户身上的标签列表的未封装版本 316 | */ 317 | exports._getUserTags = function (openid, callback) { 318 | // https://api.weixin.qq.com/cgi-bin/tags/getidlist?access_token=ACCESS_TOKEN 319 | var url = this.endpoint + '/cgi-bin/tags/getidlist?access_token=' + this.token.accessToken; 320 | var data = { 321 | 'openid':openid 322 | }; 323 | this.request(url, postJSON(data), wrapper(callback)); 324 | }; 325 | 326 | -------------------------------------------------------------------------------- /lib/api_template.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 设置所属行业 9 | * Examples: 10 | * ``` 11 | * var industryIds = { 12 | * "industry_id1":'1', 13 | * "industry_id2":"4" 14 | * }; 15 | * api.setIndustry(industryIds, callback); 16 | * ``` 17 | * Callback: 18 | * 19 | * - `err`, 调用失败时得到的异常 20 | * - `result`, 调用正常时得到的对象 21 | * 22 | * @param {Object} industryIds 公众号模板消息所属行业编号 23 | */ 24 | exports.setIndustry = function(industryIds, callback){ 25 | this.preRequest(this._setIndustry, arguments); 26 | }; 27 | 28 | exports._setIndustry = function (industryIds, callback) { 29 | var apiUrl = this.endpoint + '/cgi-bin/template/api_set_industry?access_token=' + this.token.accessToken; 30 | this.request(apiUrl, postJSON(industryIds), wrapper(callback)); 31 | }; 32 | 33 | /** 34 | * 获取设置的行业信息 35 | * Examples: 36 | * ``` 37 | * api.getIndustry(callback); 38 | * ``` 39 | * Callback: 40 | * 41 | * - `err`, 调用失败时得到的异常 42 | * - `result`, 调用正常时得到的对象 43 | * 44 | * Result: 45 | * ``` 46 | * // 结果示例 47 | * { 48 | * "primary_industry":{"first_class":"运输与仓储","second_class":"快递"}, 49 | * "secondary_industry":{"first_class":"IT科技","second_class":"互联网|电子商务"} 50 | * } 51 | * ``` 52 | * @param {Function} callback 回调函数 53 | */ 54 | exports.getIndustry = function(callback){ 55 | this.preRequest(this._getIndustry, arguments); 56 | }; 57 | 58 | exports._getIndustry = function (callback) { 59 | var apiUrl = this.endpoint + '/cgi-bin/template/get_industry?access_token=' + this.token.accessToken; 60 | this.request(apiUrl, {dataType: 'json'}, wrapper(callback)); 61 | }; 62 | 63 | /** 64 | * 获得模板ID 65 | * Examples: 66 | * ``` 67 | * var templateIdShort = 'TM00015'; 68 | * api.addTemplate(templateIdShort, callback); 69 | * ``` 70 | * Callback: 71 | * 72 | * - `err`, 调用失败时得到的异常 73 | * - `result`, 调用正常时得到的对象 74 | * 75 | * @param {String} templateIdShort 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式 76 | */ 77 | exports.addTemplate = function(templateIdShort, callback){ 78 | this.preRequest(this._addTemplate, arguments); 79 | }; 80 | 81 | exports._addTemplate = function (templateIdShort, callback) { 82 | var apiUrl = this.endpoint + '/cgi-bin/template/api_add_template?access_token=' + this.token.accessToken; 83 | var templateId = { 84 | template_id_short: templateIdShort 85 | }; 86 | this.request(apiUrl, postJSON(templateId), wrapper(callback)); 87 | }; 88 | 89 | /** 90 | * 获取模板列表 91 | * Examples: 92 | * ``` 93 | * api.getAllPrivateTemplate(callback); 94 | * ``` 95 | * Callback: 96 | * 97 | * - `err`, 调用失败时得到的异常 98 | * - `result`, 调用正常时得到的对象 99 | * 100 | * Result: 101 | * ``` 102 | * // 结果示例 103 | * { 104 | * "template_list": [{ 105 | * "template_id": "iPk5sOIt5X_flOVKn5GrTFpncEYTojx6ddbt8WYoV5s", 106 | * "title": "领取奖金提醒", 107 | * "primary_industry": "IT科技", 108 | * "deputy_industry": "互联网|电子商务", 109 | * "content": "{ {result.DATA} }\n\n领奖金额:{ {withdrawMoney.DATA} }\n领奖 时间:{ {withdrawTime.DATA} }\n银行信息:{ {cardInfo.DATA} }\n到账时间: { {arrivedTime.DATA} }\n{ {remark.DATA} }", 110 | * "example": "您已提交领奖申请\n\n领奖金额:xxxx元\n领奖时间:2013-10-10 12:22:22\n银行信息:xx银行(尾号xxxx)\n到账时间:预计xxxxxxx\n\n预计将于xxxx到达您的银行卡" 111 | * }] 112 | * } 113 | * ``` 114 | * @param {Function} callback 回调函数 115 | */ 116 | exports.getAllPrivateTemplate = function(callback){ 117 | this.preRequest(this._getAllPrivateTemplate, arguments); 118 | }; 119 | 120 | exports._getAllPrivateTemplate = function (callback) { 121 | var apiUrl = this.endpoint + '/cgi-bin/template/get_all_private_template?access_token=' + this.token.accessToken; 122 | this.request(apiUrl, {dataType: 'json'}, wrapper(callback)); 123 | }; 124 | 125 | /** 126 | * 删除模板 127 | * Examples: 128 | * ``` 129 | * var templateId = ”Dyvp3-Ff0cnail_CDSzk1fIc6-9lOkxsQE7exTJbwUE” 130 | * api.delPrivateTemplate(templateId, callback); 131 | * ``` 132 | * Callback: 133 | * 134 | * - `err`, 调用失败时得到的异常 135 | * - `result`, 调用正常时得到的对象 136 | * 137 | * @param {String} templateId 公众帐号下模板消息ID 138 | */ 139 | exports.delPrivateTemplate = function(templateId, callback){ 140 | this.preRequest(this._delPrivateTemplate, arguments); 141 | }; 142 | 143 | exports._delPrivateTemplate = function (templateId, callback) { 144 | var apiUrl = this.endpoint + '/cgi-bin/template/del_private_template?access_token=' + this.token.accessToken; 145 | var data = { 146 | template_id: templateId 147 | }; 148 | this.request(apiUrl, postJSON(data), wrapper(callback)); 149 | }; 150 | 151 | /** 152 | * 发送模板消息 153 | * 详细细节: http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html 154 | * Examples: 155 | * ``` 156 | * var templateId: '模板id'; 157 | * // URL置空,则在发送后,点击模板消息会进入一个空白页面(ios), 或无法点击(android) 158 | * var url: 'http://weixin.qq.com/download'; 159 | * var data = { 160 | * "first": { 161 | * "value":"恭喜你购买成功!", 162 | * "color":"#173177" 163 | * }, 164 | * "keyword1":{ 165 | * "value":"巧克力", 166 | * "color":"#173177" 167 | * }, 168 | * "keyword2": { 169 | * "value":"39.8元", 170 | * "color":"#173177" 171 | * }, 172 | * "keyword3": { 173 | * "value":"2014年9月22日", 174 | * "color":"#173177" 175 | * }, 176 | * "remark":{ 177 | * "value":"欢迎再次购买!", 178 | * "color":"#173177" 179 | * } 180 | * }; 181 | * api.sendTemplate('openid', templateId, dest, data, callback); 182 | * ``` 183 | * Callback: 184 | * 185 | * - `err`, 调用失败时得到的异常 186 | * - `result`, 调用正常时得到的对象 187 | * 188 | * @param {String} openid 用户的openid 189 | * @param {String} templateId 模板ID 190 | * @param {Object} dest 跳转目的地置空,则在发送后,点击模板消息会进入一个空白页面(ios),或无法点击(android) 191 | * @param {Object} data 渲染模板的数据 192 | * @param {Function} callback 回调函数 193 | */ 194 | exports.sendTemplate = function (openid, templateId, dest, data, callback, callback2) { 195 | this.preRequest(this._sendTemplate, arguments); 196 | }; 197 | 198 | exports._sendTemplate = function (openid, templateId, dest, data, callback, callback2) { 199 | /* 200 | * duplicated interface `function (openid, templateId, url, topColor, data, callback)` 201 | */ 202 | var apiUrl = this.endpoint + '/cgi-bin/message/template/send?access_token=' + this.token.accessToken; 203 | 204 | if (typeof data === 'string') { 205 | data = callback; 206 | callback = callback2; 207 | } 208 | 209 | var url, miniprogram; 210 | if (typeof dest === 'string') { 211 | url = dest; 212 | } else { 213 | if (!dest) { 214 | dest = {}; 215 | } 216 | url = dest.url; 217 | miniprogram = dest.miniprogram; 218 | } 219 | 220 | var template = { 221 | touser: openid, 222 | template_id: templateId, 223 | url: url, 224 | miniprogram: miniprogram, 225 | data: data 226 | }; 227 | this.request(apiUrl, postJSON(template), wrapper(callback)); 228 | }; 229 | -------------------------------------------------------------------------------- /lib/api_url.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 短网址服务 9 | * 详细细节 http://mp.weixin.qq.com/wiki/10/165c9b15eddcfbd8699ac12b0bd89ae6.html 10 | * Examples: 11 | * ``` 12 | * api.shorturl('http://mp.weixin.com', callback); 13 | * ``` 14 | * Callback: 15 | * 16 | * - `err`, 调用失败时得到的异常 17 | * - `result`, 调用正常时得到的对象 18 | * 19 | * @param {String} longUrl 需要转换的长链接,支持http://、https://、weixin://wxpay格式的url 20 | * @param {Function} callback 回调函数 21 | */ 22 | exports.shorturl = function (longUrl, callback) { 23 | this.preRequest(this._shorturl, arguments); 24 | }; 25 | 26 | /*! 27 | * 短网址服务 28 | */ 29 | exports._shorturl = function (longUrl, callback) { 30 | // https://api.weixin.qq.com/cgi-bin/shorturl?access_token=ACCESS_TOKEN 31 | var url = this.endpoint + '/cgi-bin/shorturl?access_token=' + this.token.accessToken; 32 | var data = { 33 | 'action': 'long2short', 34 | 'long_url': longUrl 35 | }; 36 | this.request(url, postJSON(data), wrapper(callback)); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/api_user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | var wrapper = util.wrapper; 5 | var postJSON = util.postJSON; 6 | 7 | /** 8 | * 获取用户基本信息。可以设置lang,其中zh_CN 简体,zh_TW 繁体,en 英语。默认为en 9 | * 详情请见: 10 | * Examples: 11 | * ``` 12 | * api.getUser(openid, callback); 13 | * api.getUser({openid: 'openid', lang: 'en'}, callback); 14 | * ``` 15 | * Callback: 16 | * 17 | * - `err`, 调用失败时得到的异常 18 | * - `result`, 调用正常时得到的对象 19 | * 20 | * Result: 21 | * ``` 22 | * { 23 | * "subscribe": 1, 24 | * "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", 25 | * "nickname": "Band", 26 | * "sex": 1, 27 | * "language": "zh_CN", 28 | * "city": "广州", 29 | * "province": "广东", 30 | * "country": "中国", 31 | * "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0", 32 | * "subscribe_time": 1382694957 33 | * } 34 | * ``` 35 | * @param {String|Object} options 用户的openid。或者配置选项,包含openid和lang两个属性。 36 | * @param {Function} callback 回调函数 37 | */ 38 | exports.getUser = function (options, callback) { 39 | this.preRequest(this._getUser, arguments); 40 | }; 41 | 42 | /*! 43 | * 获取用户基本信息的未封装版本 44 | */ 45 | exports._getUser = function (options, callback) { 46 | if (typeof options !== 'object') { 47 | options = { 48 | openid: options, 49 | lang: 'en' 50 | }; 51 | } 52 | // https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID 53 | var url = this.endpoint + '/cgi-bin/user/info?openid=' + options.openid + '&lang=' + options.lang + '&access_token=' + this.token.accessToken; 54 | this.request(url, {dataType: 'json'}, wrapper(callback)); 55 | }; 56 | 57 | /** 58 | * 批量获取用户基本信息 59 | * Example: 60 | * ``` 61 | * api.batchGetUsers(['openid1', 'openid2'], callback) 62 | * ``` 63 | * Callback: 64 | * 65 | * - `err`, 调用失败时得到的异常 66 | * - `result`, 调用正常时得到的对象 67 | * 68 | * Result: 69 | * ``` 70 | * { 71 | * "user_info_list": [{ 72 | * "subscribe": 1, 73 | * "openid": "otvxTs4dckWG7imySrJd6jSi0CWE", 74 | * "nickname": "iWithery", 75 | * "sex": 1, 76 | * "language": "zh_CN", 77 | * "city": "Jieyang", 78 | * "province": "Guangdong", 79 | * "country": "China", 80 | * "headimgurl": "http://wx.qlogo.cn/mmopen/xbIQx1GRqdvyqkMMhEaGOX802l1CyqMJNgUzKP8MeAeHFicRDSnZH7FY4XB7p8XHXIf6uJA2SCunTPicGKezDC4saKISzRj3nz/0", 81 | * "subscribe_time": 1434093047, 82 | * "unionid": "oR5GjjgEhCMJFyzaVZdrxZ2zRRF4", 83 | * "remark": "", 84 | * "groupid": 0 85 | * }, { 86 | * "subscribe": 0, 87 | * "openid": "otvxTs_JZ6SEiP0imdhpi50fuSZg", 88 | * "unionid": "oR5GjjjrbqBZbrnPwwmSxFukE41U", 89 | * }] 90 | * } 91 | * ``` 92 | * @param {Array} openids 用户的openid数组。 93 | * @param {Function} callback 回调函数 94 | */ 95 | exports.batchGetUsers = function (openids, callback) { 96 | this.preRequest(this._batchGetUsers, arguments); 97 | }; 98 | 99 | /*! 100 | * 批量获取用户基本信息的未封装版本 101 | */ 102 | exports._batchGetUsers = function (openids, callback) { 103 | var url = this.endpoint + '/cgi-bin/user/info/batchget?access_token=' + this.token.accessToken; 104 | var data = {}; 105 | data.user_list = openids.map(function (openid) { 106 | return {openid: openid, language: 'zh-CN'}; 107 | }); 108 | this.request(url, postJSON(data), wrapper(callback)); 109 | }; 110 | 111 | /** 112 | * 获取关注者列表 113 | * 详细细节 http://mp.weixin.qq.com/wiki/0/d0e07720fc711c02a3eab6ec33054804.html 114 | * Examples: 115 | * ``` 116 | * api.getFollowers(callback); 117 | * // or 118 | * api.getFollowers(nextOpenid, callback); 119 | * ``` 120 | * Callback: 121 | * 122 | * - `err`, 调用失败时得到的异常 123 | * - `result`, 调用正常时得到的对象 124 | * 125 | * Result: 126 | * ``` 127 | * { 128 | * "total":2, 129 | * "count":2, 130 | * "data":{ 131 | * "openid":["","OPENID1","OPENID2"] 132 | * }, 133 | * "next_openid":"NEXT_OPENID" 134 | * } 135 | * ``` 136 | * @param {String} nextOpenid 调用一次之后,传递回来的nextOpenid。第一次获取时可不填 137 | * @param {Function} callback 回调函数 138 | */ 139 | exports.getFollowers = function (nextOpenid, callback) { 140 | this.preRequest(this._getFollowers, arguments); 141 | }; 142 | 143 | /*! 144 | * 获取关注者列表的未封装版本 145 | */ 146 | exports._getFollowers = function (nextOpenid, callback) { 147 | // https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID 148 | if (typeof nextOpenid === 'function') { 149 | callback = nextOpenid; 150 | nextOpenid = ''; 151 | } 152 | var url = this.endpoint + '/cgi-bin/user/get?next_openid=' + nextOpenid + '&access_token=' + this.token.accessToken; 153 | this.request(url, {dataType: 'json'}, wrapper(callback)); 154 | }; 155 | 156 | /** 157 | * 设置用户备注名 158 | * 详细细节 http://mp.weixin.qq.com/wiki/1/4a566d20d67def0b3c1afc55121d2419.html 159 | * Examples: 160 | * ``` 161 | * api.updateRemark(openid, remark, callback); 162 | * ``` 163 | * Callback: 164 | * 165 | * - `err`, 调用失败时得到的异常 166 | * - `result`, 调用正常时得到的对象 167 | * 168 | * Result: 169 | * ``` 170 | * { 171 | * "errcode":0, 172 | * "errmsg":"ok" 173 | * } 174 | * ``` 175 | * @param {String} openid 用户的openid 176 | * @param {String} remark 新的备注名,长度必须小于30字符 177 | * @param {Function} callback 回调函数 178 | */ 179 | exports.updateRemark = function (openid, remark, callback) { 180 | this.preRequest(this._updateRemark, arguments); 181 | }; 182 | 183 | /*! 184 | * 设置用户备注名的未封装版本 185 | */ 186 | exports._updateRemark = function (openid, remark, callback) { 187 | // https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=ACCESS_TOKEN 188 | var url = this.endpoint + '/cgi-bin/user/info/updateremark?access_token=' + this.token.accessToken; 189 | var data = { 190 | openid: openid, 191 | remark: remark 192 | }; 193 | this.request(url, postJSON(data), wrapper(callback)); 194 | }; 195 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! 4 | * 对返回结果的一层封装,如果遇见微信返回的错误,将返回一个错误 5 | * 参见:http://mp.weixin.qq.com/wiki/17/fa4e1434e57290788bde25603fa2fcbd.html 6 | */ 7 | exports.wrapper = function (callback) { 8 | return function (err, data, res) { 9 | callback = callback || function () {}; 10 | if (err) { 11 | err.name = 'WeChatAPI' + err.name; 12 | return callback(err, data, res); 13 | } 14 | if (data && data.errcode) { 15 | err = new Error(data.errmsg); 16 | err.name = 'WeChatAPIError'; 17 | err.code = data.errcode; 18 | return callback(err, data, res); 19 | } 20 | if (data == null) { 21 | err = new Error('No data received.'); 22 | err.name = 'WeChatAPIError'; 23 | err.code = -1; 24 | return callback(err, data, res); 25 | } 26 | callback(null, data, res); 27 | }; 28 | }; 29 | 30 | /*! 31 | * 对提交参数一层封装,当POST JSON,并且结果也为JSON时使用 32 | */ 33 | exports.postJSON = function (data) { 34 | return { 35 | dataType: 'json', 36 | type: 'POST', 37 | data: data, 38 | headers: { 39 | 'Content-Type': 'application/json' 40 | } 41 | }; 42 | }; 43 | 44 | exports.make = function (host, name, fn) { 45 | host[name] = function () { 46 | this.preRequest(this['_' + name], arguments); 47 | }; 48 | host['_' + name] = fn; 49 | }; 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechat-api", 3 | "version": "1.35.1", 4 | "description": "微信公共平台Node库 API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "make test-all" 8 | }, 9 | "config": { 10 | "travis-cov": { 11 | "threshold": 98 12 | } 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/node-webot/wechat-api.git" 17 | }, 18 | "keywords": [ 19 | "weixin", 20 | "wechat" 21 | ], 22 | "dependencies": { 23 | "formstream": ">=1.0.0", 24 | "urllib": "2.21.0" 25 | }, 26 | "devDependencies": { 27 | "mocha": "*", 28 | "expect.js": "*", 29 | "travis-cov": "*", 30 | "coveralls": "*", 31 | "mocha-lcov-reporter": "*", 32 | "muk": "*", 33 | "rewire": "*", 34 | "istanbul": "*" 35 | }, 36 | "author": "Jackson Tian", 37 | "license": "MIT", 38 | "readmeFilename": "README.md", 39 | "directories": { 40 | "test": "test" 41 | }, 42 | "files": [ 43 | "lib", 44 | "index.js" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /test/api_card.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_card', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | 10 | describe('getApplyProtocol', function () { 11 | it('should ok', function (done) { 12 | api.getApplyProtocol(function (err, data, res) { 13 | expect(err).to.not.be.ok(); 14 | expect(data).to.have.property('category'); 15 | expect(data).to.have.property('errcode', 0); 16 | expect(data).to.have.property('errmsg', 'ok'); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | 22 | var options = { 23 | 'brand_name': 'aaaaaa', 24 | // "app_id":"xxxxxxxxxxx", 25 | 'logo_url': 'http://mmbiz.qpic.cn/mmbiz/O1DymY4NpO88CjYk0XWw9VAW99RMibqchv2OVDOibPpmMu65H47usx4fjyRwvRaZwCccibCiccMgwPk9unibewSQfjw/0?wx_fmt=jpeg', 26 | 'protocol': 'xxxxxxxxxx', 27 | 'end_time': Math.round(Date.now()/1000+3600*24*365), 28 | 'primary_category_id': 1, 29 | 'secondary_category_id': 101 30 | }; 31 | 32 | describe('submerchant', function(){ 33 | before(function(done){ 34 | api.uploadMedia('./test/fixture/image.jpg', 'image', function (err, data, res) { 35 | options.protocol = data.media_id; 36 | done(err); 37 | }); 38 | }); 39 | 40 | describe('submit', function(){ 41 | it('should ok', function(done){ 42 | api.submitSubmerchant(options, function (err, data, res) { 43 | expect(err).to.not.be.ok(); 44 | expect(data).to.have.property('info'); 45 | expect(data.info).to.have.property('merchant_id'); 46 | done(); 47 | }); 48 | }); 49 | }); 50 | 51 | describe('update', function(){ 52 | it('should ok', function(done){ 53 | options.merchant_id = options.merchant_id||408592871; 54 | api.updateSubmerchant(options, function (err, data, res) { 55 | expect(err).to.not.be.ok(); 56 | expect(data).to.have.property('info'); 57 | expect(data.info).to.have.property('merchant_id', options.merchant_id); 58 | done(); 59 | }); 60 | }); 61 | }); 62 | 63 | describe('get', function(){ 64 | it('should ok', function(done){ 65 | options.merchant_id = options.merchant_id||408592871; 66 | api.getSubmerchant(options.merchant_id, function (err, data, res) { 67 | expect(err).to.not.be.ok(); 68 | expect(data).to.have.property('info'); 69 | expect(data.info).to.have.property('merchant_id', options.merchant_id); 70 | done(); 71 | }); 72 | }); 73 | }); 74 | 75 | describe('batchget', function(){ 76 | it('should ok', function(done){ 77 | api.batchgetSubmerchant({'begin_id': 0,'limit': 50}, function (err, data) { 78 | expect(err).to.not.be.ok(); 79 | expect(data).to.have.property('info_list'); 80 | expect(data.info_list[0]).to.have.property('merchant_id', options.merchant_id); 81 | done(); 82 | }); 83 | }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/api_common.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var API = require('../'); 4 | var urllib = require('urllib'); 5 | var muk = require('muk'); 6 | var expect = require('expect.js'); 7 | var config = require('./config'); 8 | 9 | describe('api_common', function () { 10 | describe('isAccessTokenValid', function () { 11 | it('should invalid', function () { 12 | var token = new API.AccessToken('token', new Date().getTime() - 7200 * 1000); 13 | expect(token.isValid()).not.to.be.ok(); 14 | }); 15 | 16 | it('should valid', function () { 17 | var token = new API.AccessToken('token', new Date().getTime() + 7200 * 1000); 18 | expect(token.isValid()).to.be.ok(); 19 | }); 20 | }); 21 | 22 | describe('mixin', function () { 23 | it('should ok', function () { 24 | API.mixin({sayHi: function () {}}); 25 | expect(API.prototype).to.have.property('sayHi'); 26 | }); 27 | 28 | it('should not ok when override method', function () { 29 | var obj = {sayHi: function () {}}; 30 | expect(API.mixin).withArgs(obj).to.throwException(/Don't allow override existed prototype method\./); 31 | }); 32 | }); 33 | 34 | describe('getAccessToken', function () { 35 | it('should ok', function (done) { 36 | var api = new API(config.appid, config.appsecret); 37 | api.getAccessToken(function (err, token) { 38 | expect(err).not.to.be.ok(); 39 | expect(token).to.only.have.keys('accessToken', 'expireTime'); 40 | done(); 41 | }); 42 | }); 43 | 44 | it('should not ok', function (done) { 45 | var api = new API('appid', 'secret'); 46 | api.getAccessToken(function (err, token) { 47 | expect(err).to.be.ok(); 48 | expect(err).to.have.property('name', 'WeChatAPIError'); 49 | expect(err).to.have.property('message', 'invalid credential'); 50 | done(); 51 | }); 52 | }); 53 | 54 | describe('mock urllib err', function () { 55 | before(function () { 56 | muk(urllib, 'request', function (url, args, callback) { 57 | var err = new Error('Urllib Error'); 58 | err.name = 'UrllibError'; 59 | callback(err); 60 | }); 61 | }); 62 | 63 | after(function () { 64 | muk.restore(); 65 | }); 66 | 67 | it('should get mock error', function (done) { 68 | var api = new API('appid', 'secret'); 69 | api.getAccessToken(function (err, token) { 70 | expect(err).to.be.ok(); 71 | expect(err).to.have.property('name', 'WeChatAPIUrllibError'); 72 | expect(err).to.have.property('message', 'Urllib Error'); 73 | done(); 74 | }); 75 | }); 76 | }); 77 | 78 | describe('mock token', function () { 79 | before(function () { 80 | muk(urllib, 'request', function (url, args, callback) { 81 | process.nextTick(function () { 82 | callback(null, {'access_token': 'ACCESS_TOKEN','expires_in': 7200}); 83 | }); 84 | }); 85 | }); 86 | after(function () { 87 | muk.restore(); 88 | }); 89 | 90 | it('should ok', function (done) { 91 | var api = new API('appid', 'secret'); 92 | api.getAccessToken(function (err, token) { 93 | expect(err).not.to.be.ok(); 94 | expect(token).to.have.property('accessToken', 'ACCESS_TOKEN'); 95 | // token.should.have.property('expireTime', 7200); 96 | done(); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('mock saveToken err', function () { 102 | var api = new API(config.appid, config.appsecret); 103 | before(function () { 104 | muk(api, 'saveToken', function (token, callback) { 105 | process.nextTick(function () { 106 | callback(new Error('mock saveToken err')); 107 | }); 108 | }); 109 | }); 110 | after(function () { 111 | muk.restore(); 112 | }); 113 | 114 | it('should ok', function (done) { 115 | api.getAccessToken(function (err, token) { 116 | expect(err).to.be.ok(); 117 | expect(err).to.have.property('message', 'mock saveToken err'); 118 | done(); 119 | }); 120 | }); 121 | }); 122 | }); 123 | 124 | describe('preRequest', function () { 125 | it('should ok', function (done) { 126 | var api = new API(config.appid, config.appsecret); 127 | api.preRequest(function (callback) { 128 | return callback(); 129 | }, [function (err) { 130 | expect(err).not.to.be.ok(); 131 | done(); 132 | }]); 133 | }); 134 | 135 | describe('mock getToken err', function () { 136 | var api = new API(config.appid, config.appsecret); 137 | before(function () { 138 | muk(api, 'getToken', function (callback) { 139 | process.nextTick(function () { 140 | callback(new Error('mock getToken error')); 141 | }); 142 | }); 143 | }); 144 | after(function () { 145 | muk.restore(); 146 | }); 147 | 148 | it('should not ok', function (done) { 149 | api.preRequest(function (callback) { 150 | return callback(); 151 | }, [function (err) { 152 | expect(err).to.be.ok(); 153 | expect(err).have.property('message', 'mock getToken error'); 154 | done(); 155 | }]); 156 | }); 157 | }); 158 | 159 | describe('mock getAccessToken err', function () { 160 | var api = new API(config.appid, config.appsecret); 161 | before(function () { 162 | muk(api, 'getAccessToken', function (callback) { 163 | process.nextTick(function () { 164 | callback(new Error('mock getAccessToken error')); 165 | }); 166 | }); 167 | }); 168 | after(function () { 169 | muk.restore(); 170 | }); 171 | 172 | it('should not ok', function (done) { 173 | api.preRequest(function (callback) { 174 | callback(); 175 | }, [function (err) { 176 | expect(err).to.be.ok(); 177 | expect(err).have.property('message', 'mock getAccessToken error'); 178 | done(); 179 | }]); 180 | }); 181 | }); 182 | 183 | describe('mock getToken ok', function () { 184 | var api = new API(config.appid, config.appsecret); 185 | before(function () { 186 | muk(api, 'getToken', function (callback) { 187 | process.nextTick(function () { 188 | callback(null, {accessToken: 'token', expireTime: (new Date().getTime() + 10000)}); 189 | }); 190 | }); 191 | }); 192 | after(function () { 193 | muk.restore(); 194 | }); 195 | 196 | it('should not ok', function (done) { 197 | api.preRequest(function (callback) { 198 | callback(); 199 | }, [function (err) { 200 | expect(err).not.to.be.ok(); 201 | done(); 202 | }]); 203 | }); 204 | }); 205 | 206 | describe('mock getToken ok with retry', function () { 207 | var api = new API(config.appid, config.appsecret); 208 | before(function () { 209 | muk(api, 'getToken', function (callback) { 210 | process.nextTick(function () { 211 | callback(null, {accessToken: 'token', expireTime: (new Date().getTime() + 10000)}); 212 | }); 213 | }); 214 | }); 215 | 216 | after(function () { 217 | muk.restore(); 218 | }); 219 | 220 | it('should not ok', function (done) { 221 | var i = 0; 222 | api.preRequest(function (callback) { 223 | i++; 224 | if (i === 1) { 225 | return callback(null, {errcode: 40001}); 226 | } 227 | return callback(null, {errcode: 0}); 228 | 229 | }, [function (err) { 230 | expect(err).not.to.be.ok(); 231 | done(); 232 | }]); 233 | }); 234 | }); 235 | }); 236 | 237 | describe('getLatestToken', function () { 238 | it('should ok', function (done) { 239 | var api = new API(config.appid, config.appsecret); 240 | api.getLatestToken(function (err, token) { 241 | expect(err).not.to.be.ok(); 242 | expect(token).to.only.have.keys('accessToken', 'expireTime'); 243 | done(); 244 | }); 245 | }); 246 | 247 | describe('mock getToken err', function () { 248 | var api = new API(config.appid, config.appsecret); 249 | before(function () { 250 | muk(api, 'getToken', function (callback) { 251 | process.nextTick(function () { 252 | callback(new Error('mock getToken error')); 253 | }); 254 | }); 255 | }); 256 | after(function () { 257 | muk.restore(); 258 | }); 259 | 260 | it('should not ok', function (done) { 261 | api.getLatestToken(function (err) { 262 | expect(err).to.be.ok(); 263 | expect(err).have.property('message', 'mock getToken error'); 264 | done(); 265 | }); 266 | }); 267 | }); 268 | 269 | describe('mock getToken ok', function () { 270 | var api = new API(config.appid, config.appsecret); 271 | before(function () { 272 | muk(api, 'getToken', function (callback) { 273 | process.nextTick(function () { 274 | callback(null, {accessToken: 'token', expireTime: (new Date().getTime() + 10000)}); 275 | }); 276 | }); 277 | }); 278 | after(function () { 279 | muk.restore(); 280 | }); 281 | 282 | it('should not ok', function (done) { 283 | api.getLatestToken(function (err, token) { 284 | expect(err).not.to.be.ok(); 285 | expect(token).have.property('accessToken'); 286 | expect(token).have.property('expireTime'); 287 | done(); 288 | }); 289 | }); 290 | }); 291 | }); 292 | }); 293 | -------------------------------------------------------------------------------- /test/api_custom_service.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var muk = require('muk'); 6 | var urllib = require('urllib'); 7 | var expect = require('expect.js'); 8 | 9 | describe('api_custom_service', function () { 10 | var api = new API(config.appid, config.appsecret); 11 | 12 | describe('getRecords', function () { 13 | describe('getRecords mock', function () { 14 | before(function () { 15 | muk(urllib, 'request', function (url, args, callback) { 16 | var data = {'recordlist': []}; 17 | var res = { 18 | headers: { 19 | 'content-type': 'application/json' 20 | } 21 | }; 22 | process.nextTick(function () { 23 | callback(null, data, res); 24 | }); 25 | }); 26 | }); 27 | 28 | after(function () { 29 | muk.restore(); 30 | }); 31 | 32 | it('getRecords should ok', function (done) { 33 | var condition = { 34 | 'starttime' : 123456789, 35 | 'endtime' : 987654321, 36 | 'number' : 10000, 37 | 'msgid' : 1 38 | }; 39 | 40 | api.getRecords(condition, function (err, data, res) { 41 | expect(err).not.to.be.ok(); 42 | expect(data).to.have.property('recordlist'); 43 | done(); 44 | }); 45 | }); 46 | }); 47 | }); 48 | 49 | describe('getCustomServiceList', function () { 50 | it('should unauthorized', function (done) { 51 | api.getCustomServiceList(function (err, data, res) { 52 | expect(err).not.to.be.ok(); 53 | expect(data).to.have.property('kf_list'); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | describe('getOnlineCustomServiceList', function () { 60 | it('should unauthorized', function (done) { 61 | api.getOnlineCustomServiceList(function (err, data, res) { 62 | expect(err).not.to.be.ok(); 63 | expect(data).to.have.property('kf_online_list'); 64 | done(); 65 | }); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/api_customer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | var urllib = require('urllib'); 7 | var muk = require('muk'); 8 | var puling = 'ofL4cs7hr04cJIcu600_W-ZwwxHg'; 9 | var imageId = 'XDZxzuRWBPqI4R9n_nNR5uRVZVQCSneMoELyWKflwM2qF9K38vnVFzgaD97uCTUu'; 10 | var voiceId = '9R5BhAum7AEaGhwku0WhgvtO4C_7Xs78NoiRvm6v7IyoTljE4HH5o8E_UfnPrL0p'; 11 | var thumbId = 'BHxGDVy7WY6BCOcv3AwbywUE630Vw0tAV_V8bzBaCZid4Km5fwXrVOso3X0zas4n'; 12 | var cardId = 'pAtUNs-HV0evhGTWbU3ohp99tW7k'; 13 | 14 | describe('api_customer', function () { 15 | var api = new API(config.appid, config.appsecret); 16 | var mockError = function () { 17 | before(function () { 18 | muk(urllib, 'request', function (url, args, callback) { 19 | var data = {'errcode':1, 'errmsg':'mock error'}; 20 | var res = { 21 | headers: { 22 | 'content-type': 'application/json' 23 | } 24 | }; 25 | process.nextTick(function () { 26 | callback(null, data, res); 27 | }); 28 | }); 29 | }); 30 | 31 | after(function () { 32 | muk.restore(); 33 | }); 34 | }; 35 | 36 | describe('sendText', function () { 37 | it('sendText should ok', function (done) { 38 | api.sendText(puling, 'Hello World', function (err, data, res) { 39 | if (!err) { 40 | expect(err).not.to.be.ok(); 41 | expect(data).to.have.property('errcode', 0); 42 | expect(data).to.have.property('errmsg', 'ok'); 43 | } else { 44 | expect(err).to.be.ok(); 45 | expect(err).to.have.property('name', 'WeChatAPIError'); 46 | expect(err).to.have.property('message', 'response out of time limit'); 47 | } 48 | done(); 49 | }); 50 | }); 51 | 52 | describe('mock err', function () { 53 | mockError(); 54 | 55 | it('should not ok', function (done) { 56 | api.sendText(puling, 'Hellow World', function (err, data) { 57 | expect(err).to.be.ok(); 58 | expect(err.name).to.be('WeChatAPIError'); 59 | expect(err.message).to.be('mock error'); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | }); 65 | 66 | describe('sendImage', function () { 67 | it('sendImage should ok', function (done) { 68 | api.sendImage(puling, imageId, function (err, data, res) { 69 | if (!err) { 70 | expect(err).not.to.be.ok(); 71 | expect(data).to.have.property('errcode', 0); 72 | expect(data).to.have.property('errmsg', 'ok'); 73 | } else { 74 | expect(err).to.be.ok(); 75 | expect(err).to.have.property('name', 'WeChatAPIError'); 76 | // expect(err).to.have.property('message', 'response out of time limit'); 77 | } 78 | done(); 79 | }); 80 | }); 81 | 82 | describe('mock err', function () { 83 | mockError(); 84 | 85 | it('should not ok', function (done) { 86 | api.sendImage(puling, imageId, function (err, data) { 87 | expect(err).to.be.ok(); 88 | expect(err.name).to.be('WeChatAPIError'); 89 | expect(err.message).to.be('mock error'); 90 | done(); 91 | }); 92 | }); 93 | }); 94 | }); 95 | 96 | describe('sendVoice', function () { 97 | it('sendVoice should ok', function (done) { 98 | api.sendVoice(puling, voiceId, function (err, data, res) { 99 | if (!err) { 100 | expect(err).not.to.be.ok(); 101 | expect(data).to.have.property('errcode', 0); 102 | expect(data).to.have.property('errmsg', 'ok'); 103 | } else { 104 | expect(err).to.be.ok(); 105 | expect(err).to.have.property('name', 'WeChatAPIError'); 106 | expect(err).to.have.property('message', 'response out of time limit'); 107 | } 108 | done(); 109 | }); 110 | }); 111 | 112 | describe('mock err', function () { 113 | mockError(); 114 | 115 | it('should not ok', function (done) { 116 | api.sendVoice(puling, voiceId, function (err, data) { 117 | expect(err).to.be.ok(); 118 | expect(err.name).to.be('WeChatAPIError'); 119 | expect(err.message).to.be('mock error'); 120 | done(); 121 | }); 122 | }); 123 | }); 124 | }); 125 | 126 | describe('sendVideo', function () { 127 | it('sendVideo should ok', function (done) { 128 | api.sendVideo(puling, voiceId, thumbId, function (err, data, res) { 129 | if (!err) { 130 | expect(err).not.to.be.ok(); 131 | expect(data).to.have.property('errcode', 0); 132 | expect(data).to.have.property('errmsg', 'ok'); 133 | } else { 134 | expect(err).to.be.ok(); 135 | expect(err).to.have.property('name', 'WeChatAPIError'); 136 | expect(err).to.have.property('message', 'invalid media_id'); 137 | } 138 | done(); 139 | }); 140 | }); 141 | 142 | describe('mock err', function () { 143 | mockError(); 144 | 145 | it('should not ok', function (done) { 146 | api.sendVideo(puling, voiceId, thumbId, function (err, data) { 147 | expect(err).to.be.ok(); 148 | expect(err.name).to.be('WeChatAPIError'); 149 | expect(err.message).to.be('mock error'); 150 | done(); 151 | }); 152 | }); 153 | }); 154 | }); 155 | 156 | describe('sendMusic', function () { 157 | var music = { 158 | title: '音乐标题', // 可选 159 | description: '描述内容', // 可选 160 | musicurl: 'http://url.cn/xxx', // 音乐文件地址 161 | hqmusicurl: 'HQ_MUSIC_URL', 162 | thumb_media_id: 'THUMB_MEDIA_ID' 163 | }; 164 | 165 | it('sendMusic should ok', function (done) { 166 | api.sendMusic(puling, music, function (err, data, res) { 167 | if (!err) { 168 | expect(err).not.to.be.ok(); 169 | expect(data).to.have.property('errcode', 0); 170 | expect(data).to.have.property('errmsg', 'ok'); 171 | } else { 172 | expect(err).to.be.ok(); 173 | expect(err).to.have.property('name', 'WeChatAPIError'); 174 | expect(err).to.have.property('message', 'invalid media_id'); 175 | } 176 | done(); 177 | }); 178 | }); 179 | 180 | describe('mock err', function () { 181 | mockError(); 182 | 183 | it('should not ok', function (done) { 184 | api.sendMusic(puling, music, function (err, data) { 185 | expect(err).to.be.ok(); 186 | expect(err.name).to.be('WeChatAPIError'); 187 | expect(err.message).to.be('mock error'); 188 | done(); 189 | }); 190 | }); 191 | }); 192 | }); 193 | 194 | describe('sendNews', function () { 195 | var articles = [ 196 | { 197 | 'title':'Happy Day', 198 | 'description':'Is Really A Happy Day', 199 | 'url':'URL', 200 | 'picurl':'PIC_URL' 201 | }, 202 | { 203 | 'title':'Happy Day', 204 | 'description':'Is Really A Happy Day', 205 | 'url':'URL', 206 | 'picurl':'PIC_URL' 207 | } 208 | ]; 209 | 210 | it('sendMusic should ok', function (done) { 211 | api.sendNews(puling, articles, function (err, data, res) { 212 | if (!err) { 213 | expect(err).not.to.be.ok(); 214 | expect(data).to.have.property('errcode', 0); 215 | expect(data).to.have.property('errmsg', 'ok'); 216 | } else { 217 | expect(err).to.be.ok(); 218 | expect(err).to.have.property('name', 'WeChatAPIError'); 219 | expect(err).to.have.property('message'); 220 | } 221 | done(); 222 | }); 223 | }); 224 | 225 | describe('mock err', function () { 226 | mockError(); 227 | 228 | it('should not ok', function (done) { 229 | api.sendNews(puling, articles, function (err, data) { 230 | expect(err).to.be.ok(); 231 | expect(err.name).to.be('WeChatAPIError'); 232 | expect(err.message).to.be('mock error'); 233 | done(); 234 | }); 235 | }); 236 | }); 237 | }); 238 | 239 | describe.skip('sendCard', function() { 240 | var card = { 241 | // code:'12345', //option 242 | // openid: puling, //option 243 | card_id: cardId 244 | }; 245 | it('sendCard should ok', function(done) { 246 | api.sendCard(puling, card, function (err, data, res) { 247 | if (!err) { 248 | expect(err).not.to.be.ok(); 249 | expect(data).to.have.property('errcode', 0); 250 | expect(data).to.have.property('errmsg', 'ok'); 251 | } else { 252 | expect(err).to.be.ok(); 253 | expect(err).to.have.property('name', 'WeChatAPIError'); 254 | expect(err).to.have.property('message'); 255 | } 256 | done(); 257 | }); 258 | }); 259 | }); 260 | }); 261 | -------------------------------------------------------------------------------- /test/api_feedback.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_feedback', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | 10 | describe('updateFeedback', function () { 11 | it('should unauthorized with empty list', function (done) { 12 | api.updateFeedback('openid', 'feedback_id', function (err, data, res) { 13 | expect(err).to.be.ok(); 14 | expect(data).to.have.property('errcode', 48001); 15 | expect(data).to.have.property('errmsg', 'api unauthorized'); 16 | done(); 17 | }); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/api_group.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var config = require('./config'); 5 | var API = require('../'); 6 | var puling = 'ofL4cs7hr04cJIcu600_W-ZwwxHg'; 7 | 8 | describe('api_group.js', function () { 9 | var api = new API(config.appid, config.appsecret); 10 | before(function (done) { 11 | api.getAccessToken(done); 12 | }); 13 | 14 | it('getGroups should ok', function (done) { 15 | api.getGroups(function (err, data, res) { 16 | expect(err).not.to.be.ok(); 17 | expect(data).to.have.property('groups'); 18 | expect(data.groups).to.be.an('array'); 19 | done(); 20 | }); 21 | }); 22 | 23 | it('getWhichGroup should ok', function (done) { 24 | api.getWhichGroup(puling, function (err, data, res) { 25 | expect(err).not.to.be.ok(); 26 | expect(data).to.have.property('groupid'); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('createGroup should ok', function (done) { 32 | api.createGroup('new group', function (err, data, res) { 33 | // expect(err).not.to.be.ok(); 34 | // expect(data).to.have.property('group'); 35 | // expect(data.group).to.have.property('id'); 36 | // expect(data.group).to.have.property('name'); 37 | expect(err).to.have.property('message', 'too many group now, no need to add new'); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('updateGroup should ok', function (done) { 43 | api.updateGroup(101, 'renamed group', function (err, data, res) { 44 | expect(err).not.to.be.ok(); 45 | expect(data).to.have.property('errcode', 0); 46 | expect(data).to.have.property('errmsg', 'ok'); 47 | done(); 48 | }); 49 | }); 50 | 51 | it('moveUserToGroup should ok', function (done) { 52 | api.moveUserToGroup(puling, 102, function (err, data, res) { 53 | expect(err).not.to.be.ok(); 54 | expect(data).to.have.property('errcode', 0); 55 | expect(data).to.have.property('errmsg', 'ok'); 56 | done(); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/api_ip.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_get_ip', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | 10 | before(function (done) { 11 | api.getAccessToken(done); 12 | }); 13 | 14 | describe('getIpList', function () { 15 | it('should ok', function (done) { 16 | api.getIp(function (err, data, res) { 17 | expect(err).not.to.be.ok(); 18 | expect(data).to.only.have.keys('ip_list'); 19 | done(); 20 | }); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/api_mass_send.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var urllib = require('urllib'); 5 | var muk = require('muk'); 6 | var config = require('./config'); 7 | var API = require('../'); 8 | 9 | var puling = 'ofL4cs7hr04cJIcu600_W-ZwwxHg'; 10 | var imageId = 'XDZxzuRWBPqI4R9n_nNR5uRVZVQCSneMoELyWKflwM2qF9K38vnVFzgaD97uCTUu'; 11 | var movieId = 'b4F8SfaZZQwalDxwPjd923ACV5IUeYvZ9-dYKf5ytXrS-IImXEkl2U8Fl5EH-jCF'; 12 | 13 | describe('api_mass_send.js', function () { 14 | var api = new API(config.appid, config.appsecret); 15 | before(function (done) { 16 | api.getAccessToken(function (err, token) { 17 | api.token = token; 18 | done(err); 19 | }); 20 | }); 21 | 22 | describe('_uploadNews', function () { 23 | var news = { 24 | 'articles': [ 25 | { 26 | 'thumb_media_id': imageId, 27 | 'author':'xxx', 28 | 'title':'Happy Day', 29 | 'content_source_url':'www.qq.com', 30 | 'content':'content', 31 | 'digest':'digest', 32 | 'show_cover_pic':'1' 33 | } 34 | ] 35 | }; 36 | 37 | describe('mock _uploadNews ok', function () { 38 | before(function () { 39 | muk(urllib, 'request', function (url, opts, callback) { 40 | process.nextTick(function () { 41 | callback(new Error('mock invalid media_id')); 42 | }); 43 | }); 44 | }); 45 | 46 | after(function () { 47 | muk.restore(); 48 | }); 49 | 50 | it('_uploadNews should ok', function (done) { 51 | api._uploadNews(news, function (err, data) { 52 | expect(err).to.be.ok(); 53 | expect(err).to.have.property('message', 'mock invalid media_id'); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | 60 | describe('mock _uploadNews ok', function () { 61 | before(function () { 62 | muk(urllib, 'request', function (url, opts, callback) { 63 | process.nextTick(function () { 64 | callback(new Error('mock invalid media_id')); 65 | }); 66 | }); 67 | }); 68 | 69 | after(function () { 70 | muk.restore(); 71 | }); 72 | 73 | it('uploadNews should ok', function (done) { 74 | api.uploadNews(news, function (err, data) { 75 | expect(err).to.be.ok(); 76 | expect(err).to.have.property('message', 'mock invalid media_id'); 77 | done(); 78 | }); 79 | }); 80 | }); 81 | 82 | describe('mock _uploadNews ok', function () { 83 | before(function () { 84 | muk(urllib, 'request', function (url, opts, callback) { 85 | var data = { 86 | 'type':'news', 87 | 'media_id':'CsEf3ldqkAYJAU6EJeIkStVDSvffUJ54vqbThMgplD-VJXXof6ctX5fI6-aYyUiQ', 88 | 'created_at':1391857799 89 | }; 90 | process.nextTick(function () { 91 | callback(null, data); 92 | }); 93 | }); 94 | }); 95 | 96 | after(function () { 97 | muk.restore(); 98 | }); 99 | 100 | it('should ok', function (done) { 101 | api._uploadNews(news, function (err, data) { 102 | expect(err).to.not.be.ok(); 103 | expect(data).to.only.have.keys('type', 'media_id', 'created_at'); 104 | done(); 105 | }); 106 | }); 107 | }); 108 | }); 109 | 110 | describe('mock', function () { 111 | before(function () { 112 | muk(api, 'massSend', function (opts, receivers, callback) { 113 | var data = { 114 | 'errcode': 0, 115 | 'errmsg': 'send job submission success', 116 | 'msg_id': 34182 117 | }; 118 | var res = { 119 | headers: { 120 | 'content-type': 'application/json' 121 | } 122 | }; 123 | process.nextTick(function () { 124 | callback(null, data, res); 125 | }); 126 | }); 127 | }); 128 | 129 | after(function () { 130 | muk.restore(); 131 | }); 132 | it('send to openids should ok', function (done) { 133 | api.massSendText('群发消息', [puling], function (err, data) { 134 | expect(err).not.to.be.ok(); 135 | expect(data).to.have.property('errcode', 0); 136 | expect(data).to.have.property('errmsg', 'send job submission success'); 137 | expect(data).to.have.property('msg_id'); 138 | done(); 139 | }); 140 | }); 141 | 142 | it('send to group should ok', function (done) { 143 | api.massSendText('群发消息', 'groupid', function (err, data) { 144 | expect(err).not.to.be.ok(); 145 | expect(data).to.have.property('errcode', 0); 146 | expect(data).to.have.property('errmsg', 'send job submission success'); 147 | expect(data).to.have.property('msg_id'); 148 | done(); 149 | }); 150 | }); 151 | }); 152 | 153 | describe('massSendText', function () { 154 | it('should ok', function (done) { 155 | api.massSendText('群发消息', [puling], function (err, data) { 156 | expect(err).not.to.be.ok(); 157 | expect(data).to.have.property('errcode', 0); 158 | expect(data).to.have.property('errmsg', 'send job submission success'); 159 | done(); 160 | }); 161 | }); 162 | }); 163 | 164 | describe('massSendImage', function () { 165 | it('should ok', function (done) { 166 | api.massSendImage(imageId, [puling], function (err, data) { 167 | expect(err).to.be.ok(); 168 | expect(err).to.have.property('code'); 169 | expect(err).to.have.property('message'); 170 | done(); 171 | }); 172 | }); 173 | }); 174 | 175 | describe('deleteMass', function () { 176 | it('should ok', function (done) { 177 | api.deleteMass('messageId', function (err, data) { 178 | expect(err).to.be.ok(); 179 | expect(err).to.have.property('code'); 180 | expect(err).to.have.property('message'); 181 | done(); 182 | }); 183 | }); 184 | }); 185 | 186 | describe('massSendVoice', function () { 187 | it('should ok', function (done) { 188 | api.massSendVoice('media_id', [puling], function (err, data) { 189 | expect(err).to.be.ok(); 190 | expect(err).to.have.property('message'); 191 | expect(data).to.have.property('errmsg'); 192 | done(); 193 | }); 194 | }); 195 | }); 196 | 197 | describe('massSendVideo', function () { 198 | it('should ok', function (done) { 199 | var opts = { 200 | media_id: 'media_id', 201 | title: 'title', 202 | description: 'description' 203 | }; 204 | api.massSendVideo(opts, [puling], function (err, data) { 205 | expect(err).to.be.ok(); 206 | expect(err).to.have.property('message', 'invalid media_id'); 207 | done(); 208 | }); 209 | }); 210 | }); 211 | 212 | describe('massSendMPVideo', function () { 213 | describe('mock err', function () { 214 | before(function () { 215 | muk(api, 'preRequest', function (method, args) { 216 | var callback = args[args.length - 1]; 217 | process.nextTick(function () { 218 | callback(new Error('mock error')); 219 | }); 220 | }); 221 | }); 222 | after(function () { 223 | muk.restore(); 224 | }); 225 | 226 | it('should ok', function (done) { 227 | var opts = { 228 | 'media_id': movieId, 229 | 'title': 'TITLE', 230 | 'description': 'Description' 231 | }; 232 | api.massSendMPVideo(opts, [puling], function (err, data) { 233 | expect(err).to.be.ok(); 234 | expect(err).to.have.property('message', 'mock error'); 235 | done(); 236 | }); 237 | }); 238 | }); 239 | 240 | describe('mock', function () { 241 | before(function () { 242 | muk(api, 'uploadMPVideo', function (opts, callback) { 243 | process.nextTick(function () { 244 | callback(null, { 245 | 'media_id': 'rF4UdIMfYK3efUfyoddYRMU50zMiRmmt_l0kszupYh_SzrcW5Gaheq05p_lHuOTQ', 246 | 'title': 'TITLE', 247 | 'description': 'Description' 248 | }); 249 | }); 250 | }); 251 | }); 252 | 253 | after(function () { 254 | muk.restore(); 255 | }); 256 | it('send to openids should ok', function (done) { 257 | var opts = { 258 | 'media_id': 'rF4UdIMfYK3efUfyoddYRMU50zMiRmmt_l0kszupYh_SzrcW5Gaheq05p_lHuOTQ', 259 | 'title': 'TITLE', 260 | 'description': 'Description' 261 | }; 262 | api.massSendMPVideo(opts, [puling], function (err, data) { 263 | expect(err).to.be.ok(); 264 | expect(err).to.have.property('message', 'invalid media_id'); 265 | done(); 266 | }); 267 | }); 268 | }); 269 | }); 270 | 271 | describe('massSendNews', function () { 272 | it('should ok', function (done) { 273 | api.massSendNews('media id', [puling], function (err, data) { 274 | expect(err).to.be.ok(); 275 | expect(err).to.have.property('message', 'invalid media_id'); 276 | done(); 277 | }); 278 | }); 279 | 280 | it('should ok with groupid', function (done) { 281 | api.massSendNews('media id', 'invalid groupid', function (err, data) { 282 | expect(err).to.be.ok(); 283 | expect(err).to.have.property('code', 40050); 284 | expect(err).to.have.property('message', 'invalid timeline type'); 285 | done(); 286 | }); 287 | }); 288 | }); 289 | 290 | describe('uploadMPVideo', function () { 291 | describe('mock err', function () { 292 | before(function () { 293 | muk(urllib, 'request', function (url, opts, callback) { 294 | process.nextTick(function () { 295 | callback(new Error('mock err')); 296 | }); 297 | }); 298 | }); 299 | 300 | after(function () { 301 | muk.restore(); 302 | }); 303 | 304 | it('should ok', function (done) { 305 | var opts = { 306 | 'media_id': 'rF4UdIMfYK3efUfyoddYRMU50zMiRmmt_l0kszupYh_SzrcW5Gaheq05p_lHuOTQ', 307 | 'title': 'TITLE', 308 | 'description': 'Description' 309 | }; 310 | api.uploadMPVideo(opts, function (err, data) { 311 | expect(err).to.be.ok(); 312 | expect(err).to.have.property('message', 'mock err'); 313 | done(); 314 | }); 315 | }); 316 | }); 317 | }); 318 | }); 319 | -------------------------------------------------------------------------------- /test/api_media.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var urllib = require('urllib'); 5 | var muk = require('muk'); 6 | var path = require('path'); 7 | var API = require('../'); 8 | var fs = require('fs'); 9 | 10 | describe('api_media.js', function () { 11 | var api = new API('invalidappid', 'secret'); 12 | var isAccessTokenValid = api.isAccessTokenValid; 13 | before(function () { 14 | api.isAccessTokenValid = function () { 15 | return true; 16 | }; 17 | }); 18 | 19 | after(function () { 20 | api.isAccessTokenValid = isAccessTokenValid; 21 | }); 22 | 23 | describe.only('upload media', function () { 24 | var req = fs.createReadStream(path.join(__dirname, './fixture/pic.jpg')); 25 | req.headers = {}; 26 | ['Image', 'Voice', 'Video', 'Thumb'].forEach(function (method) { 27 | before(function () { 28 | muk(urllib, 'request', function (url, args, callback) { 29 | var resp = { 30 | 'type':'image', 31 | 'media_id':'usr5xL_gcxapoRjwH3bQZw_zKvcXL-DU4tRJtLtrtN71-3bXL52p3xX63ebp7tqA', 32 | 'created_at':1383233542 33 | }; 34 | process.nextTick(function () { 35 | callback(null, resp); 36 | }); 37 | }); 38 | }); 39 | 40 | after(function () { 41 | muk.restore(); 42 | }); 43 | 44 | it('upload' + method + ' should ok', function (done) { 45 | api['upload' + method](path.join(__dirname, './fixture/image.jpg'), function (err, data, res) { 46 | expect(err).not.to.be.ok(); 47 | expect(data).to.have.property('type', 'image'); 48 | expect(data).to.have.property('media_id'); 49 | expect(data).to.have.property('created_at'); 50 | done(); 51 | }); 52 | }); 53 | 54 | it('upload' + method + 'Stream should ok', function (done) { 55 | req.headers.type = method; 56 | api['upload' + method + 'Stream'](req, function (err, data, res) { 57 | expect(err).not.to.be.ok(); 58 | expect(data).to.have.property('type', 'image'); 59 | expect(data).to.have.property('media_id'); 60 | expect(data).to.have.property('created_at'); 61 | done(); 62 | }); 63 | }); 64 | 65 | it('upload' + method + ' should not ok', function (done) { 66 | api['upload' + method](path.join(__dirname, './fixture/inexist.jpg'), function (err, data, res) { 67 | expect(err).to.be.ok(); 68 | expect(err).to.have.property('name', 'Error'); 69 | expect(err).to.have.property('code', 'ENOENT'); 70 | done(); 71 | }); 72 | }); 73 | }); 74 | }); 75 | 76 | describe('get media with buffer', function () { 77 | before(function () { 78 | muk(urllib, 'request', function (url, args, callback) { 79 | var buffer = new Buffer('Hello world!'); 80 | var res = { 81 | headers: { 82 | 'content-type': 'image/jpeg' 83 | } 84 | }; 85 | process.nextTick(function () { 86 | callback(null, buffer, res); 87 | }); 88 | }); 89 | }); 90 | 91 | after(function () { 92 | muk.restore(); 93 | }); 94 | 95 | it('getMedia with buffer', function (done) { 96 | api.getMedia('media_id', function (err, data, res) { 97 | expect(err).not.to.be.ok(); 98 | expect(data.toString()).to.be('Hello world!'); 99 | done(); 100 | }); 101 | }); 102 | }); 103 | 104 | describe('get media with json', function () { 105 | before(function () { 106 | muk(urllib, 'request', function (url, args, callback) { 107 | var data = JSON.stringify({'errcode':40007, 'errmsg':'invalid media_id'}); 108 | var res = { 109 | headers: { 110 | 'content-type': 'application/json' 111 | } 112 | }; 113 | process.nextTick(function () { 114 | callback(null, data, res); 115 | }); 116 | }); 117 | }); 118 | 119 | after(function () { 120 | muk.restore(); 121 | }); 122 | it('getMedia with json', function (done) { 123 | api.getMedia('media_id', function (err, data, res) { 124 | expect(err).to.be.ok(); 125 | expect(err).to.have.property('name', 'WeChatAPIError'); 126 | expect(err).to.have.property('message', 'invalid media_id'); 127 | done(); 128 | }); 129 | }); 130 | }); 131 | 132 | describe('get media with err json', function () { 133 | before(function () { 134 | muk(urllib, 'request', function (url, args, callback) { 135 | var data = '{"errcode":40007, "errmsg":"invalid media_id"'; 136 | var res = { 137 | headers: { 138 | 'content-type': 'application/json' 139 | } 140 | }; 141 | process.nextTick(function () { 142 | callback(null, data, res); 143 | }); 144 | }); 145 | }); 146 | 147 | after(function () { 148 | muk.restore(); 149 | }); 150 | it('getMedia with err json', function (done) { 151 | api.getMedia('media_id', function (err, data, res) { 152 | expect(err).to.be.ok(); 153 | expect(err).to.have.property('name', 'SyntaxError'); 154 | done(); 155 | }); 156 | }); 157 | }); 158 | 159 | describe('upload image', function(){ 160 | before(function () { 161 | muk(urllib, 'request', function (url, args, callback) { 162 | var resp = { 163 | 'url': 'http://mmbiz.qpic.cn/mmbiz/gLO17UPS6FS2xsypf378iaNhWacZ1G1UplZYWEYfwvuU6Ont96b1roYsCNFwaRrSaKTPCUdBK9DgEHicsKwWCBRQ/0' 164 | }; 165 | process.nextTick(function () { 166 | callback(null, resp); 167 | }); 168 | }); 169 | }); 170 | 171 | after(function () { 172 | muk.restore(); 173 | }); 174 | 175 | it('should ok from upstream', function(done){ 176 | var req = fs.createReadStream(path.join(__dirname, './fixture/image.jpg')); 177 | req.headers = {}; 178 | api.uploadImageStream(req, function (err, data, res) { 179 | expect(err).not.to.be.ok(); 180 | expect(data).to.have.property('url'); 181 | done(); 182 | }); 183 | }); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /test/api_menu.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var config = require('./config'); 5 | var API = require('../'); 6 | 7 | describe('api_menu.js', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | before(function (done) { 10 | api.getAccessToken(done); 11 | }); 12 | 13 | it('createMenu should ok', function (done) { 14 | var menu = JSON.stringify(require('./fixture/menu.json')); 15 | api.createMenu(menu, function (err, result) { 16 | expect(err).not.to.be.ok(); 17 | expect(result).to.have.property('errcode', 0); 18 | expect(result).to.have.property('errmsg', 'ok'); 19 | done(); 20 | }); 21 | }); 22 | 23 | it('getMenu should ok', function (done) { 24 | api.getMenu(function (err, menu) { 25 | expect(err).not.to.be.ok(); 26 | expect(menu).to.have.property('menu'); 27 | expect(menu.menu).to.have.property('button'); 28 | expect(menu.menu.button).to.have.length(3); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('removeMenu should ok', function (done) { 34 | api.removeMenu(function (err, result) { 35 | expect(err).not.to.be.ok(); 36 | expect(result).to.have.property('errcode', 0); 37 | expect(result).to.have.property('errmsg', 'ok'); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/api_payment.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_payment', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | 10 | describe('deliverNotify', function () { 11 | it('should unauthorized', function (done) { 12 | api.deliverNotify('{}', function (err, data, res) { 13 | expect(err).to.be.ok(); 14 | expect(data).to.have.property('errcode', 48001); 15 | expect(data).to.have.property('errmsg', 'api unauthorized'); 16 | done(); 17 | }); 18 | }); 19 | }); 20 | 21 | describe('orderQuery', function () { 22 | it('orderQuery should ok', function (done) { 23 | api.orderQuery('{}', function (err, data, res) { 24 | expect(err).to.be.ok(); 25 | expect(data).to.have.property('errcode', 48001); 26 | expect(data).to.have.property('errmsg', 'api unauthorized'); 27 | done(); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/api_poi.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_poi', function(){ 8 | 9 | var api = new API(config.appid, config.appsecret); 10 | 11 | describe('get category', function(){ 12 | it('should ok', function(done){ 13 | api.getWXCategory(function (err, data, res) { 14 | expect(err).to.not.be.ok(); 15 | expect(data).to.have.property('category_list'); 16 | expect(data.category_list.length).to.have.above(0); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/api_qrcode.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var config = require('./config'); 5 | var API = require('../'); 6 | 7 | describe('api_qrcode', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | before(function (done) { 10 | api.getAccessToken(done); 11 | }); 12 | 13 | it('createTmpQRCode should ok', function (done) { 14 | api.createTmpQRCode(123, 1800, function (err, data, res) { 15 | expect(err).not.to.be.ok(); 16 | expect(data).to.have.property('ticket'); 17 | expect(data).to.have.property('expire_seconds'); 18 | done(); 19 | }); 20 | }); 21 | 22 | it('createLimitQRCode should ok', function (done) { 23 | api.createLimitQRCode(123, function (err, data, res) { 24 | expect(err).not.to.be.ok(); 25 | expect(data).to.have.property('ticket'); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('showQRCodeURL should ok', function () { 31 | expect(api.showQRCodeURL('ticket')).to.be.equal('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=ticket'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/api_semantic.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_semantic', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | it('should ok', function (done) { 10 | var condition = { 11 | 'query':'查一下明天从北京到上海的南航机票', 12 | 'city':'北京', 13 | 'category': 'flight,hotel' 14 | }; 15 | 16 | var uid = '123456'; 17 | 18 | api.semantic(uid, condition, function (err, data, res) { 19 | expect(err).to.be.ok(); 20 | expect(data).to.have.property('errcode'); 21 | expect(data).to.have.property('errmsg'); 22 | done(); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/api_shop_common.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var config = require('./config'); 5 | var API = require('../'); 6 | var expect = require('expect.js'); 7 | 8 | describe('api_shop_common', function () { 9 | var api = new API(config.appid, config.appsecret); 10 | 11 | describe('uploadPicture', function () { 12 | it('should unauthorized', function (done) { 13 | api.uploadPicture(path.join(__dirname, 'fixture/image.jpg'), function (err, data, res) { 14 | expect(err).to.be.ok(); 15 | expect(err).to.have.property('code', 48001); 16 | expect(err).to.have.property('message', 'api unauthorized'); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/api_shop_express.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_shop_express', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | var express = { 10 | 'delivery_template': { 11 | 'Name': 'testexpress', 12 | 'Assumer': 0, 13 | 'Valuation': 0, 14 | 'TopFee': [ 15 | { 16 | 'Type': 10000027, 17 | 'Normal': { 18 | 'StartStandards': 1, 19 | 'StartFees': 2, 20 | 'AddStandards': 3, 21 | 'AddFees': 1 22 | }, 23 | 'Custom': [ 24 | { 25 | 'StartStandards': 1, 26 | 'StartFees': 100, 27 | 'AddStandards': 1, 28 | 'AddFees': 3, 29 | 'DestCountry': '中国', 30 | 'DestProvince': '广东省', 31 | 'DestCity': '广州市' 32 | } 33 | ] 34 | }, 35 | { 36 | 'Type': 10000028, 37 | 'Normal': { 38 | 'StartStandards': 1, 39 | 'StartFees': 3, 40 | 'AddStandards': 3, 41 | 'AddFees': 2 42 | }, 43 | 'Custom': [ 44 | { 45 | 'StartStandards': 1, 46 | 'StartFees': 10, 47 | 'AddStandards': 1, 48 | 'AddFees': 30, 49 | 'DestCountry': '中国', 50 | 'DestProvince': '广东省', 51 | 'DestCity': '广州市' 52 | } 53 | ] 54 | }, 55 | { 56 | 'Type': 10000029, 57 | 'Normal': { 58 | 'StartStandards': 1, 59 | 'StartFees': 4, 60 | 'AddStandards': 3, 61 | 'AddFees': 3 62 | }, 63 | 'Custom': [ 64 | { 65 | 'StartStandards': 1, 66 | 'StartFees': 8, 67 | 'AddStandards': 2, 68 | 'AddFees': 11, 69 | 'DestCountry': '中国', 70 | 'DestProvince': '广东省', 71 | 'DestCity': '广州市' 72 | } 73 | ] 74 | } 75 | ] 76 | } 77 | }; 78 | 79 | describe('addExpressTemplate', function () { 80 | it('should unauthorized with empty list', function (done) { 81 | api.addExpressTemplate(express, function (err, data, res) { 82 | expect(err).to.be.ok(); 83 | expect(data).to.have.property('errcode', 48001); 84 | expect(data).to.have.property('errmsg', 'api unauthorized'); 85 | done(); 86 | }); 87 | }); 88 | }); 89 | 90 | describe('deleteExpressTemplate', function () { 91 | it('should unauthorized', function (done) { 92 | api.deleteExpressTemplate('tempalte_id', function (err, data, res) { 93 | expect(err).to.be.ok(); 94 | expect(data).to.have.property('errcode', 48001); 95 | expect(data).to.have.property('errmsg', 'api unauthorized'); 96 | done(); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('updateExpressTemplate', function () { 102 | it('should unauthorized', function (done) { 103 | api.updateExpressTemplate(express, function (err, data, res) { 104 | expect(err).to.be.ok(); 105 | expect(data).to.have.property('errcode', 48001); 106 | expect(data).to.have.property('errmsg', 'api unauthorized'); 107 | done(); 108 | }); 109 | }); 110 | }); 111 | 112 | describe('getExpressTemplateById', function () { 113 | it('should unauthorized', function (done) { 114 | api.getExpressTemplateById('template_id', function (err, data, res) { 115 | expect(err).to.be.ok(); 116 | expect(data).to.have.property('errcode', 48001); 117 | expect(data).to.have.property('errmsg', 'api unauthorized'); 118 | done(); 119 | }); 120 | }); 121 | }); 122 | 123 | describe('getAllExpressTemplates', function () { 124 | it('should unauthorized', function (done) { 125 | api.getAllExpressTemplates(function (err, data, res) { 126 | expect(err).to.be.ok(); 127 | expect(data).to.have.property('errcode', 48001); 128 | expect(data).to.have.property('errmsg', 'api unauthorized'); 129 | done(); 130 | }); 131 | }); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /test/api_shop_goods.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_shop_goods', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | var goods = { 10 | 'product_base':{ 11 | 'category_id':[ 12 | '537074298' 13 | ], 14 | 'property':[ 15 | {'id':'1075741879','vid':'1079749967'}, 16 | {'id':'1075754127','vid':'1079795198'}, 17 | {'id':'1075777334','vid':'1079837440'} 18 | ], 19 | 'name':'testaddproduct', 20 | 'sku_info':[ 21 | { 22 | 'id':'1075741873', 23 | 'vid':['1079742386','1079742363'] 24 | } 25 | ], 26 | 'main_img': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjD3ulEKogfsiaua49pvLfUS8Ym0GSYjViaLic0FD3vN0V8PILcibEGb2fPfEOmw/0', 27 | 'img':[ 28 | 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjD3ulEKogfsiaua49pvLfUS8Ym0GSYjViaLic0FD3vN0V8PILcibEGb2fPfEOmw/0' 29 | ], 30 | 'detail':[ 31 | {'text':'testfirst'}, 32 | {'img': '4whpV1VZl2iccsvYbHvnphkyGtnvjD3ul1UcLcwxrFdwTKYhH9Q5YZoCfX4Ncx655ZK6ibnlibCCErbKQtReySaVA/0'}, 33 | {'text':'testagain'} 34 | ], 35 | 'buy_limit':10 36 | }, 37 | 'sku_list':[ 38 | { 39 | 'sku_id':'1075741873:1079742386', 40 | 'price':30, 41 | 'icon_url': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl28bJj62XgfHPibY3ORKicN1oJ4CcoIr4BMbfA8LqyyjzOZzqrOGz3f5K Wq1QGP3fo6TOTSYD3TBQjuw/0', 42 | 'product_code':'testing', 43 | 'ori_price':9000000, 44 | 'quantity':800 45 | }, 46 | { 47 | 'sku_id':'1075741873:1079742363', 48 | 'price':30, 49 | 'icon_url': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl28bJj62XgfHPibY3ORKicN1oJ4CcoIr4BMbfA8LqyyjzOZzqrOGz3f5K Wq1QGP3fo6TOTSYD3TBQjuw/0', 50 | 'product_code':'testingtesting', 51 | 'ori_price':9000000, 52 | 'quantity':800 53 | } 54 | ], 55 | 'attrext':{ 56 | 'location':{ 57 | 'country':'中国', 58 | 'province':'广东省', 59 | 'city':'广州市', 60 | 'address':'T.I.T创意园' 61 | }, 62 | 'isPostFree':0, 63 | 'isHasReceipt':1, 64 | 'isUnderGuaranty':0, 65 | 'isSupportReplace':0 66 | }, 67 | 'delivery_info':{ 68 | 'delivery_type':0, 69 | 'template_id':0, 70 | 'express':[ 71 | {'id':10000027,'price':100}, 72 | {'id':10000028,'price':100}, 73 | {'id':10000029,'price':100} 74 | ] 75 | } 76 | }; 77 | 78 | describe('createGoods', function () { 79 | it('should unauthorized with empty list', function (done) { 80 | api.createGoods(goods, function (err, data, res) { 81 | expect(err).to.be.ok(); 82 | expect(data).to.have.property('errcode', 48001); 83 | expect(data).to.have.property('errmsg', 'api unauthorized'); 84 | done(); 85 | }); 86 | }); 87 | }); 88 | 89 | describe('deleteGoods', function () { 90 | it('should unauthorized', function (done) { 91 | api.deleteGoods('product_id', function (err, data, res) { 92 | expect(err).to.be.ok(); 93 | expect(data).to.have.property('errcode', 48001); 94 | expect(data).to.have.property('errmsg', 'api unauthorized'); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | 100 | describe('updateGoods', function () { 101 | it('should unauthorized', function (done) { 102 | api.updateGoods(goods, function (err, data, res) { 103 | expect(err).to.be.ok(); 104 | expect(data).to.have.property('errcode', 48001); 105 | expect(data).to.have.property('errmsg', 'api unauthorized'); 106 | done(); 107 | }); 108 | }); 109 | }); 110 | 111 | describe('getGoods', function () { 112 | it('should unauthorized', function (done) { 113 | api.getGoods('product_id', function (err, data, res) { 114 | expect(err).to.be.ok(); 115 | expect(data).to.have.property('errcode', 48001); 116 | expect(data).to.have.property('errmsg', 'api unauthorized'); 117 | done(); 118 | }); 119 | }); 120 | }); 121 | 122 | describe('getGoodsByStatus', function () { 123 | it('should unauthorized', function (done) { 124 | api.getGoodsByStatus(0, function (err, data, res) { 125 | expect(err).to.be.ok(); 126 | expect(data).to.have.property('errcode', 48001); 127 | expect(data).to.have.property('errmsg', 'api unauthorized'); 128 | done(); 129 | }); 130 | }); 131 | }); 132 | 133 | describe('updateGoodsStatus', function () { 134 | it('should unauthorized', function (done) { 135 | api.updateGoodsStatus('product_id', 1, function (err, data, res) { 136 | expect(err).to.be.ok(); 137 | expect(data).to.have.property('errcode', 48001); 138 | expect(data).to.have.property('errmsg', 'api unauthorized'); 139 | done(); 140 | }); 141 | }); 142 | }); 143 | 144 | describe('getSubCats', function () { 145 | it('should unauthorized', function (done) { 146 | api.getSubCats('cat_id', function (err, data, res) { 147 | expect(err).to.be.ok(); 148 | expect(data).to.have.property('errcode', 48001); 149 | expect(data).to.have.property('errmsg', 'api unauthorized'); 150 | done(); 151 | }); 152 | }); 153 | }); 154 | 155 | describe('getSKUs', function () { 156 | it('should unauthorized', function (done) { 157 | api.getSKUs('cat_id', function (err, data, res) { 158 | expect(err).to.be.ok(); 159 | expect(data).to.have.property('errcode', 48001); 160 | expect(data).to.have.property('errmsg', 'api unauthorized'); 161 | done(); 162 | }); 163 | }); 164 | }); 165 | 166 | describe('getProperties', function () { 167 | it('should unauthorized', function (done) { 168 | api.getProperties('cat_id', function (err, data, res) { 169 | expect(err).to.be.ok(); 170 | expect(data).to.have.property('errcode'); 171 | expect(data).to.have.property('errmsg'); 172 | done(); 173 | }); 174 | }); 175 | }); 176 | }); 177 | -------------------------------------------------------------------------------- /test/api_shop_group.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_shop_group', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | 10 | describe('createGoodsGroup', function () { 11 | it('should unauthorized with empty list', function (done) { 12 | api.createGoodsGroup('group_name', [], function (err, data, res) { 13 | expect(err).to.be.ok(); 14 | expect(data).to.have.property('errcode', 48001); 15 | expect(data).to.have.property('errmsg', 'api unauthorized'); 16 | done(); 17 | }); 18 | }); 19 | 20 | it('should unauthorized', function (done) { 21 | api.createGoodsGroup('group_name', ['product_id'], function (err, data, res) { 22 | expect(err).to.be.ok(); 23 | expect(data).to.have.property('errcode', 48001); 24 | expect(data).to.have.property('errmsg', 'api unauthorized'); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | 30 | describe('getGroupById', function () { 31 | it('should unauthorized', function (done) { 32 | api.getGroupById('id', function (err, data, res) { 33 | expect(err).to.be.ok(); 34 | expect(data).to.have.property('errcode', 48001); 35 | expect(data).to.have.property('errmsg', 'api unauthorized'); 36 | done(); 37 | }); 38 | }); 39 | }); 40 | 41 | describe('getAllGroups', function () { 42 | it('should unauthorized', function (done) { 43 | api.getAllGroups(function (err, data, res) { 44 | expect(err).to.be.ok(); 45 | expect(data).to.have.property('errcode', 48001); 46 | expect(data).to.have.property('errmsg', 'api unauthorized'); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | 52 | describe('deleteGoodsGroup', function () { 53 | it('should unauthorized', function (done) { 54 | api.deleteGoodsGroup('group_id', function (err, data, res) { 55 | expect(err).to.be.ok(); 56 | expect(data).to.have.property('errcode', 48001); 57 | expect(data).to.have.property('errmsg', 'api unauthorized'); 58 | done(); 59 | }); 60 | }); 61 | }); 62 | 63 | describe('updateGoodsForGroup', function () { 64 | it('should unauthorized', function (done) { 65 | api.updateGoodsForGroup('group_id', ['add_id'], ['remove_id'], function (err, data, res) { 66 | expect(err).to.be.ok(); 67 | expect(data).to.have.property('errcode', 48001); 68 | expect(data).to.have.property('errmsg', 'api unauthorized'); 69 | done(); 70 | }); 71 | }); 72 | 73 | it('should unauthorized with empty list', function (done) { 74 | api.updateGoodsForGroup('group_id', [], [], function (err, data, res) { 75 | expect(err).to.be.ok(); 76 | expect(data).to.have.property('errcode', 48001); 77 | expect(data).to.have.property('errmsg', 'api unauthorized'); 78 | done(); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('updateGoodsGroup', function () { 84 | it('should unauthorized', function (done) { 85 | api.updateGoodsGroup('group_id', 'new name', function (err, data, res) { 86 | expect(err).to.be.ok(); 87 | expect(data).to.have.property('errcode', 48001); 88 | expect(data).to.have.property('errmsg', 'api unauthorized'); 89 | done(); 90 | }); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/api_shop_order.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_shop_order', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | 10 | describe('getOrderById', function () { 11 | it('should unauthorized', function (done) { 12 | api.getOrderById('order_id', function (err, data, res) { 13 | expect(err).to.be.ok(); 14 | expect(data).to.have.property('errcode', 48001); 15 | expect(data).to.have.property('errmsg', 'api unauthorized'); 16 | done(); 17 | }); 18 | }); 19 | }); 20 | 21 | describe('getOrdersByStatus', function () { 22 | it('should unauthorized: (callback)', function (done) { 23 | api.getOrdersByStatus(function (err, data, res) { 24 | expect(err).to.be.ok(); 25 | expect(data).to.have.property('errcode', 48001); 26 | expect(data).to.have.property('errmsg', 'api unauthorized'); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('should unauthorized with status(status, callback)', function (done) { 32 | api.getOrdersByStatus(2, function (err, data, res) { 33 | expect(err).to.be.ok(); 34 | expect(data).to.have.property('errcode', 48001); 35 | expect(data).to.have.property('errmsg', 'api unauthorized'); 36 | done(); 37 | }); 38 | }); 39 | 40 | it('should unauthorized with beginTime(beginTime, callback)', function (done) { 41 | api.getOrdersByStatus(new Date(), function (err, data, res) { 42 | expect(err).to.be.ok(); 43 | expect(data).to.have.property('errcode', 48001); 44 | expect(data).to.have.property('errmsg', 'api unauthorized'); 45 | done(); 46 | }); 47 | }); 48 | 49 | it('should exception: (string, callback)', function () { 50 | expect(function () { 51 | api.getOrdersByStatus('some string', function () {}); 52 | }).to.throwException(/first parameter must be Number or Date/); 53 | }); 54 | 55 | it('should unauthorized with beginTime&endTime(status, endTime, callback)', function (done) { 56 | api.getOrdersByStatus(2, new Date(), function (err, data, res) { 57 | expect(err).to.be.ok(); 58 | expect(data).to.have.property('errcode', 48001); 59 | expect(data).to.have.property('errmsg', 'api unauthorized'); 60 | done(); 61 | }); 62 | }); 63 | 64 | it('should exception: (string, string, callback)', function () { 65 | expect(function () { 66 | api.getOrdersByStatus('some string', 'string', function () {}); 67 | }).to.throwException(/first parameter must be Number and second parameter must be Date/); 68 | }); 69 | 70 | it('should unauthorized with beginTime&endTime(status, beginTime, endTime, callback)', function (done) { 71 | api.getOrdersByStatus(2, new Date(), new Date(), function (err, data, res) { 72 | expect(err).to.be.ok(); 73 | expect(data).to.have.property('errcode', 48001); 74 | expect(data).to.have.property('errmsg', 'api unauthorized'); 75 | done(); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('setExpressForOrder', function () { 81 | it('should unauthorized', function (done) { 82 | api.setExpressForOrder('orderId', 'deliveryCompany', 'deliveryTrackNo', function (err, data, res) { 83 | expect(err).to.be.ok(); 84 | expect(data).to.have.property('errcode', 48001); 85 | expect(data).to.have.property('errmsg', 'api unauthorized'); 86 | done(); 87 | }); 88 | }); 89 | }); 90 | 91 | describe('closeOrder', function () { 92 | it('should unauthorized', function (done) { 93 | api.closeOrder('orderId', function (err, data, res) { 94 | expect(err).to.be.ok(); 95 | expect(data).to.have.property('errcode', 48001); 96 | expect(data).to.have.property('errmsg', 'api unauthorized'); 97 | done(); 98 | }); 99 | }); 100 | }); 101 | 102 | }); 103 | -------------------------------------------------------------------------------- /test/api_shop_shelf.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_shop_shelf', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | var shelf = { 10 | 'shelf_data': { 11 | 'module_infos': [ 12 | { 13 | 'group_info': { 14 | 'filter': { 15 | 'count': 2 16 | }, 17 | 'group_id': 50 18 | }, 19 | 'eid': 1 20 | }, 21 | { 22 | 'group_infos': { 23 | 'groups': [ 24 | { 25 | 'group_id': 49 26 | }, 27 | { 28 | 'group_id': 50 29 | }, 30 | { 31 | 'group_id': 51 32 | } 33 | ] 34 | }, 35 | 'eid': 2 36 | }, 37 | { 38 | 'group_info': { 39 | 'group_id': 52, 40 | 'img': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5Jm64z4I0TTicv0TjN7Vl9bykUUibYKIOjicAwIt6Oy0Y6a1Rjp5Tos8tg/0' 41 | }, 42 | 'eid': 3 43 | }, 44 | { 45 | 'group_infos': { 46 | 'groups': [ 47 | { 48 | 'group_id': 49, 49 | 'img': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0' 50 | }, 51 | { 52 | 'group_id': 50, 53 | 'img': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5G1kdy3ViblHrR54gbCmbiaMnl5HpLGm5JFeENyO9FEZAy6mPypEpLibLA/0' 54 | }, 55 | { 56 | 'group_id': 52, 57 | 'img': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0' 58 | } 59 | ] 60 | }, 61 | 'eid': 4 62 | }, 63 | { 64 | 'group_infos': { 65 | 'groups': [ 66 | { 67 | 'group_id': 43 68 | }, 69 | { 70 | 'group_id': 44 71 | }, 72 | { 73 | 'group_id': 45 74 | }, 75 | { 76 | 'group_id': 46 77 | } 78 | ], 79 | 'img_background': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0' 80 | }, 81 | 'eid': 5 82 | } 83 | ] 84 | }, 85 | 'shelf_banner': 'http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2ibrWQn8zWFUh1YznsMV0XEiavFfLzDWYyvQOBBszXlMaiabGWzz5B2KhNn2IDemHa3iarmCyribYlZYyw/0', 86 | 'shelf_name': '测试货架' 87 | }; 88 | 89 | describe('createShelf', function () { 90 | it('should unauthorized with empty list', function (done) { 91 | api.createShelf(shelf, function (err, data, res) { 92 | expect(err).to.be.ok(); 93 | expect(data).to.have.property('errcode', 48001); 94 | expect(data).to.have.property('errmsg', 'api unauthorized'); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | 100 | describe('deleteShelf', function () { 101 | it('should unauthorized', function (done) { 102 | api.deleteShelf('shelf_id', function (err, data, res) { 103 | expect(err).to.be.ok(); 104 | expect(data).to.have.property('errcode', 48001); 105 | expect(data).to.have.property('errmsg', 'api unauthorized'); 106 | done(); 107 | }); 108 | }); 109 | }); 110 | 111 | describe('updateShelf', function () { 112 | it('should unauthorized', function (done) { 113 | api.updateShelf(shelf, function (err, data, res) { 114 | expect(err).to.be.ok(); 115 | expect(data).to.have.property('errcode', 48001); 116 | expect(data).to.have.property('errmsg', 'api unauthorized'); 117 | done(); 118 | }); 119 | }); 120 | }); 121 | 122 | describe('getAllShelves', function () { 123 | it('should unauthorized', function (done) { 124 | api.getAllShelves(function (err, data, res) { 125 | expect(err).to.be.ok(); 126 | expect(err).to.have.property('code', 48001); 127 | expect(err).to.have.property('message', 'api unauthorized'); 128 | done(); 129 | }); 130 | }); 131 | }); 132 | 133 | describe('getShelfById', function () { 134 | it('should unauthorized', function (done) { 135 | api.getShelfById('shelf_id', function (err, data, res) { 136 | expect(err).to.be.ok(); 137 | expect(data).to.have.property('errcode'); 138 | expect(data).to.have.property('errmsg'); 139 | done(); 140 | }); 141 | }); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /test/api_shop_stock.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_shop_stock', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | 10 | describe('updateStock', function () { 11 | it('should unauthorized', function (done) { 12 | api.updateStock(-1, 'product_id', 'sku', function (err, data, res) { 13 | expect(err).to.be.ok(); 14 | expect(data).to.have.property('errcode', 48001); 15 | expect(data).to.have.property('errmsg', 'api unauthorized'); 16 | done(); 17 | }); 18 | }); 19 | 20 | it('should unauthorized', function (done) { 21 | api.updateStock(1, 'product_id', 'sku', function (err, data, res) { 22 | expect(err).to.be.ok(); 23 | expect(data).to.have.property('errcode', 48001); 24 | expect(data).to.have.property('errmsg', 'api unauthorized'); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/api_tag.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var config = require('./config'); 5 | var API = require('../'); 6 | var puling = 'oNeQKj7AquX1pADyghgvLmLTqIgQ'; 7 | var tagName = 'test_tag_name_v1'; 8 | var tagId = 103; 9 | 10 | describe('api_tag.js', function () { 11 | var api = new API(config.appid, config.appsecret); 12 | before(function (done) { 13 | api.getAccessToken(done); 14 | }); 15 | 16 | // 1 创建标签 17 | it('createTag should ok', function (done) { 18 | api.createTag(tagName, function (err, data, res) { 19 | expect(err).not.to.be.ok(); 20 | expect(data).to.have.property('tag'); 21 | done(); 22 | }); 23 | }); 24 | 25 | // 2 获取公众号的所有标签 26 | it('getTags should ok', function (done) { 27 | api.getTags(function (err, data, res) { 28 | expect(err).not.to.be.ok(); 29 | expect(data).to.have.property('tags'); 30 | expect(data.tags).to.be.an('array'); 31 | done(); 32 | }); 33 | }); 34 | 35 | // 3 编辑标签 36 | it('editTag should ok', function (done) { 37 | api.editTag(tagId, tagName + '1', function (err, data, res) { 38 | expect(err).not.to.be.ok(); 39 | expect(data).to.have.property('errcode', 0); 40 | expect(data).to.have.property('errmsg', 'ok'); 41 | done(); 42 | }); 43 | }); 44 | 45 | // 4 删除标签 46 | // it('deleteTag should ok', function (done) { 47 | // api.deleteTag(103, function (err, data, res) { 48 | // expect(err).not.to.be.ok(); 49 | // expect(data).to.have.property('errcode', 0); 50 | // expect(data).to.have.property('errmsg', 'ok'); 51 | // done(); 52 | // }); 53 | // }); 54 | 55 | 56 | // 6 批量为用户打标签 57 | it('membersBatchtagging should ok', function (done) { 58 | api.membersBatchtagging(tagId, [puling], function (err, data, res) { 59 | expect(err).not.to.be.ok(); 60 | expect(data).to.have.property('errcode', 0); 61 | expect(data).to.have.property('errmsg', 'ok'); 62 | done(); 63 | }); 64 | }); 65 | 66 | // 5 获取标签的所有粉丝 67 | it('getTagUsers should ok', function (done) { 68 | api.getTagUsers(tagId, '', function (err, data, res) { 69 | expect(err).not.to.be.ok(); 70 | expect(data).to.have.property('data'); 71 | expect(data.data.openid).to.be.an('array'); 72 | done(); 73 | }); 74 | }); 75 | 76 | // 7 批量为用户取消标签 77 | it('membersBatchuntagging should ok', function (done) { 78 | api.membersBatchuntagging(tagId, [puling], function (err, data, res) { 79 | expect(err).not.to.be.ok(); 80 | expect(data).to.have.property('errcode', 0); 81 | expect(data).to.have.property('errmsg', 'ok'); 82 | done(); 83 | }); 84 | }); 85 | 86 | // 8 获取用户身上的标签列表 87 | it('getUserTags should ok', function (done) { 88 | api.getUserTags(puling, function (err, data, res) { 89 | expect(err).not.to.be.ok(); 90 | expect(data).to.have.property('tagid_list'); 91 | expect(data.tagid_list).to.be.an('array'); 92 | done(); 93 | }); 94 | }); 95 | 96 | }); 97 | -------------------------------------------------------------------------------- /test/api_template.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | var urllib = require('urllib'); 7 | var muk = require('muk'); 8 | var puling = 'ofL4cs7hr04cJIcu600_W-ZwwxHg'; 9 | 10 | describe('api_template', function () { 11 | var api = new API(config.appid, config.appsecret); 12 | var mockError = function () { 13 | before(function () { 14 | muk(urllib, 'request', function (url, args, callback) { 15 | var data = {'errcode':1, 'errmsg':'mock error'}; 16 | var res = { 17 | headers: { 18 | 'content-type': 'application/json' 19 | } 20 | }; 21 | process.nextTick(function () { 22 | callback(null, data, res); 23 | }); 24 | }); 25 | }); 26 | 27 | after(function () { 28 | muk.restore(); 29 | }); 30 | }; 31 | 32 | describe('sendTemplate', function () { 33 | it('sendTemplate should ok', function (done) { 34 | var templateId = '模板id'; 35 | // 跳转目的地置空,则在发送后,点击模板消息会进入一个空白页面(ios), 或无法点击(android) 36 | var dest = { 37 | url: 'http://weixin.qq.com/download', 38 | miniprogram:{ 39 | appid: 'xiaochengxuappid12345', 40 | pagepath: 'index?foo=bar' 41 | } 42 | }; 43 | var data = { 44 | user: { 45 | 'value':'黄先生', 46 | 'color':'#173177' 47 | } 48 | }; 49 | api.sendTemplate(puling, templateId, dest, data, function (err, data, res) { 50 | if (!err) { 51 | expect(err).not.to.be.ok(); 52 | expect(data).to.have.property('errcode', 0); 53 | expect(data).to.have.property('errmsg', 'ok'); 54 | } else { 55 | expect(err).to.be.ok(); 56 | expect(err).to.have.property('name', 'WeChatAPIError'); 57 | expect(err.message).to.contain('invalid template_id'); 58 | } 59 | done(); 60 | }); 61 | }); 62 | 63 | describe('mock err', function () { 64 | mockError(); 65 | 66 | it('should not ok', function (done) { 67 | var templateId = '模板id'; 68 | // 跳转目的地置空,则在发送后,点击模板消息会进入一个空白页面(ios), 或无法点击(android) 69 | var dest = { 70 | url: 'http://weixin.qq.com/download', 71 | miniprogram:{ 72 | appid: 'xiaochengxuappid12345', 73 | pagepath: 'index?foo=bar' 74 | } 75 | }; 76 | var data = { 77 | user: { 78 | 'value':'黄先生', 79 | 'color':'#173177' 80 | } 81 | }; 82 | api.sendTemplate(puling, templateId, dest, data, function (err, data, res) { 83 | expect(err).to.be.ok(); 84 | expect(err.name).to.be('WeChatAPIError'); 85 | expect(err.message).to.be('mock error'); 86 | done(); 87 | }); 88 | }); 89 | }); 90 | }); 91 | 92 | describe('getIndustry', function () { 93 | it('getIndustry should ok', function (done) { 94 | api.getIndustry(function (err, data, res) { 95 | if (!err) { 96 | expect(err).not.to.be.ok(); 97 | expect(data).to.have.property('primary_industry'); 98 | expect(data).to.have.property('secondary_industry'); 99 | } else { 100 | expect(err).to.be.ok(); 101 | expect(err).to.have.property('name', 'WeChatAPIError'); 102 | } 103 | done(); 104 | }); 105 | }); 106 | }); 107 | 108 | describe('getAllPrivateTemplate', function () { 109 | it('getAllPrivateTemplate should ok', function (done) { 110 | api.getAllPrivateTemplate(function (err, data, res) { 111 | if (!err) { 112 | expect(err).not.to.be.ok(); 113 | expect(data).to.have.property('template_list'); 114 | expect(data.template_list).to.be.an('array'); 115 | } else { 116 | expect(err).to.be.ok(); 117 | expect(err).to.have.property('name', 'WeChatAPIError'); 118 | } 119 | done(); 120 | }); 121 | }); 122 | }); 123 | 124 | describe('delPrivateTemplate', function () { 125 | it('delPrivateTemplate should ok', function (done) { 126 | var templateId = '模板id'; 127 | api.delPrivateTemplate(templateId, function (err, data, res) { 128 | if (!err) { 129 | expect(err).not.to.be.ok(); 130 | expect(data).to.have.property('errcode', 0); 131 | expect(data).to.have.property('errmsg', 'ok'); 132 | } else { 133 | expect(err).to.be.ok(); 134 | expect(err).to.have.property('name', 'WeChatAPIError'); 135 | expect(err.message).to.contain('invalid template_id'); 136 | } 137 | done(); 138 | }); 139 | }); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /test/api_url.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var config = require('./config'); 5 | var API = require('../'); 6 | 7 | describe('api_url.js', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | before(function (done) { 10 | api.getLatestToken(done); 11 | }); 12 | 13 | it('shorturl should ok', function (done) { 14 | api.shorturl('https://github.com/', function (err, result) { 15 | expect(err).to.be.ok(); 16 | expect(err).to.have.property('message', 'api unauthorized'); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/api_user.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./config'); 4 | var API = require('../'); 5 | var expect = require('expect.js'); 6 | 7 | describe('api_user', function () { 8 | var api = new API(config.appid, config.appsecret); 9 | var puling = 'ofL4cs7hr04cJIcu600_W-ZwwxHg'; 10 | 11 | before(function (done) { 12 | api.getAccessToken(done); 13 | }); 14 | 15 | describe('getUser', function () { 16 | it('should ok', function (done) { 17 | api.getUser(puling, function (err, data, res) { 18 | expect(err).not.to.be.ok(); 19 | expect(data).to.only.have.keys('subscribe', 'openid', 'nickname', 20 | 'sex', 'language', 'city', 'province', 'country', 'headimgurl', 21 | 'subscribe_time', 'remark', 'groupid'); 22 | done(); 23 | }); 24 | }); 25 | }); 26 | 27 | describe('updateRemark', function () { 28 | it('should ok', function (done) { 29 | api.updateRemark(puling, 'remarked', function (err, data, res) { 30 | expect(err).not.to.be.ok(); 31 | expect(data).have.property('errcode', 0); 32 | expect(data).have.property('errmsg', 'ok'); 33 | done(); 34 | }); 35 | }); 36 | }); 37 | 38 | it('getFollowers should ok', function (done) { 39 | api.getFollowers(function (err, data, res) { 40 | expect(err).not.to.be.ok(); 41 | expect(data).to.only.have.keys('total', 'count', 'data', 'next_openid'); 42 | done(); 43 | }); 44 | }); 45 | 46 | it('getFollowers with nextOpenId should ok', function (done) { 47 | api.getFollowers(puling, function (err, data, res) { 48 | expect(err).not.to.be.ok(); 49 | expect(data).to.have.key('next_openid'); 50 | done(); 51 | }); 52 | }); 53 | 54 | describe('batchGetUser', function() { 55 | it('should ok', function(done) { 56 | api.batchGetUsers([puling], function (err, data, res) { 57 | expect(err).not.to.be.ok(); 58 | expect(data).to.only.have.keys('user_info_list'); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | appid: process.env.WX_APPID || 'wxc9135aade4e81d57', 5 | appsecret: process.env.WX_APPSECRET||'0461795e98b8ffde5a212b5098f1b9b6' 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixture/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-webot/wechat-api/005bc10d6e8ade175df31ce3da131ffa8d669f5b/test/fixture/image.jpg -------------------------------------------------------------------------------- /test/fixture/menu.json: -------------------------------------------------------------------------------- 1 | { 2 | "button":[ 3 | { 4 | "type":"click", 5 | "name":"今日歌曲", 6 | "key":"V1001_TODAY_MUSIC" 7 | }, 8 | { 9 | "type":"click", 10 | "name":"歌手简介", 11 | "key":"V1001_TODAY_SINGER" 12 | }, 13 | { 14 | "name":"菜单", 15 | "sub_button":[ 16 | { 17 | "type":"view", 18 | "name":"搜索", 19 | "url":"http://www.soso.com/" 20 | }, 21 | { 22 | "type":"view", 23 | "name":"视频", 24 | "url":"http://v.qq.com/" 25 | }, 26 | { 27 | "type":"click", 28 | "name":"赞一下我们", 29 | "key":"V1001_GOOD" 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /test/fixture/movie.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-webot/wechat-api/005bc10d6e8ade175df31ce3da131ffa8d669f5b/test/fixture/movie.mp4 -------------------------------------------------------------------------------- /test/fixture/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-webot/wechat-api/005bc10d6e8ade175df31ce3da131ffa8d669f5b/test/fixture/pic.jpg -------------------------------------------------------------------------------- /test/fixture/test.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-webot/wechat-api/005bc10d6e8ade175df31ce3da131ffa8d669f5b/test/fixture/test.mp3 --------------------------------------------------------------------------------