@jayfong/x-server 1.22.0 → 1.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [1.24.1](https://github.com/jfWorks/x-server/compare/v1.24.0...v1.24.1) (2022-05-02)
6
+
7
+ ## [1.24.0](https://github.com/jfWorks/x-server/compare/v1.23.0...v1.24.0) (2022-05-02)
8
+
9
+
10
+ ### Features
11
+
12
+ * add PayService ([c8d5ecc](https://github.com/jfWorks/x-server/commit/c8d5eccf2393c1a1bf98415564032b09ffb10497))
13
+
14
+ ## [1.23.0](https://github.com/jfWorks/x-server/compare/v1.22.0...v1.23.0) (2022-05-01)
15
+
16
+
17
+ ### Features
18
+
19
+ * **captcha:** add generateDataUrl ([54205df](https://github.com/jfWorks/x-server/commit/54205df24d0cc397029d8c66ea17020d7cb5c5fd))
20
+
5
21
  ## [1.22.0](https://github.com/jfWorks/x-server/compare/v1.21.0...v1.22.0) (2022-05-01)
6
22
 
7
23
 
package/lib/_cjs/index.js CHANGED
@@ -178,6 +178,14 @@ Object.keys(_mail).forEach(function (key) {
178
178
  exports[key] = _mail[key];
179
179
  });
180
180
 
181
+ var _pay = require("./services/pay");
182
+
183
+ Object.keys(_pay).forEach(function (key) {
184
+ if (key === "default" || key === "__esModule") return;
185
+ if (key in exports && exports[key] === _pay[key]) return;
186
+ exports[key] = _pay[key];
187
+ });
188
+
181
189
  var _rate_limit = require("./services/rate_limit");
182
190
 
183
191
  Object.keys(_rate_limit).forEach(function (key) {
@@ -7,6 +7,8 @@ exports.CaptchaService = void 0;
7
7
 
8
8
  var _svgCaptcha = _interopRequireDefault(require("svg-captcha"));
9
9
 
10
+ var _miniSvgDataUri = _interopRequireDefault(require("mini-svg-data-uri"));
11
+
10
12
  var _x = require("../x");
11
13
 
12
14
  class CaptchaService {
@@ -40,6 +42,20 @@ class CaptchaService {
40
42
  };
41
43
  }
42
44
 
45
+ async generateDataUrl() {
46
+ const {
47
+ token,
48
+ code,
49
+ svg
50
+ } = await this.generateImage();
51
+ const dataUrl = (0, _miniSvgDataUri.default)(svg);
52
+ return {
53
+ token: token,
54
+ code: code,
55
+ dataUrl: dataUrl
56
+ };
57
+ }
58
+
43
59
  async verify(options) {
44
60
  if (options.code.length !== this.options.size) {
45
61
  return false;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+
5
+ exports.__esModule = true;
6
+ exports.PayService = void 0;
7
+
8
+ var _form = _interopRequireDefault(require("alipay-sdk/lib/form"));
9
+
10
+ var _alipaySdk = _interopRequireDefault(require("alipay-sdk"));
11
+
12
+ var _crypto = _interopRequireDefault(require("crypto"));
13
+
14
+ var _got = _interopRequireDefault(require("got"));
15
+
16
+ var _http_error = require("../core/http_error");
17
+
18
+ var _x = require("../x");
19
+
20
+ class PayService {
21
+ constructor(options) {
22
+ this.options = options;
23
+ this.serviceName = 'pay';
24
+ this.alipaySdk = void 0;
25
+ }
26
+
27
+ async prepareAlipay(options) {
28
+ if (!this.alipaySdk) {
29
+ this.alipaySdk = new _alipaySdk.default({
30
+ appId: this.options.alipay.appId,
31
+ privateKey: this.options.alipay.privateKey,
32
+ alipayPublicKey: this.options.alipay.publicKey
33
+ });
34
+ }
35
+
36
+ const token = await _x.x.cache.save('x', '1h');
37
+ const formData = new _form.default();
38
+ formData.setMethod('get');
39
+ formData.addField('notifyUrl', options.notifyUrl);
40
+ formData.addField('returnUrl', options.returnUrl);
41
+ formData.addField('bizContent', {
42
+ outTradeNo: options.tradeNumber,
43
+ productCode: 'FAST_INSTANT_TRADE_PAY',
44
+ totalAmount: options.totalMoney,
45
+ subject: options.goodsName,
46
+ goodsType: '0',
47
+ passbackParams: token
48
+ });
49
+ const payUrl = await this.alipaySdk.exec('alipay.trade.page.pay', {}, {
50
+ formData: formData
51
+ });
52
+ return {
53
+ payUrl: payUrl
54
+ };
55
+ }
56
+
57
+ async prepareWepay(options) {
58
+ const token = await _x.x.cache.save('x', '1h');
59
+ const res = await this.wepayRequest('https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', {
60
+ appid: this.options.wepay.appId,
61
+ mchid: this.options.wepay.merchantId,
62
+ description: options.goodsName,
63
+ out_trade_no: options.tradeNumber,
64
+ attach: token,
65
+ notify_url: options.notifyUrl,
66
+ amount: {
67
+ total: options.totalMoney * 100,
68
+ currency: 'CNY'
69
+ },
70
+ payer: {
71
+ openid: options.openid
72
+ }
73
+ });
74
+
75
+ if (!res || !res.prepay_id) {
76
+ const error = res && res.message || '支付错误';
77
+ throw new Error(error);
78
+ }
79
+
80
+ const params = {
81
+ appId: this.options.wepay.appId,
82
+ timestamp: String(Math.round(Date.now() / 1000)),
83
+ nonceStr: Math.random().toString().substr(2, 12),
84
+ package: `prepay_id=${res.prepay_id}`,
85
+ signType: 'RSA',
86
+ paySign: ''
87
+ };
88
+ params.paySign = this.wepaySign([params.appId, params.timestamp, params.nonceStr, params.package]);
89
+ return {
90
+ payParams: params
91
+ };
92
+ }
93
+
94
+ async verifyNotifyData(data) {
95
+ if (!data || typeof data !== 'object') return false;
96
+ const token = // 微信支付
97
+ data.resource ? JSON.parse(this.wepayDecrypt(data.resource)).attach : // 支付宝
98
+ data.passback_params;
99
+ if (!token) return false;
100
+ if ((await _x.x.cache.get(token)) == null) return false;
101
+ await _x.x.cache.remove(token);
102
+ return true;
103
+ }
104
+
105
+ async verifyNotifyDataOrFail(data, message) {
106
+ if (!(await this.verifyNotifyData(data))) {
107
+ throw new _http_error.HttpError.BadRequest(message);
108
+ }
109
+ }
110
+
111
+ wepaySign(data) {
112
+ return _crypto.default.createSign('RSA-SHA256').update(data.map(v => `${v}\n`).join('')).sign(this.options.wepay.privateKey, 'base64');
113
+ }
114
+
115
+ wepayDecrypt(payload) {
116
+ const ciphertextBuffer = Buffer.from(payload.ciphertext, 'base64');
117
+ const authTag = ciphertextBuffer.slice(ciphertextBuffer.length - 16);
118
+ const data = ciphertextBuffer.slice(0, ciphertextBuffer.length - 16);
119
+
120
+ const decipherIv = _crypto.default.createDecipheriv('aes-256-gcm', this.options.wepay.secretKey, payload.nonce);
121
+
122
+ decipherIv.setAuthTag(Buffer.from(authTag));
123
+ decipherIv.setAAD(Buffer.from(payload.associated_data));
124
+ const decryptStr = decipherIv.update(data, undefined, 'utf8');
125
+ decipherIv.final();
126
+ return decryptStr;
127
+ }
128
+
129
+ async wepayRequest(url, data) {
130
+ const path = url.replace(/^https?:\/\/[^/]+/, '');
131
+ const body = JSON.stringify(data);
132
+ const params = {
133
+ method: 'POST',
134
+ url: path,
135
+ time: String(Math.round(Date.now() / 1000)),
136
+ rand: Math.random().toString().substr(2, 12),
137
+ body: body,
138
+ signature: ''
139
+ };
140
+ params.signature = this.wepaySign([params.method, params.url, params.time, params.rand, params.body]);
141
+ const Authorization = [`WECHATPAY2-SHA256-RSA2048 `, `mchid="${this.options.wepay.merchantId}",`, `nonce_str="${params.rand}",`, `signature="${params.signature}",`, `timestamp="${params.time}",`, `serial_no="${this.options.wepay.certificateSerialNumber}"`].join('');
142
+ const res = await _got.default.post(url, {
143
+ json: data,
144
+ headers: {
145
+ Authorization
146
+ },
147
+ responseType: 'json',
148
+ resolveBodyOnly: true
149
+ });
150
+ return res;
151
+ }
152
+
153
+ }
154
+
155
+ exports.PayService = PayService;
package/lib/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export * from './services/captcha';
20
20
  export * from './services/dispose';
21
21
  export * from './services/jwt';
22
22
  export * from './services/mail';
23
+ export * from './services/pay';
23
24
  export * from './services/rate_limit';
24
25
  export * from './services/redis';
25
26
  export * from './x';
package/lib/index.js CHANGED
@@ -21,6 +21,7 @@ export * from "./services/captcha";
21
21
  export * from "./services/dispose";
22
22
  export * from "./services/jwt";
23
23
  export * from "./services/mail";
24
+ export * from "./services/pay";
24
25
  export * from "./services/rate_limit";
25
26
  export * from "./services/redis";
26
27
  export * from "./x"; // @endindex
@@ -19,6 +19,11 @@ export interface CaptchaGenerateImageResult {
19
19
  code: string;
20
20
  svg: string;
21
21
  }
22
+ export interface CaptchaGenerateDataUrlResult {
23
+ token: string;
24
+ code: string;
25
+ dataUrl: string;
26
+ }
22
27
  export interface CaptchaVerifyOptions {
23
28
  token: string;
24
29
  code: string;
@@ -29,6 +34,7 @@ export declare class CaptchaService implements BaseService {
29
34
  constructor(options: CaptchaOptions);
30
35
  generate(): Promise<CaptchaGenerateResult>;
31
36
  generateImage(): Promise<CaptchaGenerateImageResult>;
37
+ generateDataUrl(): Promise<CaptchaGenerateDataUrlResult>;
32
38
  verify(options: CaptchaVerifyOptions): Promise<boolean>;
33
39
  }
34
40
  declare module '../x' {
@@ -1,4 +1,5 @@
1
1
  import svgCaptcha from 'svg-captcha';
2
+ import svgToDataUrl from 'mini-svg-data-uri';
2
3
  import { x } from "../x";
3
4
  export class CaptchaService {
4
5
  constructor(options) {
@@ -30,6 +31,20 @@ export class CaptchaService {
30
31
  };
31
32
  }
32
33
 
34
+ async generateDataUrl() {
35
+ const {
36
+ token,
37
+ code,
38
+ svg
39
+ } = await this.generateImage();
40
+ const dataUrl = svgToDataUrl(svg);
41
+ return {
42
+ token: token,
43
+ code: code,
44
+ dataUrl: dataUrl
45
+ };
46
+ }
47
+
33
48
  async verify(options) {
34
49
  if (options.code.length !== this.options.size) {
35
50
  return false;
@@ -0,0 +1,82 @@
1
+ import { BaseService } from './base';
2
+ export interface PayOptions {
3
+ /** 支付宝 */
4
+ alipay?: {
5
+ /** 应用 ID */
6
+ appId: string;
7
+ /** 商户应用私钥 */
8
+ privateKey: string;
9
+ /** 支付宝公钥,用于对返回结果验签 */
10
+ publicKey: string;
11
+ };
12
+ /** 微信支付 */
13
+ wepay?: {
14
+ /** 公众号 APPID */
15
+ appId: string;
16
+ /** 商户 ID */
17
+ merchantId: string;
18
+ /** 商户应用私钥 */
19
+ privateKey: string;
20
+ /** 证书序列号 */
21
+ certificateSerialNumber: string;
22
+ /** 秘钥,用于解密请求平台证书、回调数据 */
23
+ secretKey: string;
24
+ };
25
+ }
26
+ export interface PayPrepareAlipayOptions {
27
+ /** 商品名称 */
28
+ goodsName: string;
29
+ /** 交易单号 */
30
+ tradeNumber: string;
31
+ /** 总金额 */
32
+ totalMoney: number;
33
+ /** 通知地址(POST) */
34
+ notifyUrl: string;
35
+ /** 返回地址 */
36
+ returnUrl: string;
37
+ }
38
+ export interface PayPrepareAlipayResult {
39
+ /** 支付地址,需跳转过去 */
40
+ payUrl: string;
41
+ }
42
+ export interface PayPrepareWepayOptions {
43
+ /** 商品名称 */
44
+ goodsName: string;
45
+ /** 交易单号 */
46
+ tradeNumber: string;
47
+ /** 总金额 */
48
+ totalMoney: number;
49
+ /** 通知地址(POST) */
50
+ notifyUrl: string;
51
+ /** 对应公众号用户的 openid */
52
+ openid: string;
53
+ }
54
+ export interface PayPrepareWepayResult {
55
+ /** 支付参数,可直接传给 JSSDK 调用 */
56
+ payParams: {
57
+ appId: string;
58
+ timestamp: string;
59
+ nonceStr: string;
60
+ package: string;
61
+ signType: string;
62
+ paySign: string;
63
+ };
64
+ }
65
+ export declare class PayService implements BaseService {
66
+ private options;
67
+ serviceName: string;
68
+ private alipaySdk;
69
+ constructor(options: PayOptions);
70
+ prepareAlipay(options: PayPrepareAlipayOptions): Promise<PayPrepareAlipayResult>;
71
+ prepareWepay(options: PayPrepareWepayOptions): Promise<PayPrepareWepayResult>;
72
+ verifyNotifyData(data: any): Promise<boolean>;
73
+ verifyNotifyDataOrFail(data: any, message?: string): Promise<void>;
74
+ private wepaySign;
75
+ private wepayDecrypt;
76
+ private wepayRequest;
77
+ }
78
+ declare module '../x' {
79
+ interface X {
80
+ pay: PayService;
81
+ }
82
+ }
@@ -0,0 +1,138 @@
1
+ import AlipayFormData from 'alipay-sdk/lib/form';
2
+ import AlipaySdk from 'alipay-sdk';
3
+ import crypto from 'crypto';
4
+ import got from 'got';
5
+ import { HttpError } from "../core/http_error";
6
+ import { x } from "../x";
7
+ export class PayService {
8
+ constructor(options) {
9
+ this.options = options;
10
+ this.serviceName = 'pay';
11
+ this.alipaySdk = void 0;
12
+ }
13
+
14
+ async prepareAlipay(options) {
15
+ if (!this.alipaySdk) {
16
+ this.alipaySdk = new AlipaySdk({
17
+ appId: this.options.alipay.appId,
18
+ privateKey: this.options.alipay.privateKey,
19
+ alipayPublicKey: this.options.alipay.publicKey
20
+ });
21
+ }
22
+
23
+ const token = await x.cache.save('x', '1h');
24
+ const formData = new AlipayFormData();
25
+ formData.setMethod('get');
26
+ formData.addField('notifyUrl', options.notifyUrl);
27
+ formData.addField('returnUrl', options.returnUrl);
28
+ formData.addField('bizContent', {
29
+ outTradeNo: options.tradeNumber,
30
+ productCode: 'FAST_INSTANT_TRADE_PAY',
31
+ totalAmount: options.totalMoney,
32
+ subject: options.goodsName,
33
+ goodsType: '0',
34
+ passbackParams: token
35
+ });
36
+ const payUrl = await this.alipaySdk.exec('alipay.trade.page.pay', {}, {
37
+ formData: formData
38
+ });
39
+ return {
40
+ payUrl: payUrl
41
+ };
42
+ }
43
+
44
+ async prepareWepay(options) {
45
+ const token = await x.cache.save('x', '1h');
46
+ const res = await this.wepayRequest('https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', {
47
+ appid: this.options.wepay.appId,
48
+ mchid: this.options.wepay.merchantId,
49
+ description: options.goodsName,
50
+ out_trade_no: options.tradeNumber,
51
+ attach: token,
52
+ notify_url: options.notifyUrl,
53
+ amount: {
54
+ total: options.totalMoney * 100,
55
+ currency: 'CNY'
56
+ },
57
+ payer: {
58
+ openid: options.openid
59
+ }
60
+ });
61
+
62
+ if (!res || !res.prepay_id) {
63
+ const error = res && res.message || '支付错误';
64
+ throw new Error(error);
65
+ }
66
+
67
+ const params = {
68
+ appId: this.options.wepay.appId,
69
+ timestamp: String(Math.round(Date.now() / 1000)),
70
+ nonceStr: Math.random().toString().substr(2, 12),
71
+ package: `prepay_id=${res.prepay_id}`,
72
+ signType: 'RSA',
73
+ paySign: ''
74
+ };
75
+ params.paySign = this.wepaySign([params.appId, params.timestamp, params.nonceStr, params.package]);
76
+ return {
77
+ payParams: params
78
+ };
79
+ }
80
+
81
+ async verifyNotifyData(data) {
82
+ if (!data || typeof data !== 'object') return false;
83
+ const token = // 微信支付
84
+ data.resource ? JSON.parse(this.wepayDecrypt(data.resource)).attach : // 支付宝
85
+ data.passback_params;
86
+ if (!token) return false;
87
+ if ((await x.cache.get(token)) == null) return false;
88
+ await x.cache.remove(token);
89
+ return true;
90
+ }
91
+
92
+ async verifyNotifyDataOrFail(data, message) {
93
+ if (!(await this.verifyNotifyData(data))) {
94
+ throw new HttpError.BadRequest(message);
95
+ }
96
+ }
97
+
98
+ wepaySign(data) {
99
+ return crypto.createSign('RSA-SHA256').update(data.map(v => `${v}\n`).join('')).sign(this.options.wepay.privateKey, 'base64');
100
+ }
101
+
102
+ wepayDecrypt(payload) {
103
+ const ciphertextBuffer = Buffer.from(payload.ciphertext, 'base64');
104
+ const authTag = ciphertextBuffer.slice(ciphertextBuffer.length - 16);
105
+ const data = ciphertextBuffer.slice(0, ciphertextBuffer.length - 16);
106
+ const decipherIv = crypto.createDecipheriv('aes-256-gcm', this.options.wepay.secretKey, payload.nonce);
107
+ decipherIv.setAuthTag(Buffer.from(authTag));
108
+ decipherIv.setAAD(Buffer.from(payload.associated_data));
109
+ const decryptStr = decipherIv.update(data, undefined, 'utf8');
110
+ decipherIv.final();
111
+ return decryptStr;
112
+ }
113
+
114
+ async wepayRequest(url, data) {
115
+ const path = url.replace(/^https?:\/\/[^/]+/, '');
116
+ const body = JSON.stringify(data);
117
+ const params = {
118
+ method: 'POST',
119
+ url: path,
120
+ time: String(Math.round(Date.now() / 1000)),
121
+ rand: Math.random().toString().substr(2, 12),
122
+ body: body,
123
+ signature: ''
124
+ };
125
+ params.signature = this.wepaySign([params.method, params.url, params.time, params.rand, params.body]);
126
+ const Authorization = [`WECHATPAY2-SHA256-RSA2048 `, `mchid="${this.options.wepay.merchantId}",`, `nonce_str="${params.rand}",`, `signature="${params.signature}",`, `timestamp="${params.time}",`, `serial_no="${this.options.wepay.certificateSerialNumber}"`].join('');
127
+ const res = await got.post(url, {
128
+ json: data,
129
+ headers: {
130
+ Authorization
131
+ },
132
+ responseType: 'json',
133
+ resolveBodyOnly: true
134
+ });
135
+ return res;
136
+ }
137
+
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayfong/x-server",
3
- "version": "1.22.0",
3
+ "version": "1.24.1",
4
4
  "license": "ISC",
5
5
  "sideEffects": false,
6
6
  "main": "lib/_cjs/index.js",
@@ -33,6 +33,7 @@
33
33
  "@types/jsonwebtoken": "^8.5.8",
34
34
  "@types/nodemailer": "^6.4.4",
35
35
  "@types/ws": "^8.5.3",
36
+ "alipay-sdk": "^3.2.0",
36
37
  "bufferutil": "^4.0.6",
37
38
  "bull": "^4.8.1",
38
39
  "chokidar": "^3.5.3",
@@ -50,9 +51,11 @@
50
51
  "fastify-multipart": "^5.3.1",
51
52
  "fastify-websocket": "^4.2.2",
52
53
  "fs-extra": "^10.0.1",
54
+ "got": "^11.8.2",
53
55
  "http-errors": "^1.8.1",
54
56
  "ioredis": "^4.28.5",
55
57
  "jsonwebtoken": "^8.5.1",
58
+ "mini-svg-data-uri": "^1.4.4",
56
59
  "node-ssh": "^12.0.4",
57
60
  "nodemailer": "^6.7.3",
58
61
  "pino-pretty": "^7.6.1",