├── .coveralls.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── README_EN.md
├── assets
└── jason-wx-reward-code.png
├── demo
├── Order.js
├── index.html
├── index.js
├── oauth.html
├── wechat-config-sample.js
├── wechat-jssdk-demo-legacy.gif
└── wechat-jssdk-demo-new.gif
├── lib
├── Card.js
├── JSSDK.js
├── MiniProgram.js
├── OAuth.js
├── Payment.js
├── Wechat.js
├── client.js
├── code.js
├── config.js
├── index.js
├── store
│ ├── FileStore.js
│ ├── MongoStore.js
│ └── Store.js
└── utils.js
├── package.json
└── test
├── Card.test.js
├── JSSDK.test.js
├── MiniProgram.test.js
├── OAuth.test.js
├── Payment.test.js
├── Store.test.js
├── bootstrap.js
├── config.test.js
├── data-2.xml
├── data.xml
├── db.test.js
├── index.test.js
├── utils.test.js
└── xml.test.js
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | repo_token: iUSiEgqT1o1NNeHUvMQz2bxetC1NzTbZG
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 | .nyc_output
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # node-waf configuration
22 | .lock-wscript
23 |
24 | # Compiled binary addons (http://nodejs.org/api/addons.html)
25 | build/Release
26 |
27 | # Dependency directory
28 | node_modules
29 |
30 | # Optional npm cache directory
31 | .npm
32 |
33 | # Optional REPL history
34 | .node_repl_history
35 |
36 | *.map
37 | --no-cache
38 | .idea
39 | config.json
40 | .Ds_Store
41 | wechat-info*.json
42 | Gemfile
43 | Gemfile.lock
44 | _site/
45 | package-lock.json
46 | cert
47 | db_*.json
48 | demo/wechat-config.js
49 | dist
50 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | _site
4 | cert
5 | coverage
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "trailingComma": "es5"
5 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | repo_token: iUSiEgqT1o1NNeHUvMQz2bxetC1NzTbZG
3 | services: mongodb
4 | node_js:
5 | - 8
6 | - 10
7 | - 12
8 | - stable
9 | script: npm run coverage
10 | after_success: 'npm run coveralls'
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-present jason
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wechat-jssdk
2 | [](https://www.npmjs.com/package/wechat-jssdk)
3 | [](https://nodejs.org/)
4 | [](https://coveralls.io/github/JasonBoy/wechat-jssdk)
5 | [](https://www.npmjs.com/package/wechat-jssdk)
6 | [](https://github.com/prettier/prettier)
7 |
8 | 微信JSSDK与NodeJS及Web端整合
9 | WeChat JS-SDK integration with NodeJS and Web.
10 |
11 | [English](https://github.com/JasonBoy/wechat-jssdk/blob/master/README_EN.md)
12 | | [Release Notes](https://github.com/JasonBoy/wechat-jssdk/releases)
13 |
14 | 
15 |
16 | ## 主要功能
17 |
18 | - [:heartbeat:服务端](#使用方法)
19 | - [:heartpulse:浏览器端](#浏览器端)
20 | - [:unlock:OAuth网页授权](#oauth)
21 | - [:fries:微信卡券](#微信卡券)
22 | - [:credit_card:微信支付](#微信支付)
23 | - [:baby_chick:微信小程序](#小程序)
24 | - [:cd:使用Stores](#使用Stores)
25 | - [:movie_camera:完整 Demo](#demo)
26 |
27 | ## 使用方法
28 | ```bash
29 | npm install wechat-jssdk --save
30 | # 或者
31 | yarn add wechat-jssdk
32 | ```
33 |
34 | ```javascript
35 | const {Wechat} = require('wechat-jssdk');
36 | const wx = new Wechat(wechatConfig);
37 | ```
38 | ### Wechat 配置项
39 |
40 | `wechatConfig` 为以下格式:
41 | ```javascript
42 | {
43 | //第一个为设置网页授权回调地址
44 | wechatRedirectUrl: "http://yourdomain.com/wechat/oauth-callback",
45 | wechatToken: "xxx", //第一次在微信控制台保存开发者配置信息时使用
46 | appId: "xxx",
47 | appSecret: "xxx",
48 | card: true, //开启卡券支持,默认关闭
49 | payment: true, //开启支付支持,默认关闭
50 | merchantId: '', //商户ID
51 | paymentSandBox: true, //沙箱模式,验收用例
52 | paymentKey: '', //必传,验签密钥,TIP:获取沙箱密钥也需要真实的密钥,所以即使在沙箱模式下,真实验签密钥也需要传入。
53 | //pfx 证书
54 | paymentCertificatePfx: fs.readFileSync(path.join(process.cwd(), 'cert/apiclient_cert.p12')),
55 | //默认微信支付通知地址
56 | paymentNotifyUrl: `http://your.domain.com/api/wechat/payment/`,
57 | //小程序配置
58 | "miniProgram": {
59 | "appId": "mp_appid",
60 | "appSecret": "mp_app_secret",
61 | }
62 | }
63 | ```
64 |
65 | 其他支持的设置都有默认值,基本都是微信API的地址,且基本不会改变, 可以查看 `./lib/config.js`.
66 |
67 | ## 设置微信环境
68 | 1.去微信公众平台
69 |
70 | 下载类似 `MP_verify_XHZon7GAGRdcAFxx.txt` 这样的文件放到网站根目录, 如`http://yourdomain.com/MP_verify_XHZon7GAGRdcAFxx.txt`,微信会验证这个链接.
71 |
72 | 2.然后在你的express/koa app中提供一个接口给浏览器获取验证信息, @see [demo](#demo)
73 |
74 | ```javascript
75 | //express app:
76 | router.get('/get-signature', (req, res) => {
77 | wx.jssdk.getSignature(req.query.url).then(signatureData => {
78 | res.json(signatureData);
79 | });
80 | });
81 | //koa2/koa-router app:
82 | router.get('/get-signature', async ctx => {
83 | ctx.body = await wx.jssdk.getSignature(ctx.request.query.url);
84 | });
85 | ```
86 | 3.获取签名后,进入下一步浏览器端使用方法.
87 |
88 | ## 浏览器端
89 | ```javascript
90 | const WechatJSSDK = require('wechat-jssdk/dist/client.umd');
91 | //ES6 import
92 | import WechatJSSDK from 'wechat-jssdk/dist/client.umd';
93 |
94 | //没有打包的话直接script扔到html,然后从`window`获取, e.g:
95 | const wechatObj = new window.WechatJSSDK(config)
96 | ```
97 |
98 | `config`应该为:
99 |
100 | ```javascript
101 | const config = {
102 | //前4个是微信验证签名必须的参数,第2-4个参数为类似上面 '/get-signature' 从node端获取的结果
103 | 'appId': 'xxx',
104 | 'nonceStr': 'xxx',
105 | 'signature': 'xxx',
106 | 'timestamp': 'xxx',
107 | //下面为可选参数
108 | 'debug': true, //开启 debug 模式
109 | 'jsApiList': [], //设置所有想要使用的微信jsapi列表, 默认值为 ['updateAppMessageShareData','updateTimelineShareData','onMenuShareTimeline', 'onMenuShareAppMessage'],分享到朋友圈及聊天记录
110 | 'customUrl': '' //自定义微信js链接
111 | }
112 | const wechatObj = new WechatJSSDK(config);
113 | wechatObj.initialize()
114 | .then(w => {
115 | //set up your share info, "w" is the same instance as "wechatObj"
116 | })
117 | .catch(err => {
118 | console.error(err);
119 | });
120 | ```
121 | 验证签名成功后, 就可以自定义你的分享内容了:
122 | > sdk默认只注册了`updateAppMessageShareData`,`updateTimelineShareData`,`onMenuShareTimeline(wx即将废弃)`,`onMenuShareAppMessage(wx即将废弃)`
123 | ```javascript
124 | //自定义分享到聊天窗口
125 | //内部调用 `wechatObj.callWechatApi('updateAppMessageShareData', {...})`, 语法糖而已
126 | wechatObj.updateAppMessageShareData({
127 | type: 'link',
128 | title: 'title',
129 | link: location.href,
130 | imgUrl: '/logo.png',
131 | desc: 'description',
132 | success: function (){},
133 | fail: function (){},
134 | complete: function (){},
135 | cancel: function (){}
136 | });
137 | //自定义分享到朋友圈
138 | //语法糖
139 | wechatObj.updateTimelineShareData({
140 | type: 'link',
141 | title: 'title',
142 | link: location.href,
143 | imgUrl: '/logo.png'
144 | });
145 | ```
146 | 要获取原始的微信对象 `wx`,可以通过`wechatObj.getOriginalWx()`来获取。
147 | 如果第一次验证失败,可以在`error`回调里更新签名信息,并重新发验证请求:
148 | `wechatObj.signSignature(newSignatureConfig);`, `newSignatureConfig`只需包含:
149 | ```
150 | {
151 | 'nonceStr': 'xxx',
152 | 'signature': 'xxx',
153 | 'timestamp': 'xxx',
154 | }
155 | ```
156 |
157 | 调用其他微信接口:
158 | `wechatObj.callWechatApi(apiName, apiConfig)`
159 | `apiName`和`apiConfig`请参考微信官方接口文档
160 |
161 | ## OAuth
162 | 默认生成微信授权URL为 `wx.oauth.snsUserInfoUrl` 和 `wx.oauth.snsUserBaseUrl`,其中的默认回调URL为 `wechatConfig` 中配置的 `wechatRedirectUrl`.
163 | 你也可以通过调用 `wx.oauth. generateOAuthUrl(customUrl, scope, state)`来自定义回调地址
164 | ```javascript
165 | //callback url handler
166 | //如"wechatRedirectUrl"配置为 "http://127.0.0.1/wechat/oauth-callback", 你的路由需要为:
167 | router.get('/wechat/oauth-callback', function (req, res) {
168 | //得到code,获取用户信息
169 | wx.oauth.getUserInfo(req.query.code)
170 | .then(function(userProfile) {
171 | console.log(userProfile)
172 | res.render("demo", {
173 | wechatInfo: userProfile
174 | });
175 | });
176 | });
177 | ```
178 | TIP: 确保上面的重定向地址域名已经在微信里的授权回调地址设置里设置过。
179 | 
180 |
181 | ## 微信卡券
182 |
183 | 在wechatConfig设置 `card: true` 来支持卡券功能的服务端支持, 参考[demo](#demo).
184 | 要查看卡券 APIs, 参考 [cards apis](https://github.com/JasonBoy/wechat-jssdk/wiki/API#card-apis)
185 |
186 | ## 微信支付
187 |
188 | 在wechatConfig设置 `payment: true` 来支持微信支付功能的服务端支持, 其他一些支付必须的配置也需要一同设置.
189 | 参考 [demo](#demo).
190 | 要查看支付 APIs, 参考 [payment apis](https://github.com/JasonBoy/wechat-jssdk/wiki/API#payment-apis)
191 |
192 | ## 小程序
193 |
194 | 使用小程序的服务端支持([看接口](https://github.com/JasonBoy/wechat-jssdk/wiki/API#mini-programv4)), 在配置里设置小程序的`appId` 和 `appSecret`:
195 | ```javascript
196 | const { Wechat, MiniProgram } = require('wechat-jssdk');
197 | const wechatConfig = {
198 | "appId": "appid",
199 | "appSecret": "app_secret",
200 | //...other configs
201 | //...
202 | //小程序配置
203 | "miniProgram": {
204 | "appId": "mp_appid",
205 | "appSecret": "mp_app_secret",
206 | }
207 | };
208 | const wx = new Wechat(wechatConfig);
209 | //调用小程序接口
210 | wx.miniProgram.getSession('code');
211 |
212 | //手动实例化 MiniProgram
213 | const miniProgram = new MiniProgram({
214 | miniProgram: {
215 | "appId": "mp_appid",
216 | "appSecret": "mp_app_secret",
217 | }
218 | })
219 | ```
220 |
221 | ## 使用Stores
222 | Store用来自定义存储token持久化(如文件,数据库等待),实现自己的Store, 请查看[API](https://github.com/JasonBoy/wechat-jssdk/wiki/Store)
223 | 自带 Store: `FileStore`, `MongoStore`,默认为`FileStore`, 存储到`wechat-info.json`文件.
224 |
225 | ## APIs
226 | 查看 [API wiki](https://github.com/JasonBoy/wechat-jssdk/wiki/API)
227 |
228 | ## Demo
229 |
230 | 在v3.1.0后,demo页面增加卡券和支付的用例测试,
231 | Copy `demo/wechat-config-sample.js` 到 `demo/wechat-config.js`,
232 | 然后在里面里面修改 `appId`, `appSecret`, 及其他的[配置](#wechat-config) 如支付的其他配置如果需要使用支付功能的话.
233 |
234 | 在`./demo/index.js`中设置你自己的`appId`, `appSecret`, 然后 `npm start` 或 `npm run dev`, 使用微信开发者工具测试。
235 |
236 | ## Buy me a coffee
237 | 如果您觉得这个项目对您有用,可以请我喝杯咖啡
238 | 
239 |
240 | ## LICENSE
241 |
242 | MIT @ 2016-present [jason](http://blog.lovemily.me)
243 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | # wechat-jssdk
2 | [](https://www.npmjs.com/package/wechat-jssdk)
3 | [](https://nodejs.org/)
4 | [](https://travis-ci.org/JasonBoy/wechat-jssdk)
5 | [](https://coveralls.io/github/JasonBoy/wechat-jssdk)
6 | [](https://www.npmjs.com/package/wechat-jssdk)
7 | [](https://github.com/prettier/prettier)
8 |
9 |
10 | WeChat JS-SDK integration with NodeJS and Web.
11 |
12 | [中文使用文档](https://github.com/JasonBoy/wechat-jssdk/wiki/%E4%B8%AD%E6%96%87%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
13 | [Changelog](https://github.com/JasonBoy/wechat-jssdk/releases)
14 |
15 | 
16 |
17 | ## Features
18 |
19 | - [:heartbeat:Server-Side](#usage)
20 | - [:heartpulse:Browser-Side](#browser-side-usage)
21 | - [:unlock:OAuth](#oauth)
22 | - [:fries:Cards and Offers](#cards-and-offers)
23 | - [:credit_card:Wechat Payment](#payment)
24 | - [:baby_chick:Wechat Mini Program](#mini-program)
25 | - [:cd:Using Stores](#using-stores)
26 | - [:movie_camera:Full Featured Demo](#demo)
27 |
28 | ## Usage
29 |
30 | ```bash
31 | npm install wechat-jssdk --save
32 | # or
33 | yarn add wechat-jssdk
34 | ```
35 |
36 | ```javascript
37 | const {Wechat} = require('wechat-jssdk');
38 | const wx = new Wechat(wechatConfig);
39 | ```
40 |
41 | ## Wechat Config
42 |
43 | `wechatConfig` info:
44 |
45 | ```javascript
46 | {
47 | //set your oauth redirect url, defaults to localhost
48 | "wechatRedirectUrl": "http://yourdomain.com/wechat/oauth-callback",
49 | //"wechatToken": "wechat_token", //not necessary required
50 | "appId": "appid",
51 | "appSecret": "app_secret",
52 | card: true, //enable cards
53 | payment: true, //enable payment support
54 | merchantId: '', //
55 | paymentSandBox: true, //dev env
56 | paymentKey: '', //API key to gen payment sign
57 | paymentCertificatePfx: fs.readFileSync(path.join(process.cwd(), 'cert/apiclient_cert.p12')),
58 | //default payment notify url
59 | paymentNotifyUrl: `http://your.domain.com/api/wechat/payment/`,
60 | //mini program config
61 | "miniProgram": {
62 | "appId": "mp_appid",
63 | "appSecret": "mp_app_secret",
64 | }
65 | }
66 | ```
67 |
68 | ### Setup your Wechat ENV
69 | 1.Set your own URL in [Wechat Website](https://mp.weixin.qq.com)
70 |
71 | Usually wechat will provide you a `MP_verify_XHZon7GAGRdcAFxx.txt` like file to ask you to put that on your website root,
72 | which will be accessed by wechat on `http://yourdomain.com/MP_verify_XHZon7GAGRdcAFxx.txt` to verify that you own the domain.
73 |
74 | 2.You should also provide a api for your browser to get token for the current url, see [demo](#demo)
75 |
76 | ```javascript
77 | //express app for example:
78 | router.get('/get-signature', (req, res) => {
79 | wx.jssdk.getSignature(req.query.url).then(signatureData => {
80 | res.json(signatureData);
81 | });
82 | });
83 | //koa2/koa-router app for example:
84 | router.get('/get-signature', async ctx => {
85 | ctx.body = await wx.jssdk.getSignature(ctx.request.query.url);
86 | });
87 | ```
88 | 3.Now you can get to the next step in your browser to pass the verification.
89 |
90 |
91 | ## Browser Side Usage
92 |
93 | You can use it from the browser side as follows. Since we have [configured the `browser` field in package.json](https://github.com/yuezk/wechat-jssdk/blob/3ab192a5a67e8db65b2ae6cd9978013eef363b73/package.json#L7), the bundlers (e.g., webpack or rollup, etc.) will resolve the module to `wechat-jssdk/dist/client.umd.js`.
94 |
95 | ```javascript
96 | const WechatJSSDK = require('wechat-jssdk');
97 | //ES6 import
98 | import WechatJSSDK from 'wechat-jssdk';
99 | const wechatObj = new WechatJSSDK(config)
100 |
101 | // or if you do not have a bundle process, just add the script tag, and access "WechatJSSDK" from window, e.g:
102 | const wechatObj = new window.WechatJSSDK(config)
103 | ```
104 | where config will be:
105 | ```javascript
106 | const config = {
107 | //below are mandatory options to finish the wechat signature verification
108 | //the 4 options below should be received like api '/get-signature' above
109 | 'appId': 'app_id',
110 | 'nonceStr': 'your_nonceStr',
111 | 'signature': 'url_signature',
112 | 'timestamp': 'your_timestamp',
113 | //below are optional
114 | //enable debug mode, same as debug
115 | 'debug': true,
116 | 'jsApiList': [], //optional, pass all the jsapi you want, the default will be ['onMenuShareTimeline', 'onMenuShareAppMessage']
117 | 'customUrl': '' //set custom weixin js script url, usually you don't need to add this js manually
118 | }
119 | const wechatObj = new WechatJSSDK(config);
120 | wechatObj.initialize()
121 | .then(w => {
122 | //set up your share info, "w" is the same instance as "wechatObj"
123 | })
124 | .catch(err => {
125 | console.error(err);
126 | });
127 | ```
128 | after signature signed successfully, you can customize the share information:
129 |
130 | ```javascript
131 | //customize share on chat info
132 | //sugar method for `wechatObj.callWechatApi('onMenuShareAppMessage', {...})`
133 | wechatObj.shareOnChat({
134 | type: 'link',
135 | title: 'title',
136 | link: location.href,
137 | imgUrl: '/logo.png',
138 | desc: 'description',
139 | success: function (){},
140 | cancel: function (){}
141 | });
142 | //customize share on timeline info
143 | //sugar method
144 | wechatObj.shareOnMoment({
145 | type: 'link',
146 | title: 'title',
147 | link: location.href,
148 | imgUrl: '/logo.png'
149 | });
150 | ```
151 | You can also access the original wechat object `wx` from `wechatObj.getOriginalWx()`.
152 |
153 | Call other wechat apis: `wechatObj.callWechatApi(apiName, config)`:
154 |
155 | ```javascript
156 | wechatObj.callWechatApi('onMenuShareAppMessage', {
157 | type: 'link',
158 | title: 'title',
159 | link: location.href,
160 | imgUrl: '/logo.png',
161 | desc: 'description',
162 | success: function (){},
163 | cancel: function (){}
164 | });
165 | ```
166 | or with the original one:
167 | `wechatObj.getOriginalWx().onMenuShareAppMessage(config)`
168 |
169 | ## OAuth
170 | Wechat support web OAuth to get user profile in wechat app.
171 | In your page, provide a link, which you can get by `wx.oauth.snsUserInfoUrl` which is the default oauth url, to the wechat OAuth page,
172 | also you need provide a callback url(as show below) to get the wechat code after user click Agree button, the callback url is configured in the `wechatConfig` object above while initializing,
173 | but you can customize your own callback url by using `wx.oauth.generateOAuthUrl(customUrl, scope, state)` api.
174 | ```javascript
175 | //in node:
176 | const wx = new Wechat(config);
177 | const url = wx.oauth.generateOAuthUrl('http://mycustom.com/oauth-callback', 'snsapi_userinfo', 'custom_state');
178 | res.render("oauth-page", {
179 | wechatOAuthUrl: url,
180 | });
181 | //insert "wechatOAuthUrl" into your html:
182 |
183 | //custom callback url, agree clicked by user, come back here:
184 | router.get('/oauth-callback', function (req, res) {
185 | wx.oauth.getUserInfo(req.query.code)
186 | .then(function(userProfile) {
187 | console.log(userProfile)
188 | res.render("demo", {
189 | wechatInfo: userProfile
190 | });
191 | });
192 | });
193 | ```
194 |
195 | ## Cards and Offers
196 |
197 | since(v3.1)
198 | Set `card: true` in config to enable the cards support on server side, see [demo](#demo).
199 | For cards APIs, see [cards apis](https://github.com/JasonBoy/wechat-jssdk/wiki/API#card-apis)
200 |
201 | ## Payment
202 |
203 | since(v3.1)
204 | Set `payment: true` in config to enable the payment support on server side, you should also provide payment related info.
205 | See [demo](#demo).
206 | For payment APIs, see [payment apis](https://github.com/JasonBoy/wechat-jssdk/wiki/API#payment-apis)
207 |
208 | ## Mini Program
209 |
210 | since(v4)
211 | To enable mini program support([see API](https://github.com/JasonBoy/wechat-jssdk/wiki/API#mini-programv4)), you can just set mini program `appId` & `appSecret` in config:
212 | ```javascript
213 | const { Wechat, MiniProgram } = require('wechat-jssdk');
214 | const wechatConfig = {
215 | "appId": "appid",
216 | "appSecret": "app_secret",
217 | //...other configs
218 | //...
219 | //mini program config
220 | "miniProgram": {
221 | "appId": "mp_appid",
222 | "appSecret": "mp_app_secret",
223 | }
224 | };
225 | const wx = new Wechat(wechatConfig);
226 | //access mini program instance
227 | wx.miniProgram.getSession('code');
228 |
229 | //Use MiniProgram directly
230 | const miniProgram = new MiniProgram({
231 | miniProgram: {
232 | "appId": "mp_appid",
233 | "appSecret": "mp_app_secret",
234 | }
235 | })
236 | ```
237 |
238 | ## Using Stores
239 |
240 | [Store](https://github.com/JasonBoy/wechat-jssdk/wiki/Store) are used to save url signatures into files, dbs, etc..., but also keep a copy in memory for better performence.
241 | The default store used is `FileStore` which will persist tokens and signatures into `wechat-info.json` file every 10 minutes, also it will load these info from the file in next initialization.
242 | Built in Stores: `FileStore`, `MongoStore`,
243 | ### Using Custom Stores:
244 |
245 | ```javascript
246 | ...
247 | const {Wechat, MongoStore, FileStore} = require('wechat-jssdk');
248 | const wx = new Wechat({
249 | appId: 'xxx',
250 | ...,
251 | //file store
252 | //store: new FileStore(),
253 | //======
254 | //pass the MongoStore instance to config
255 | //default 127.0.0.1:27017/wechat db, no need to pass anything to constructor
256 | store: new MongoStore({
257 | //dbName: 'myWechat', //default wechat
258 | dbAddress: 'mongodb://127.0.0.1:27017/wechat', //set the whole connection uri by yourself
259 | dbOptions: {}, //set mongoose connection config
260 | })
261 | })
262 |
263 | ```
264 |
265 | ### Create your own Store
266 |
267 | You can also create own Store to store tokens anywhere you want, by doing that, you may need to extend the base `Store` class, and reimplement the [apis](https://github.com/JasonBoy/wechat-jssdk/wiki/Store) you need:
268 |
269 | ```javascript
270 | const {Store} = require('wechat-jssdk');
271 | class CustomStore extends Store {
272 | constructor (options) {
273 | super();
274 | console.log('using my own store!');
275 | }
276 | }
277 | ```
278 |
279 | ## APIs
280 | see [API wiki](https://github.com/JasonBoy/wechat-jssdk/wiki/API)
281 |
282 | [A Blog About This](http://blog.lovemily.me/next-generation-wechat-jssdk-integration-with-nodejs/)
283 |
284 | ## Debug
285 |
286 | Add `DEBUG=wechat*` when start your app to enable wechat-jssdk debug
287 | `DEBUG=wechat* node your-app.js`
288 |
289 | ## Demo
290 |
291 | In v3.1+, the demo page is updated to test the new added `Cards & Offers` and `Payment` support.
292 | Copy the `demo/wechat-config-sample.js` to `demo/wechat-config.js`,
293 | and use your own `appId`, `appSecret`, and other [configs](#wechat-config) like payment if your want to enable them.
294 |
295 | Use `npm start` or `npm run dev` to start the demo.
296 |
297 | ## LICENSE
298 |
299 | MIT @ 2016-present [jason](http://blog.lovemily.me)
300 |
--------------------------------------------------------------------------------
/assets/jason-wx-reward-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JasonBoy/wechat-jssdk/331c8722a89732e22e7910207c8092d332202ca1/assets/jason-wx-reward-code.png
--------------------------------------------------------------------------------
/demo/Order.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('wechat-Order');
4 | const path = require('path');
5 | const low = require('lowdb');
6 | const FileSync = require('lowdb/adapters/FileSync');
7 | const isEmpty = require('lodash.isempty');
8 |
9 | const adapter = new FileSync(path.join(__dirname, '../db_demo.json'));
10 | const db = low(adapter);
11 |
12 | const utils = require('../lib/utils');
13 | const Payment = require('../lib/Payment');
14 |
15 | db.defaults({
16 | //our own system orders
17 | orders: [],
18 | //wechat unified orders
19 | unifiedOrders: [],
20 | //wechat notified order states
21 | wechatOrders: [],
22 | //refund
23 | refundOrders: [],
24 | //save wechat payment notify result
25 | wechatNotifyOrders: [],
26 | wechatNotifyRefunds: [],
27 | }).write();
28 |
29 | const defaultInfo = {
30 | device_info: 'wechat_test_web',
31 | body: `ORDER_测试`,
32 | detail: JSON.stringify({
33 | goods_detail: [
34 | {
35 | goods_id: 'iphone6s_16G',
36 | wxpay_goods_id: '1001',
37 | goods_name: 'iPhone6s 16G',
38 | quantity: 1,
39 | price: 528800,
40 | goods_category: '123456',
41 | body: '苹果手机',
42 | },
43 | ],
44 | }),
45 | attach: '上海分店',
46 | total_fee: '101',
47 | spbill_create_ip: '127.0.0.1',
48 | // time_start: utils.simpleDate(now),
49 | // time_expire: utils.simpleDate(nowPlusTwoHours),
50 | // goods_tag: 'wx_test',
51 | trade_type: Payment.PAYMENT_TYPE.JSAPI,
52 | // notify_url: 'http://beautytest.yjyyun.com/payment/',
53 | // product_id: '',
54 | // limit_pay: '',
55 | // openid: info.openId,
56 | scene_info: JSON.stringify({
57 | id: 'SH001',
58 | name: '上大餐厅',
59 | area_code: '200100',
60 | address: '广中路引力楼1楼',
61 | }),
62 | };
63 |
64 | /**
65 | * A demo implementation for order & payment
66 | */
67 | class Order {
68 | constructor(options) {
69 | this.payment = options.payment;
70 | }
71 |
72 | createOrderCase1(info) {
73 | const now = new Date();
74 | const order = Object.assign(
75 | {},
76 | defaultInfo,
77 | {
78 | body: defaultInfo.body + '_1',
79 | time_start: utils.simpleDate(now),
80 | total_fee: '101',
81 | // goods_tag: 'wx_test',
82 | },
83 | info
84 | );
85 | return this.createOrder(order);
86 | }
87 |
88 | createOrderCase2(info) {
89 | const now = new Date();
90 | const order = Object.assign(
91 | {},
92 | defaultInfo,
93 | {
94 | body: defaultInfo.body + '_2',
95 | time_start: utils.simpleDate(now),
96 | total_fee: '102',
97 | goods_tag: 'wx_test',
98 | },
99 | info
100 | );
101 | return this.createOrder(order);
102 | }
103 |
104 | createOrderCase3(info) {
105 | const now = new Date();
106 | const order = Object.assign(
107 | {},
108 | defaultInfo,
109 | {
110 | body: defaultInfo.body + '_3',
111 | time_start: utils.simpleDate(now),
112 | total_fee: '130',
113 | goods_tag: 'wx_test',
114 | },
115 | info
116 | );
117 | return this.createOrder(order);
118 | }
119 |
120 | createOrderCase4(info) {
121 | const now = new Date();
122 | const order = Object.assign(
123 | {},
124 | defaultInfo,
125 | {
126 | body: defaultInfo.body + '_4',
127 | time_start: utils.simpleDate(now),
128 | total_fee: '131',
129 | goods_tag: 'wx_test',
130 | },
131 | info
132 | );
133 | return this.createOrder(order);
134 | }
135 |
136 | createOrderCase5(info) {
137 | const now = new Date();
138 | const order = Object.assign(
139 | {},
140 | defaultInfo,
141 | {
142 | body: defaultInfo.body + '_5',
143 | time_start: utils.simpleDate(now),
144 | total_fee: '132',
145 | goods_tag: 'wx_test',
146 | },
147 | info
148 | );
149 | return this.createOrder(order);
150 | }
151 |
152 | createOrderCase6(info) {
153 | const now = new Date();
154 | const order = Object.assign(
155 | {},
156 | defaultInfo,
157 | {
158 | body: defaultInfo.body + '_6',
159 | time_start: utils.simpleDate(now),
160 | total_fee: '133',
161 | goods_tag: 'wx_test',
162 | },
163 | info
164 | );
165 | return this.createOrder(order);
166 | }
167 |
168 | createOrderCase7(info) {
169 | const now = new Date();
170 | const order = Object.assign(
171 | {},
172 | defaultInfo,
173 | {
174 | body: defaultInfo.body + '_7',
175 | time_start: utils.simpleDate(now),
176 | total_fee: '134',
177 | goods_tag: 'wx_test',
178 | },
179 | info
180 | );
181 | return this.createOrder(order);
182 | }
183 |
184 | //optional
185 | createOrderCase8(info) {
186 | const now = new Date();
187 | const order = Object.assign(
188 | {},
189 | defaultInfo,
190 | {
191 | body: defaultInfo.body + '_8',
192 | time_start: utils.simpleDate(now),
193 | total_fee: '179',
194 | goods_tag: 'wx_test',
195 | },
196 | info
197 | );
198 | return this.createOrder(order);
199 | }
200 |
201 | createOrder(order) {
202 | return this.payment
203 | .unifiedOrder(order)
204 | .then(result => {
205 | const requestData = Object.assign(
206 | { id: result.requestData.out_trade_no },
207 | result.requestData
208 | );
209 | const responseData = Object.assign(
210 | { id: result.responseData.out_trade_no },
211 | result.responseData
212 | );
213 | const hasOrder = db
214 | .get('orders')
215 | .find({ id: requestData.id })
216 | .has('id')
217 | .value();
218 | if (hasOrder) {
219 | } else {
220 | db.get('orders')
221 | .push(requestData)
222 | .write();
223 | db.get('unifiedOrders')
224 | .push(responseData)
225 | .write();
226 | debug('add new order & unified order finished!');
227 | }
228 | return Promise.resolve(responseData);
229 | })
230 | .then(data => {
231 | return this.payment
232 | .generateChooseWXPayInfo(data.prepay_id)
233 | .then(chooseWXPayData => {
234 | console.log('parsed data:', data);
235 | console.log('WXpaydata data:', chooseWXPayData);
236 | return Promise.resolve({
237 | orderId: data.out_trade_no,
238 | chooseWXPay: chooseWXPayData,
239 | });
240 | });
241 | });
242 | }
243 |
244 | queryOrder(tradeNo) {
245 | return this.payment
246 | .queryOrder({
247 | out_trade_no: tradeNo,
248 | })
249 | .then(result => {
250 | const temp = Object.assign(
251 | { id: result.responseData.out_trade_no },
252 | result.responseData
253 | );
254 | db.get('wechatOrders')
255 | .push(temp)
256 | .write();
257 | debug('write wechat query order finished!');
258 | return Promise.resolve(result);
259 | });
260 | }
261 |
262 | getOrderFromDB(tradeNo) {
263 | return db
264 | .get('orders')
265 | .find({ id: tradeNo })
266 | .value();
267 | }
268 |
269 | updateNotifyResult(data) {
270 | const order = db
271 | .get('wechatNotifiesOrders')
272 | .find({ id: data.out_trade_no })
273 | .value();
274 | db.get('orders')
275 | .find({ id: data.out_trade_no })
276 | .assign({ processed: true })
277 | .value();
278 |
279 | if (!isEmpty(order)) {
280 | if (order.processed) return;
281 | //update existing order info
282 | db.get('wechatNotifiesOrders')
283 | .find({ id: data.out_trade_no })
284 | .assign(data)
285 | .write();
286 | return;
287 | }
288 | const temp = Object.assign(
289 | { id: data.out_trade_no, processed: true },
290 | data
291 | );
292 | db.get('wechatNotifiesOrders')
293 | .push(temp)
294 | .write();
295 | }
296 |
297 | updateNotifyRefundResult(data) {
298 | const order = db
299 | .get('wechatNotifyRefunds')
300 | .find({ id: data.out_trade_no })
301 | .value();
302 | if (!isEmpty(order)) {
303 | if (order.processed) return;
304 | //update existing order info
305 | db.get('wechatNotifyRefunds')
306 | .find({ id: data.out_trade_no })
307 | .assign(data)
308 | .write();
309 | return;
310 | }
311 | const temp = Object.assign(
312 | { id: data.out_trade_no, processed: true },
313 | data
314 | );
315 | db.get('wechatNotifyRefunds')
316 | .push(temp)
317 | .write();
318 | }
319 | }
320 |
321 | module.exports = Order;
322 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Wechat JSSDK DEMO
8 |
9 |
10 |
11 | =====点击右上角分享!=====
12 | =====OAuth 网页授权=====
13 | OAuth 用户信息授权
14 | Implicit OAuth 静默授权
15 | OAuth without new code 用缓存code直接拿用户信息
16 | =====CARD 卡券=====
17 | ChooseCard 选择卡券并打开
18 | OpenCard 打开卡券
19 | AddCard 添加卡券
20 | =====Payment 支付=====
21 |
22 |
23 |
24 |
25 | QueryOrder 查询上一个创建的订单
26 | UnifiedOrder 创建订单用例1
27 | UnifiedOrder 创建订单用例2
28 | UnifiedOrder 创建订单用例3
29 | UnifiedOrder 创建订单用例4
30 | UnifiedOrder 创建订单用例5
31 | UnifiedOrder 创建订单用例6
32 | UnifiedOrder 创建订单用例7
33 | UnifiedOrder 创建订单用例8(可选)
34 | 下载对账单
35 | chooseWXPay 微信支付上一个订单
36 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const express = require('express');
4 | const http = require('http');
5 | const nunjucks = require('nunjucks');
6 | const { Wechat, Payment } = require('../lib');
7 | const path = require('path');
8 | const debug = require('debug')('wechat-demo');
9 | const bodyParser = require('body-parser');
10 | const isEmpty = require('lodash.isempty');
11 | const utils = require('../lib/utils');
12 |
13 | const cookieParser = require('cookie-parser');
14 | const session = require('express-session');
15 |
16 | const wechatConfig = require('./wechat-config');
17 | const Order = require('./Order');
18 |
19 | const DOMAIN = wechatConfig.domain;
20 |
21 | const wx = new Wechat(wechatConfig);
22 |
23 | const order = new Order({ payment: wx.payment });
24 |
25 | const app = express();
26 |
27 | nunjucks.configure(__dirname, {
28 | autoescape: true,
29 | express: app,
30 | noCache: true,
31 | });
32 |
33 | // app.engine('html', nunjucks);
34 | app.set('view engine', 'html');
35 | app.enable('trust proxy');
36 | app.set('views', __dirname);
37 |
38 | app.use(cookieParser());
39 | app.use(
40 | session({
41 | name: 'sid',
42 | secret: 'wechat-app',
43 | saveUninitialized: true,
44 | resave: true,
45 | })
46 | );
47 |
48 | app.use(function(req, res, next) {
49 | res.locals.appId = wechatConfig.appId;
50 | res.locals.domain = DOMAIN;
51 | next();
52 | });
53 |
54 | app.get('/', function(req, res) {
55 | //also you can generate one at runtime:
56 | const implicitOAuthUrl = wx.oauth.generateOAuthUrl(
57 | DOMAIN + '/implicit-oauth',
58 | 'snsapi_base'
59 | );
60 | res.render('index.html', {
61 | oauthUrl: wx.oauth.snsUserInfoUrl,
62 | implicitOAuth: implicitOAuthUrl,
63 | });
64 | });
65 |
66 | app.get('/api/wechat', function(req, res) {
67 | if (wx.jssdk.verifySignature(req.query)) {
68 | res.send(req.query.echostr);
69 | return;
70 | }
71 | res.send('error');
72 | });
73 |
74 | app.get('/get-signature', function(req, res) {
75 | console.log(req.query);
76 | wx.jssdk.getSignature(req.query.url).then(
77 | data => {
78 | console.log('OK', data);
79 | res.json(data);
80 | },
81 | reason => {
82 | console.error(reason);
83 | res.json(reason);
84 | }
85 | );
86 | });
87 |
88 | /**
89 | * @see wechatRedirectUrl in Wechat config
90 | */
91 | app.get('/oauth', function(req, res) {
92 | //use default openid as the key
93 | const key = req.session.openid;
94 |
95 | //use custom key for oauth token store
96 | // const key = req.sessionID;
97 | // console.log('oauth sessionID: %s', key);
98 | wx.oauth.getUserInfo(req.query.code, key).then(function(userProfile) {
99 | console.log('userProfile:', userProfile);
100 | //set openid to session to use in following request
101 | req.session.openid = userProfile.openid;
102 | console.log(req.session.openid);
103 | res.render('oauth.html', {
104 | wechatInfo: JSON.stringify(userProfile),
105 | });
106 | });
107 | });
108 |
109 | app.get('/implicit-oauth', function(req, res) {
110 | const redirect = req.query.from;
111 | wx.oauth.getUserBaseInfo(req.query.code).then(function(tokenInfo) {
112 | console.log('implicit oauth: ', tokenInfo);
113 | // console.log('implicit oauth: ', JSON.stringify(tokenInfo));
114 | req.session.openid = tokenInfo.openid;
115 | if (redirect) {
116 | res.redirect(redirect);
117 | return;
118 | }
119 | res.render('oauth.html', {
120 | wechatInfo: JSON.stringify(tokenInfo, null, 2),
121 | });
122 | });
123 | });
124 |
125 | app.get('/oauth-cache', function(req, res) {
126 | const key = req.session.openid;
127 | console.log('openid: ', key);
128 |
129 | // const sid = req.sessionID;
130 | // console.log('sessionID: %s', sid);
131 |
132 | //get user info without code, but with cached access token,
133 | //if cached token is expired, or cannot refresh the token,
134 | //it will redirect to the "/oauth" router above in catch handler to get new code
135 | wx.oauth
136 | .getUserInfo(null, key)
137 | .then(function(userProfile) {
138 | console.log(userProfile);
139 | res.render('oauth.html', {
140 | wechatInfo: JSON.stringify(userProfile),
141 | });
142 | })
143 | .catch(() => {
144 | //need to get new code
145 | res.redirect(wx.oauth.snsUserInfoUrl);
146 | });
147 | });
148 |
149 | app.get('/choose-card', function(req, res) {
150 | const qs = req.query;
151 | wx.card
152 | .getCardSignature(qs.shopId, qs.cardType, qs.cardId)
153 | .then(sigInfo => {
154 | res.json(sigInfo);
155 | })
156 | .catch(reason => {
157 | res.json(reason);
158 | });
159 | });
160 |
161 | app.get('/get-card-ext', function(req, res) {
162 | const qs = req.query;
163 | wx.card
164 | .getCardExt(qs.cardId, '', '', '', 'wechat-jssdk')
165 | .then(sigInfo => {
166 | res.json({ data: sigInfo });
167 | })
168 | .catch(reason => {
169 | res.json(reason);
170 | });
171 | });
172 |
173 | app.get('/decode-card-code', function(req, res) {
174 | wx.card
175 | .decryptCardCode(req.query.encryptCode)
176 | .then(data => {
177 | res.json(data);
178 | })
179 | .catch(data => {
180 | res.json(data);
181 | });
182 | });
183 |
184 | app.get('/client.js', function(req, res) {
185 | res.sendFile(path.join(__dirname, '../dist/client.umd.js'));
186 | });
187 |
188 | app.get('/create-order', function(req, res) {
189 | const openid = req.session.openid;
190 | console.log('req.session.openid:', openid);
191 | const orderCase = req.query.case;
192 | const orderInfo = {
193 | openid: req.session.openid || 'oy5F1wQTfhx4-V3L5TcUn5V9v2Lo',
194 | spbill_create_ip: '104.247.128.2', //req.ip,
195 | };
196 | let p = undefined;
197 | switch (orderCase) {
198 | case '1':
199 | p = order.createOrderCase1(orderInfo);
200 | break;
201 | case '2':
202 | p = order.createOrderCase2(orderInfo);
203 | break;
204 | case '3':
205 | p = order.createOrderCase3(orderInfo);
206 | break;
207 | case '4':
208 | p = order.createOrderCase4(orderInfo);
209 | break;
210 | case '5':
211 | p = order.createOrderCase5(orderInfo);
212 | break;
213 | case '6':
214 | p = order.createOrderCase6(orderInfo);
215 | break;
216 | case '7':
217 | p = order.createOrderCase7(orderInfo);
218 | break;
219 | case '8':
220 | p = order.createOrderCase8(orderInfo);
221 | break;
222 | default:
223 | p = order.createOrderCase1(orderInfo);
224 | break;
225 | }
226 |
227 | p.then(data => {
228 | console.log(data.orderId);
229 | req.session.orderId = data.orderId;
230 | res.json(data.chooseWXPay);
231 | }).catch(err => {
232 | res.json(err);
233 | });
234 | });
235 |
236 | app.get('/query-order', function(req, res) {
237 | const orderId = req.query.tradeNo || req.session.orderId;
238 | if (!orderId) {
239 | res.json({
240 | msg: 'no available out_trade_no!',
241 | });
242 | return;
243 | }
244 |
245 | order
246 | .queryOrder(orderId)
247 | .then(data => {
248 | res.json(data);
249 | })
250 | .catch(err => {
251 | res.json(err);
252 | });
253 | });
254 |
255 | app.get('/download-bill', function(req, res) {
256 | const query = req.query;
257 | wx.payment
258 | .downloadBill(query.billDate, Payment.DOWNLOAD_BILL_TYPE.SUCCESS)
259 | .then(result => {
260 | if (result.body) {
261 | res.type('zip');
262 | res.send(result.body);
263 | }
264 | })
265 | .catch(err => {
266 | console.error(err);
267 | res.json(err);
268 | });
269 | });
270 |
271 | app.get('/settlements', function(req, res) {
272 | const {
273 | usetag, //int 1 -> settled, 2 -> unsettled
274 | offset, //int
275 | limit, //int
276 | date_start, //string e.g. '20180701'
277 | date_end, //string e.g. '20180820'
278 | // visit https://pay.weixin.qq.com/wiki/doc/api/external/jsapi.php?chapter=9_14&index=9 for more info
279 | } = req.query;
280 | wx.payment
281 | .querySettlement({
282 | usetag,
283 | offset,
284 | limit,
285 | date_start,
286 | date_end,
287 | })
288 | .then(result => {
289 | res.json(result.responseData);
290 | })
291 | .catch(err => {
292 | console.error(err);
293 | res.json(err);
294 | });
295 | });
296 |
297 | app.get('/exchange-rate', (req, res) => {
298 | const {
299 | fee_type, //string 'USD'
300 | date, //string '20180801'
301 | // visit https://pay.weixin.qq.com/wiki/doc/api/external/jsapi.php?chapter=9_15&index=10 for more info
302 | } = req.query;
303 | wx.payment
304 | .queryExchangeRate({
305 | fee_type: fee_type || 'GBP',
306 | date: date || utils.simpleDate(new Date(), 'YYYYMMDD'),
307 | })
308 | .then(result => {
309 | res.json(result.responseData);
310 | })
311 | .catch(err => {
312 | console.error(err);
313 | res.json(err);
314 | });
315 | });
316 |
317 | //demo: unified order pay result notify_url goes here
318 | app.post('/pay-result-notify', bodyParser.text(), function(req, res) {
319 | wx.payment
320 | .parseNotifyData(req.body)
321 | .then(data => {
322 | const sign = data.sign;
323 | data.sign = undefined;
324 | const genSignData = wx.payment.generateSignature(data, data.sign_type);
325 | //case test, only case 6 will return sign
326 | if (!sign || (sign && sign === genSignData.sign)) {
327 | const tradeNo = data.out_trade_no;
328 | if (tradeNo) {
329 | const order = order.getOrderFromDB(tradeNo);
330 | //order info inconsistent
331 | if (isEmpty(order) || order.total_fee != data.total_fee) {
332 | return Promise.reject(new Error('notify data not consistent!'));
333 | }
334 | //already processed
335 | if (order && order.processed) {
336 | return;
337 | }
338 | }
339 |
340 | order.updateNotifyResult(data);
341 | //sign check and send back
342 | wx.payment.replyData(true).then(ret => {
343 | res.send(ret);
344 | });
345 | return;
346 | }
347 | return Promise.reject(new Error('notify sign not matched!'));
348 | })
349 | .catch(err => {
350 | console.error(err);
351 | // wx.payment.replyData(false).then(ret => {
352 | // res.send(ret);
353 | // });
354 | });
355 | });
356 | //process refund notify result
357 | app.post('/refund-result-notify', bodyParser.text(), function(req, res) {
358 | wx.payment
359 | .decryptRefundNotifyResult(req.body)
360 | .then(result => {
361 | const parsedXMLData = result.parsedXMLData;
362 | const decryptedReqInfoData = result.decryptedData;
363 | order.updateNotifyRefundResult(
364 | Object.assign(parsedXMLData, decryptedReqInfoData)
365 | );
366 |
367 | wx.payment.replyData(true).then(ret => {
368 | res.send(ret);
369 | });
370 | })
371 | .catch(err => {
372 | console.error(err);
373 | wx.payment.replyData(false).then(ret => {
374 | res.send(ret);
375 | });
376 | });
377 | });
378 |
379 | const server = http.createServer(app);
380 | const port = process.env.PORT || 3000;
381 | //should use like nginx to proxy the request to 3000, the signature domain must be on PORT 80.
382 | server.listen(port);
383 | server.on('listening', function() {
384 | debug('Express listening on port %d', port);
385 | });
386 |
387 | process.on('exit', function() {
388 | wx.store.flush();
389 | });
390 |
--------------------------------------------------------------------------------
/demo/oauth.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Wechat JSSDK DEMO
7 |
8 |
9 |
10 | User profile:
11 |
12 |
13 | {{ wechatInfo }}
14 |
15 |
16 | Back to Home
17 |
18 |
19 |
--------------------------------------------------------------------------------
/demo/wechat-config-sample.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const Wechat = require('../lib');
5 | const MongoStore = Wechat.MongoStore;
6 | const FileStore = Wechat.FileStore;
7 |
8 | const DOMAIN = 'http://your.domain.com';
9 |
10 | module.exports = {
11 | //=====a service account test=====
12 | domain: DOMAIN,
13 | wechatToken: '',
14 | appId: '',
15 | appSecret: '',
16 | wechatRedirectUrl: `${DOMAIN}/oauth`,
17 | // store: new MongoStore({limit: 5}),
18 | store: new FileStore({ interval: 1000 * 60 * 3 }),
19 | card: true,
20 | payment: true,
21 | merchantId: '',
22 | paymentSandBox: true, //dev env
23 | paymentKey: '',
24 | // paymentSandBoxKey: '',
25 | paymentCertificatePfx: fs.readFileSync(
26 | path.join(process.cwd(), 'cert/apiclient_cert.p12')
27 | ),
28 | paymentNotifyUrl: `${DOMAIN}/api/wechat/payment/`,
29 | };
30 |
--------------------------------------------------------------------------------
/demo/wechat-jssdk-demo-legacy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JasonBoy/wechat-jssdk/331c8722a89732e22e7910207c8092d332202ca1/demo/wechat-jssdk-demo-legacy.gif
--------------------------------------------------------------------------------
/demo/wechat-jssdk-demo-new.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JasonBoy/wechat-jssdk/331c8722a89732e22e7910207c8092d332202ca1/demo/wechat-jssdk-demo-new.gif
--------------------------------------------------------------------------------
/lib/Card.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('wechat-Card');
4 |
5 | const isEmpty = require('lodash.isempty');
6 |
7 | const utils = require('./utils');
8 | const config = require('./config');
9 |
10 | const Store = require('./store/Store');
11 | const FileStore = require('./store/FileStore');
12 | const codeUtils = require('./code');
13 |
14 | const wxConfig = config.getDefaultConfiguration();
15 |
16 | const CODE_TYPE = {
17 | CODE_TYPE_QRCODE: 'CODE_TYPE_QRCODE',
18 | CODE_TYPE_BARCODE: 'CODE_TYPE_BARCODE',
19 | CODE_TYPE_ONLY_QRCODE: 'CODE_TYPE_ONLY_QRCODE',
20 | CODE_TYPE_TEXT: 'CODE_TYPE_TEXT',
21 | CODE_TYPE_NONE: 'CODE_TYPE_NONE',
22 | };
23 |
24 | const CARD_TYPE = {
25 | GROUPON: 'GROUPON',
26 | CASH: 'CASH',
27 | DISCOUNT: 'DISCOUNT',
28 | GIFT: 'GIFT',
29 | GENERAL_COUPON: 'GENERAL_COUPON',
30 | };
31 |
32 | const TOKEN_TYPE = 'wx_card';
33 |
34 | class Card {
35 | /**
36 | * Wechat Card/Coupons class
37 | * @constructor
38 | * @param options
39 | * @return {Card} Card instance
40 | */
41 | constructor(options) {
42 | config.checkPassedConfiguration(options);
43 |
44 | this.wechatConfig = isEmpty(options)
45 | ? /* istanbul ignore next */ wxConfig
46 | : Object.assign({}, wxConfig, options);
47 |
48 | /* istanbul ignore if */
49 | if (!options.store || !(options.store instanceof Store)) {
50 | debug('[Card]Store not provided, using default FileStore...');
51 | this.store = new FileStore(options.storeOptions);
52 | } else {
53 | this.store = options.store;
54 | }
55 | }
56 |
57 | /* istanbul ignore next */
58 | static get CODE_TYPE() {
59 | return CODE_TYPE;
60 | }
61 | /* istanbul ignore next */
62 | static get CARD_TYPE() {
63 | return CARD_TYPE;
64 | }
65 |
66 | /**
67 | * Get Card api_ticket
68 | * @param {string} accessToken
69 | * @return {Promise}
70 | */
71 | getApiTicketRemotely(accessToken) {
72 | const params = {
73 | access_token: accessToken,
74 | type: TOKEN_TYPE,
75 | };
76 | return utils
77 | .sendWechatRequest(this.wechatConfig.ticketUrl, {
78 | query: params,
79 | })
80 | .then(data => {
81 | data = Object.assign({ modifyDate: new Date() }, data);
82 | data.errcode = undefined;
83 | data.errmsg = undefined;
84 | return this.store.updateCardTicket(data);
85 | })
86 | .catch(reason => {
87 | /* istanbul ignore next */
88 | debug('get card api_ticket failed!');
89 | return Promise.reject(reason);
90 | });
91 | }
92 |
93 | /**
94 | * Get global access token
95 | * @param {Boolean} force if should check for cached token
96 | * @return {Promise}
97 | */
98 | getGlobalToken(force) {
99 | const cfg = this.wechatConfig;
100 | /* istanbul ignore if */
101 | if (force) {
102 | return utils
103 | .getGlobalAccessToken(cfg.appId, cfg.appSecret, cfg.accessTokenUrl)
104 | .then(globalToken => {
105 | const info = {
106 | modifyDate: new Date(),
107 | accessToken: globalToken.access_token,
108 | };
109 | return this.store.updateGlobalToken(info);
110 | });
111 | }
112 |
113 | return this.store.getGlobalToken().then(globalToken => {
114 | if (
115 | !globalToken ||
116 | !globalToken.accessToken ||
117 | utils.isExpired(globalToken.modifyDate)
118 | ) {
119 | debug(
120 | 'global access token was expired, getting new global access token now...'
121 | );
122 | return utils
123 | .getGlobalAccessToken(cfg.appId, cfg.appSecret, cfg.accessTokenUrl)
124 | .then(globalToken => {
125 | const info = {
126 | modifyDate: new Date(),
127 | accessToken: globalToken.access_token,
128 | };
129 | return this.store.updateGlobalToken(info);
130 | });
131 | // .then(info => Promise.reject(info));
132 | }
133 | return Promise.resolve(globalToken);
134 | });
135 | }
136 |
137 | /**
138 | * Get card api_ticket
139 | * @return {Promise}
140 | */
141 | getApiTicket() {
142 | return this.store
143 | .getCardTicket()
144 | .then(ticketInfo => {
145 | if (
146 | ticketInfo &&
147 | ticketInfo.ticket &&
148 | !utils.isExpired(ticketInfo.modifyDate)
149 | ) {
150 | return Promise.resolve(ticketInfo);
151 | }
152 | return this.getGlobalToken().then(info => Promise.reject(info));
153 | })
154 | .catch(globalToken => {
155 | // console.log(globalToken);
156 | return this.getApiTicketRemotely(globalToken.accessToken);
157 | });
158 | }
159 |
160 | /**
161 | * Generate card signature info for chooseCard function
162 | * @param {string=} shopId, aka: location_id
163 | * @param {string=} cardType
164 | * @param {string=} cardId
165 | * @return {Promise}
166 | */
167 | getCardSignature(shopId, cardType, cardId) {
168 | const infoForCardSign = {
169 | shopId: shopId || /* istanbul ignore next */ '', //location_id
170 | cardType: cardType || /* istanbul ignore next */ '',
171 | cardId: cardId || /* istanbul ignore next */ '',
172 | timestamp: utils.timestamp(),
173 | nonceStr: utils.nonceStr(),
174 | appid: this.wechatConfig.appId,
175 | api_ticket: '',
176 | };
177 | return this.getApiTicket()
178 | .then(ticketInfo => {
179 | infoForCardSign.api_ticket = ticketInfo.ticket;
180 | const keys = Object.keys(infoForCardSign);
181 | const values = keys.map(key => infoForCardSign[key]);
182 | values.sort();
183 | infoForCardSign.cardSign = utils.genSHA1(values.join(''));
184 | infoForCardSign.appid = undefined;
185 | infoForCardSign.api_ticket = undefined;
186 | infoForCardSign.signType = 'SHA1';
187 | return Promise.resolve(infoForCardSign);
188 | })
189 | .catch(reason => {
190 | /* istanbul ignore next */
191 | return Promise.reject(reason);
192 | });
193 | }
194 |
195 | /**
196 | * Generate cardExt
197 | * @param {string} cardId
198 | * @param {string=} code
199 | * @param {string=} openid
200 | * @param {string=} fixed_begintimestamp
201 | * @param {string=} outer_str
202 | * @return {Promise}
203 | */
204 | getCardExt(cardId, code, openid, fixed_begintimestamp, outer_str) {
205 | const infoForCardExt = {
206 | // card_id: cardId || '',
207 | // code: code || '',
208 | // openid: openid || '',
209 | timestamp: utils.timestamp(),
210 | nonce_str: utils.nonceStr(),
211 | // fixed_begintimestamp: fixed_begintimestamp || '',
212 | // outer_str: outer_str || '',
213 | // signature: '',
214 | };
215 | /* istanbul ignore else */
216 | if (cardId) {
217 | infoForCardExt.card_id = cardId;
218 | }
219 | /* istanbul ignore else */
220 | if (code) {
221 | infoForCardExt.code = code;
222 | }
223 | /* istanbul ignore else */
224 | if (openid) {
225 | infoForCardExt.openid = openid;
226 | }
227 | return this.getApiTicket()
228 | .then(ticketInfo => {
229 | infoForCardExt.api_ticket = ticketInfo.ticket;
230 | const keys = Object.keys(infoForCardExt);
231 | const values = keys.map(key => infoForCardExt[key]);
232 | infoForCardExt.signature = utils.genSHA1(values.sort().join(''));
233 | fixed_begintimestamp &&
234 | (infoForCardExt.fixed_begintimestamp = fixed_begintimestamp);
235 | outer_str && (infoForCardExt.outer_str = outer_str);
236 | infoForCardExt.api_ticket = undefined;
237 | return Promise.resolve(JSON.stringify(infoForCardExt));
238 | })
239 | .catch(reason => {
240 | /* istanbul ignore next */
241 | return Promise.reject(reason);
242 | });
243 | }
244 |
245 | /**
246 | * Simply send decode card encrypt_code api
247 | * @param {String} encryptCode encrypt_code of real card code
248 | * @param {object} qs querystring object to send with the request
249 | * @return {Promise}
250 | */
251 | sendDecodeRequest(encryptCode, qs) {
252 | return utils.sendWechatRequest(this.wechatConfig.decodeCardCodeUrl, {
253 | query: qs,
254 | method: 'POST',
255 | json: true,
256 | body: {
257 | encrypt_code: encryptCode,
258 | },
259 | });
260 | }
261 |
262 | /**
263 | * Decode/Decrypt card encrypt_code to get real card code
264 | * @param {string} encryptCode
265 | * @return {Promise}
266 | */
267 | decryptCardCode(encryptCode) {
268 | return this.getGlobalToken().then(info => {
269 | const accessToken = info.accessToken;
270 | const params = {
271 | access_token: accessToken,
272 | };
273 | return this.sendDecodeRequest(encryptCode, params).catch(reason => {
274 | debug('decode card encrypt_code failed!');
275 | //retry when access token error
276 | if (codeUtils.errorByAccessTokenRelated(reason.errcode)) {
277 | return this.getGlobalToken(true).then(info => {
278 | const accessToken = info.accessToken;
279 | const params = {
280 | access_token: accessToken,
281 | };
282 | return this.sendDecodeRequest(encryptCode, params).catch(reason => {
283 | debug(
284 | 'decode card encrypt_code failed again, tray again later!!!'
285 | );
286 | return Promise.reject(reason);
287 | });
288 | });
289 | }
290 | return Promise.reject(reason);
291 | });
292 | });
293 | }
294 | }
295 |
296 | module.exports = Card;
297 |
--------------------------------------------------------------------------------
/lib/JSSDK.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('wechat-JSSDK');
4 |
5 | const isEmpty = require('lodash.isempty');
6 | const urlParser = require('url');
7 |
8 | const utils = require('./utils');
9 | const config = require('./config');
10 |
11 | const Store = require('./store/Store');
12 | const FileStore = require('./store/FileStore');
13 |
14 | const wxConfig = config.getDefaultConfiguration();
15 |
16 | class JSSDK {
17 | /**
18 | * Pass custom wechat config for the instance
19 | * @constructor
20 | * @param {object=} options
21 | * @see ./config.js
22 | * @return {JSSDK} JSSDK instance
23 | */
24 | constructor(options) {
25 | config.checkPassedConfiguration(options);
26 | this.refreshedTimes = 0;
27 | this.wechatConfig = isEmpty(options)
28 | ? /* istanbul ignore next */ wxConfig
29 | : Object.assign({}, wxConfig, options);
30 |
31 | //no custom store provided, using default FileStore
32 | /* istanbul ignore if */
33 | if (!options.store || !(options.store instanceof Store)) {
34 | debug('[JSSDK]Store not provided, using default FileStore...');
35 | this.store = new FileStore(options.storeOptions);
36 | } else {
37 | this.store = options.store;
38 | }
39 |
40 | //clear the counter every 2 hour
41 | setInterval(
42 | () => (this.refreshedTimes = 0),
43 | options.clearCountInterval || 1000 * 7200
44 | );
45 | }
46 |
47 | /**
48 | * Check if token is expired starting from the modify date
49 | * @param modifyDate
50 | * @static
51 | * @return {boolean}
52 | */
53 | static isTokenExpired(modifyDate) {
54 | return utils.isExpired(modifyDate);
55 | }
56 |
57 | /**
58 | * Create NonceStr before generating the signature
59 | * @static
60 | * @return {string}
61 | */
62 | static createNonceStr() {
63 | return utils.nonceStr();
64 | }
65 |
66 | /**
67 | * Filter the signature for the client
68 | * @param {object} originalSignatureObj original signature information
69 | * @return {object} filtered signature object
70 | */
71 | filterSignature(originalSignatureObj) {
72 | if (!originalSignatureObj) {
73 | return {};
74 | }
75 | return {
76 | appId: this.wechatConfig.appId,
77 | timestamp: originalSignatureObj.timestamp,
78 | nonceStr: originalSignatureObj.nonceStr,
79 | signature: originalSignatureObj.signature,
80 | url: originalSignatureObj.url,
81 | };
82 | }
83 |
84 | /**
85 | * Remove hash from the url, wechat signature does not need it
86 | * @param {string} url original url
87 | * @static
88 | * @return {string}
89 | */
90 | static normalizeUrl(url) {
91 | const temp = urlParser.parse(url);
92 | const hashIndex = url.indexOf(temp.hash);
93 | //remove hash from url
94 | return hashIndex > 0 ? url.substring(0, hashIndex) : url;
95 | }
96 |
97 | /**
98 | * Generate the url signature with the provided info
99 | * @param {string} url current url
100 | * @param {string} accessToken
101 | * @param {string} ticket js ticket
102 | * @static
103 | * @returns {object} generated wechat signature info
104 | */
105 | static generateSignature(url, accessToken, ticket) {
106 | const ret = {
107 | jsapi_ticket: ticket,
108 | nonceStr: JSSDK.createNonceStr(),
109 | timestamp: utils.timestamp(),
110 | url: JSSDK.normalizeUrl(url),
111 | };
112 | const originalStr = utils.paramsToString(ret);
113 | ret.signature = utils.genSHA1(originalStr);
114 | ret.accessToken = accessToken;
115 | return ret;
116 | }
117 |
118 | /**
119 | * Need to verify before you are a wechat developer
120 | * @param {object} query url query sent by the wechat server to do the validation
121 | * @return {boolean}
122 | */
123 | verifySignature(query) {
124 | const keys = [
125 | this.wechatConfig.wechatToken,
126 | query['timestamp'],
127 | query['nonce'],
128 | ];
129 | let str = keys.sort().join('');
130 | str = utils.genSHA1(str);
131 | return str === query.signature;
132 | }
133 |
134 | /**
135 | * Send request to get wechat access token
136 | * @return {Promise}
137 | */
138 | getAccessToken() {
139 | const cfg = this.wechatConfig;
140 | return utils.getGlobalAccessToken(
141 | cfg.appId,
142 | cfg.appSecret,
143 | cfg.accessTokenUrl
144 | );
145 | }
146 |
147 | /**
148 | * Get wechat ticket with the accessToken
149 | * @param {string} accessToken token received from the @see getAccessToken above
150 | * @return {Promise}
151 | */
152 | getJsApiTicket(accessToken) {
153 | const params = {
154 | access_token: accessToken,
155 | type: 'jsapi',
156 | };
157 | return utils
158 | .sendWechatRequest(this.wechatConfig.ticketUrl, {
159 | query: params,
160 | })
161 | .then(data => data)
162 | .catch(reason => {
163 | debug('get ticket failed!');
164 | return Promise.reject(reason);
165 | });
166 | }
167 |
168 | /**
169 | * Update the global token or js_ticket, we should cache this to prevent requesting too often
170 | * @param {string} token
171 | * @param {string} ticket
172 | * @return {Promise} resolved with the updated globalToken object
173 | */
174 | updateAccessTokenOrTicketGlobally(token, ticket) {
175 | const info = { modifyDate: new Date() };
176 | token && (info.accessToken = token);
177 | ticket && (info.jsapi_ticket = ticket);
178 | return this.store.updateGlobalToken(info);
179 | }
180 |
181 | /**
182 | * Get new access token and ticket from wechat server, and update that to cache
183 | * @param {boolean=} force force update, by default it will only get at most 5 times within 2 hours,
184 | * cause the wechat server limits the access_token requests number
185 | * @return {Promise}
186 | */
187 | getGlobalTokenAndTicket(force) {
188 | force || this.refreshedTimes++;
189 | /* istanbul ignore if */
190 | if (!force && this.refreshedTimes > 5) {
191 | return Promise.reject(
192 | new Error('maximum manual refresh threshold reached!')
193 | );
194 | }
195 | let accessToken = '';
196 | return this.getAccessToken()
197 | .then(result => {
198 | accessToken = result.access_token;
199 | return accessToken;
200 | })
201 | .catch(reason => {
202 | debug('get new global token failed!');
203 | return Promise.reject(reason);
204 | })
205 | .then(receivedAccessToken => {
206 | return this.getJsApiTicket(receivedAccessToken);
207 | })
208 | .then(ticketResult => {
209 | return this.updateAccessTokenOrTicketGlobally(
210 | accessToken,
211 | ticketResult.ticket
212 | );
213 | })
214 | .catch(ticketReason => {
215 | debug('get new global ticket failed!');
216 | debug(ticketReason);
217 | return Promise.reject(ticketReason);
218 | });
219 | }
220 |
221 | /**
222 | * Get or generate global token info for signature generating process
223 | * @return {Promise}
224 | */
225 | prepareGlobalToken() {
226 | return this.store.getGlobalToken().then(globalToken => {
227 | if (
228 | !globalToken ||
229 | !globalToken.accessToken ||
230 | JSSDK.isTokenExpired(globalToken.modifyDate)
231 | ) {
232 | debug(
233 | 'global access token was expired, getting new global access token and ticket now...'
234 | );
235 | return this.getGlobalTokenAndTicket(true);
236 | }
237 | debug('global ticket exists, use cached access token');
238 | return Promise.resolve(globalToken);
239 | });
240 | }
241 |
242 | /**
243 | * Save or update the signature
244 | * @param {object} info signature information to save
245 | * @return {Promise}
246 | */
247 | saveSignature(info) {
248 | const signature = Object.assign({}, info);
249 | signature.createDate = new Date();
250 | signature.modifyDate = signature.createDate;
251 | return this.store
252 | .isSignatureExisting(signature.url)
253 | .then(existing => {
254 | if (existing) {
255 | debug('wechat url signature existed, try updating the signature...');
256 | return this.updateSignature(signature.url, signature);
257 | }
258 | return this.store.saveSignature(signature.signatureName, signature);
259 | })
260 | .then(sig => {
261 | debug('create/update wechat signature finished');
262 | return Promise.resolve(sig);
263 | });
264 | }
265 |
266 | /**
267 | * Update the signature for existing url
268 | * @param {string} url signature of url need to update
269 | * @param {object} info update info need to be updated to the existing url signature info
270 | * @return {Promise}
271 | */
272 | updateSignature(url, info) {
273 | url = JSSDK.normalizeUrl(url);
274 | info.modifyDate = new Date();
275 | delete info.createDate;
276 | delete info.url; //prevent changing the original url
277 | delete info.signatureName; //prevent changing the original name
278 | return this.store.updateSignature(url, info).then(sig => {
279 | debug('update wechat signature finished');
280 | return Promise.resolve(sig);
281 | });
282 | }
283 |
284 | /**
285 | * Get the signature from cache or create a new one
286 | * @param {string} url
287 | * @param {boolean=} forceNewSignature if true, generate a new signature rather than getting from cache
288 | * @return {Promise}
289 | */
290 | getSignature(url, forceNewSignature) {
291 | url = JSSDK.normalizeUrl(url);
292 | return this.store.getSignature(url).then(signature => {
293 | if (
294 | !forceNewSignature &&
295 | signature &&
296 | !JSSDK.isTokenExpired(signature.modifyDate)
297 | ) {
298 | signature = this.filterSignature(signature);
299 | return Promise.resolve(signature);
300 | }
301 | return this.createSignature(url);
302 | });
303 | }
304 |
305 | /**
306 | * Create a new signature now, and save to store
307 | * @param {string} url signature will be created for the url
308 | * @return {Promise} resolved with filtered signature results
309 | */
310 | createSignature(url) {
311 | return this.prepareGlobalToken()
312 | .then(data => {
313 | const ret = JSSDK.generateSignature(
314 | url,
315 | data.accessToken,
316 | data.jsapi_ticket
317 | );
318 | ret.signatureName = ret.url;
319 | return this.saveSignature(ret);
320 | })
321 | .then(sig => this.filterSignature(sig));
322 | }
323 |
324 | /**
325 | * Just get url signature from cache
326 | * @param {string} url
327 | * @return {Promise} filtered signature info
328 | */
329 | getCachedSignature(url) {
330 | url = JSSDK.normalizeUrl(url);
331 | return this.store.getSignature(url).then(signature => {
332 | return Promise.resolve(this.filterSignature(signature));
333 | });
334 | }
335 | }
336 |
337 | module.exports = JSSDK;
338 |
--------------------------------------------------------------------------------
/lib/MiniProgram.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('wechat-MiniProgram');
4 | const crypto = require('crypto');
5 | const isEmpty = require('lodash.isempty');
6 |
7 | const utils = require('./utils');
8 | const config = require('./config');
9 |
10 | const Store = require('./store/Store');
11 | const FileStore = require('./store/FileStore');
12 |
13 | const wxConfig = config.getDefaultConfiguration();
14 |
15 | class MiniProgram {
16 | /**
17 | * Wechat mini program class, must have "options.miniProgram" option
18 | * @constructor
19 | * @param options
20 | * @return {MiniProgram} MiniProgram instance
21 | */
22 | constructor(options = {}) {
23 | // config.checkPassedConfiguration(options);
24 |
25 | let miniOptions = options.miniProgram || /* istanbul ignore next */ {};
26 |
27 | /* istanbul ignore if */
28 | if (!miniOptions.appId) {
29 | throw new Error('wechat mini program appId not found');
30 | }
31 |
32 | /* istanbul ignore if */
33 | if (!miniOptions.appSecret) {
34 | throw new Error('wechat mini program appSecret not found');
35 | }
36 |
37 | this.miniProgramOptions = miniOptions = Object.assign(
38 | {},
39 | wxConfig.miniProgram,
40 | miniOptions
41 | );
42 | options.miniProgram = miniOptions;
43 |
44 | this.wechatConfig = isEmpty(options)
45 | ? /* istanbul ignore next */ wxConfig
46 | : Object.assign({}, wxConfig, options);
47 | //alias
48 | this.appId = miniOptions.appId;
49 | this.appSecret = miniOptions.appSecret;
50 |
51 | /* istanbul ignore else */
52 | if (
53 | !options.store ||
54 | /* istanbul ignore next */ !(options.store instanceof Store)
55 | ) {
56 | debug('[MiniProgram]Store not provided, using default FileStore...');
57 | this.store = new FileStore(options.storeOptions);
58 | } else {
59 | this.store = options.store;
60 | }
61 | }
62 |
63 | /**
64 | * Get the new session from wechat
65 | * @param code - code from wx.login()
66 | * @param key - key used to store the session data, default will use the openid
67 | * @return {Promise