paymongo-cli 1.4.6 → 1.4.7
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 +58 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/config.js +17 -16
- package/dist/commands/dev/logs.js +3 -3
- package/dist/commands/dev/status.js +2 -2
- package/dist/commands/dev/stop.js +3 -3
- package/dist/commands/dev.js +7 -7
- package/dist/commands/env.js +6 -6
- package/dist/commands/init.js +3 -3
- package/dist/commands/login.js +3 -3
- package/dist/commands/payments.js +9 -8
- package/dist/commands/team/index.js +7 -6
- package/dist/commands/trigger.js +18 -16
- package/dist/commands/webhooks.js +7 -6
- package/dist/index.js +9 -2
- package/dist/services/analytics/service.js +19 -18
- package/dist/services/api/client.js +15 -15
- package/dist/services/dev/process-manager.js +30 -32
- package/dist/services/dev/server.js +45 -39
- package/dist/utils/bulk.js +36 -4
- package/dist/utils/constants.js +11 -1
- package/dist/utils/errors.js +6 -0
- package/dist/utils/validator.js +10 -9
- package/dist/utils/webhook-store.js +18 -15
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import ApiClient from '../services/api/client.js';
|
|
|
6
6
|
import { BulkOperations } from '../utils/bulk.js';
|
|
7
7
|
import Spinner from '../utils/spinner.js';
|
|
8
8
|
import { validateWebhookUrl, validateEventTypes } from '../utils/validator.js';
|
|
9
|
+
import { CommandError } from '../utils/errors.js';
|
|
9
10
|
export async function exportAction(options) {
|
|
10
11
|
const spinner = new Spinner();
|
|
11
12
|
const configManager = new ConfigManager();
|
|
@@ -54,7 +55,7 @@ export async function exportAction(options) {
|
|
|
54
55
|
spinner.stop();
|
|
55
56
|
const err = error;
|
|
56
57
|
console.error(chalk.red('❌ Failed to export webhooks:'), err.message);
|
|
57
|
-
|
|
58
|
+
throw new CommandError();
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
export async function importAction(filename, options) {
|
|
@@ -153,7 +154,7 @@ export async function importAction(filename, options) {
|
|
|
153
154
|
spinner.stop();
|
|
154
155
|
const err = error;
|
|
155
156
|
console.error(chalk.red('❌ Failed to import webhooks:'), err.message);
|
|
156
|
-
|
|
157
|
+
throw new CommandError();
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
export async function createAction(options) {
|
|
@@ -281,7 +282,7 @@ export async function createAction(options) {
|
|
|
281
282
|
console.log('');
|
|
282
283
|
console.log(chalk.yellow('💡 For help, visit: https://developers.paymongo.com/docs/webhooks'));
|
|
283
284
|
}
|
|
284
|
-
|
|
285
|
+
throw new CommandError();
|
|
285
286
|
}
|
|
286
287
|
}
|
|
287
288
|
export async function listAction(options) {
|
|
@@ -374,7 +375,7 @@ export async function listAction(options) {
|
|
|
374
375
|
else {
|
|
375
376
|
console.error(chalk.red('❌ Failed to list webhooks:'), err.message);
|
|
376
377
|
}
|
|
377
|
-
|
|
378
|
+
throw new CommandError();
|
|
378
379
|
}
|
|
379
380
|
}
|
|
380
381
|
export async function deleteAction(id, options) {
|
|
@@ -426,7 +427,7 @@ export async function deleteAction(id, options) {
|
|
|
426
427
|
else {
|
|
427
428
|
console.error(chalk.red('❌ Failed to delete webhook:'), err.message);
|
|
428
429
|
}
|
|
429
|
-
|
|
430
|
+
throw new CommandError();
|
|
430
431
|
}
|
|
431
432
|
}
|
|
432
433
|
export async function showAction(id) {
|
|
@@ -479,7 +480,7 @@ export async function showAction(id) {
|
|
|
479
480
|
else {
|
|
480
481
|
console.error(chalk.red('❌ Failed to get webhook details:'), err.message);
|
|
481
482
|
}
|
|
482
|
-
|
|
483
|
+
throw new CommandError();
|
|
483
484
|
}
|
|
484
485
|
}
|
|
485
486
|
const command = new Command('webhooks')
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
|
+
import { CommandError } from './utils/errors.js';
|
|
5
6
|
const require = createRequire(import.meta.url);
|
|
6
7
|
const { version } = require('../package.json');
|
|
7
8
|
const program = new Command();
|
|
@@ -68,11 +69,17 @@ EXAMPLES
|
|
|
68
69
|
For more information, visit: https://github.com/leodyversemilla07/paymongo-cli
|
|
69
70
|
`);
|
|
70
71
|
process.on('uncaughtException', (error) => {
|
|
72
|
+
if (error instanceof CommandError) {
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
71
75
|
console.error(chalk.red('An unexpected error occurred:'), error.message);
|
|
72
76
|
process.exit(1);
|
|
73
77
|
});
|
|
74
|
-
process.on('unhandledRejection', (reason
|
|
75
|
-
|
|
78
|
+
process.on('unhandledRejection', (reason) => {
|
|
79
|
+
if (reason instanceof CommandError) {
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
console.error(chalk.red('An unexpected error occurred:'), reason instanceof Error ? reason.message : String(reason));
|
|
76
83
|
process.exit(1);
|
|
77
84
|
});
|
|
78
85
|
program.parse();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import Logger from '../../utils/logger.js';
|
|
@@ -7,6 +7,7 @@ export class AnalyticsService {
|
|
|
7
7
|
dataFile;
|
|
8
8
|
logger;
|
|
9
9
|
config;
|
|
10
|
+
_ready;
|
|
10
11
|
constructor(config) {
|
|
11
12
|
this.logger = new Logger();
|
|
12
13
|
this.config = config;
|
|
@@ -14,36 +15,36 @@ export class AnalyticsService {
|
|
|
14
15
|
this.dataFile = analyticsDir
|
|
15
16
|
? path.join(analyticsDir, 'analytics.json')
|
|
16
17
|
: path.join(os.homedir(), '.paymongo-cli', 'analytics.json');
|
|
17
|
-
this.loadEvents();
|
|
18
|
+
this._ready = this.loadEvents();
|
|
18
19
|
}
|
|
19
|
-
loadEvents() {
|
|
20
|
+
async loadEvents() {
|
|
20
21
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.events = this.events.slice(-1000);
|
|
26
|
-
}
|
|
22
|
+
const data = JSON.parse(await fs.readFile(this.dataFile, 'utf-8'));
|
|
23
|
+
this.events = data.events || [];
|
|
24
|
+
if (this.events.length > 1000) {
|
|
25
|
+
this.events = this.events.slice(-1000);
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
28
|
catch (error) {
|
|
29
|
+
if (error.code === 'ENOENT') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
30
32
|
this.logger.error('Failed to load analytics data', error);
|
|
31
33
|
this.events = [];
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
|
-
saveEvents() {
|
|
36
|
+
async saveEvents() {
|
|
35
37
|
try {
|
|
36
38
|
const dir = path.dirname(this.dataFile);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
fs.writeFileSync(this.dataFile, JSON.stringify({ events: this.events }, null, 2));
|
|
39
|
+
await fs.mkdir(dir, { recursive: true });
|
|
40
|
+
await fs.writeFile(this.dataFile, JSON.stringify({ events: this.events }, null, 2));
|
|
41
41
|
}
|
|
42
42
|
catch (error) {
|
|
43
43
|
this.logger.error('Failed to save analytics data', error);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
recordEvent(event) {
|
|
46
|
+
async recordEvent(event) {
|
|
47
|
+
await this._ready;
|
|
47
48
|
if (!this.config.analytics?.enabled) {
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
@@ -56,7 +57,7 @@ export class AnalyticsService {
|
|
|
56
57
|
if (this.events.length > 1000) {
|
|
57
58
|
this.events = this.events.slice(-1000);
|
|
58
59
|
}
|
|
59
|
-
this.saveEvents();
|
|
60
|
+
await this.saveEvents();
|
|
60
61
|
}
|
|
61
62
|
getAnalytics() {
|
|
62
63
|
const totalEvents = this.events.length;
|
|
@@ -87,8 +88,8 @@ export class AnalyticsService {
|
|
|
87
88
|
errorsByType,
|
|
88
89
|
};
|
|
89
90
|
}
|
|
90
|
-
clearAnalytics() {
|
|
91
|
+
async clearAnalytics() {
|
|
91
92
|
this.events = [];
|
|
92
|
-
this.saveEvents();
|
|
93
|
+
await this.saveEvents();
|
|
93
94
|
}
|
|
94
95
|
}
|
|
@@ -2,7 +2,7 @@ import { request } from 'undici';
|
|
|
2
2
|
import { NetworkError, ApiKeyError, PayMongoError, withRetry } from '../../utils/errors.js';
|
|
3
3
|
import Cache from '../../utils/cache.js';
|
|
4
4
|
import RateLimiter from './rate-limiter.js';
|
|
5
|
-
|
|
5
|
+
import { CLI_VERSION, REQUEST_TIMEOUT, CACHE_TTL, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_DEFAULT_MAX, RATE_LIMIT_WEBHOOKS_MAX, RATE_LIMIT_PAYMENTS_MAX, RATE_LIMIT_REFUNDS_MAX, RATE_LIMIT_ENV_MULTIPLIER, PAYMONGO_API_BASE, } from '../../utils/constants.js';
|
|
6
6
|
export class ApiClient {
|
|
7
7
|
config;
|
|
8
8
|
baseUrl;
|
|
@@ -12,13 +12,13 @@ export class ApiClient {
|
|
|
12
12
|
rateLimiter;
|
|
13
13
|
constructor(options) {
|
|
14
14
|
this.config = options.config;
|
|
15
|
-
this.baseUrl =
|
|
15
|
+
this.baseUrl = PAYMONGO_API_BASE;
|
|
16
16
|
this.timeout = options.timeout || REQUEST_TIMEOUT;
|
|
17
17
|
this.defaultHeaders = {
|
|
18
18
|
'Content-Type': 'application/json',
|
|
19
|
-
'User-Agent':
|
|
19
|
+
'User-Agent': `paymongo-cli/${CLI_VERSION}`,
|
|
20
20
|
};
|
|
21
|
-
this.cache = new Cache({ ttl:
|
|
21
|
+
this.cache = new Cache({ ttl: CACHE_TTL });
|
|
22
22
|
const rateLimitEnabled = options.enableRateLimiting !== false && this.config.rateLimiting?.enabled !== false;
|
|
23
23
|
if (rateLimitEnabled) {
|
|
24
24
|
const rateLimitConfig = options.rateLimitConfig || this.getDefaultRateLimitConfig();
|
|
@@ -42,26 +42,26 @@ export class ApiClient {
|
|
|
42
42
|
getDefaultRateLimitConfig() {
|
|
43
43
|
return {
|
|
44
44
|
default: {
|
|
45
|
-
maxRequests:
|
|
46
|
-
windowMs:
|
|
47
|
-
environmentMultiplier:
|
|
45
|
+
maxRequests: RATE_LIMIT_DEFAULT_MAX,
|
|
46
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
47
|
+
environmentMultiplier: RATE_LIMIT_ENV_MULTIPLIER,
|
|
48
48
|
},
|
|
49
49
|
endpoints: {
|
|
50
50
|
'/webhooks': {
|
|
51
|
-
maxRequests:
|
|
52
|
-
windowMs:
|
|
51
|
+
maxRequests: RATE_LIMIT_WEBHOOKS_MAX,
|
|
52
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
53
53
|
},
|
|
54
54
|
'/payments': {
|
|
55
|
-
maxRequests:
|
|
56
|
-
windowMs:
|
|
55
|
+
maxRequests: RATE_LIMIT_PAYMENTS_MAX,
|
|
56
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
57
57
|
},
|
|
58
58
|
'/payment_intents': {
|
|
59
|
-
maxRequests:
|
|
60
|
-
windowMs:
|
|
59
|
+
maxRequests: RATE_LIMIT_PAYMENTS_MAX,
|
|
60
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
61
61
|
},
|
|
62
62
|
'/refunds': {
|
|
63
|
-
maxRequests:
|
|
64
|
-
windowMs:
|
|
63
|
+
maxRequests: RATE_LIMIT_REFUNDS_MAX,
|
|
64
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
environments: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
@@ -6,31 +6,30 @@ const STATE_DIR = path.join(os.homedir(), '.paymongo-cli');
|
|
|
6
6
|
const STATE_FILE = path.join(STATE_DIR, 'dev-server.json');
|
|
7
7
|
const LOG_FILE = path.join(STATE_DIR, 'dev-server.log');
|
|
8
8
|
export class DevProcessManager {
|
|
9
|
-
static saveState(state) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
9
|
+
static async saveState(state) {
|
|
10
|
+
await fs.mkdir(STATE_DIR, { recursive: true });
|
|
11
|
+
await fs.writeFile(STATE_FILE, JSON.stringify(state, null, 2));
|
|
14
12
|
}
|
|
15
|
-
static loadState() {
|
|
13
|
+
static async loadState() {
|
|
16
14
|
try {
|
|
17
|
-
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
const content = fs.readFileSync(STATE_FILE, 'utf-8');
|
|
15
|
+
const content = await fs.readFile(STATE_FILE, 'utf-8');
|
|
21
16
|
return JSON.parse(content);
|
|
22
17
|
}
|
|
23
|
-
catch {
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
24
22
|
return null;
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
|
-
static clearState() {
|
|
25
|
+
static async clearState() {
|
|
28
26
|
try {
|
|
29
|
-
|
|
30
|
-
fs.unlinkSync(STATE_FILE);
|
|
31
|
-
}
|
|
27
|
+
await fs.unlink(STATE_FILE);
|
|
32
28
|
}
|
|
33
|
-
catch {
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
static isProcessRunning(pid) {
|
|
@@ -56,32 +55,31 @@ export class DevProcessManager {
|
|
|
56
55
|
return false;
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
|
-
static getLogFile() {
|
|
60
|
-
|
|
61
|
-
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
62
|
-
}
|
|
58
|
+
static async getLogFile() {
|
|
59
|
+
await fs.mkdir(STATE_DIR, { recursive: true });
|
|
63
60
|
return LOG_FILE;
|
|
64
61
|
}
|
|
65
|
-
static readLogs(lines = 50) {
|
|
62
|
+
static async readLogs(lines = 50) {
|
|
66
63
|
try {
|
|
67
|
-
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
const content = fs.readFileSync(LOG_FILE, 'utf-8');
|
|
64
|
+
const content = await fs.readFile(LOG_FILE, 'utf-8');
|
|
71
65
|
const allLines = content.split('\n').filter(line => line.trim());
|
|
72
66
|
return allLines.slice(-lines);
|
|
73
67
|
}
|
|
74
|
-
catch {
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
75
72
|
return [];
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
|
-
static clearLogs() {
|
|
75
|
+
static async clearLogs() {
|
|
79
76
|
try {
|
|
80
|
-
|
|
81
|
-
fs.writeFileSync(LOG_FILE, '');
|
|
82
|
-
}
|
|
77
|
+
await fs.writeFile(LOG_FILE, '');
|
|
83
78
|
}
|
|
84
|
-
catch {
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
static formatUptime(startedAt) {
|
|
@@ -2,15 +2,18 @@ import * as http from 'http';
|
|
|
2
2
|
import * as crypto from 'crypto';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { AnalyticsService } from '../analytics/service.js';
|
|
5
|
+
import Logger from '../../utils/logger.js';
|
|
5
6
|
export class DevServer {
|
|
6
7
|
server;
|
|
7
8
|
port;
|
|
8
9
|
config;
|
|
9
10
|
analytics;
|
|
11
|
+
logger;
|
|
10
12
|
constructor(port, config) {
|
|
11
13
|
this.port = port;
|
|
12
14
|
this.config = config;
|
|
13
15
|
this.analytics = new AnalyticsService(config);
|
|
16
|
+
this.logger = new Logger();
|
|
14
17
|
this.server = http.createServer((req, res) => {
|
|
15
18
|
this.handleWebhookRequest(req, res);
|
|
16
19
|
});
|
|
@@ -18,7 +21,7 @@ export class DevServer {
|
|
|
18
21
|
async start() {
|
|
19
22
|
return new Promise((resolve, reject) => {
|
|
20
23
|
this.server.listen(this.port, () => {
|
|
21
|
-
|
|
24
|
+
this.logger.success(`Webhook server listening on http://localhost:${this.port}`);
|
|
22
25
|
resolve();
|
|
23
26
|
});
|
|
24
27
|
this.server.on('error', (error) => {
|
|
@@ -29,7 +32,7 @@ export class DevServer {
|
|
|
29
32
|
async stop() {
|
|
30
33
|
return new Promise((resolve) => {
|
|
31
34
|
this.server.close(() => {
|
|
32
|
-
|
|
35
|
+
this.logger.warning('Webhook server stopped');
|
|
33
36
|
resolve();
|
|
34
37
|
});
|
|
35
38
|
});
|
|
@@ -46,42 +49,45 @@ export class DevServer {
|
|
|
46
49
|
body += chunk.toString();
|
|
47
50
|
});
|
|
48
51
|
req.on('end', () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
this.logWebhookEvent(event);
|
|
65
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
66
|
-
res.end(JSON.stringify({ success: true }));
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
console.error(chalk.red('✗'), 'Failed to process webhook:', error.message);
|
|
70
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
71
|
-
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
72
|
-
this.analytics.recordEvent({
|
|
73
|
-
type: 'unknown',
|
|
52
|
+
void this.processWebhookBody(body, req, res);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async processWebhookBody(body, req, res) {
|
|
56
|
+
try {
|
|
57
|
+
const event = JSON.parse(body);
|
|
58
|
+
const signatureValid = this.verifyWebhookSignature(req, body);
|
|
59
|
+
if (!signatureValid) {
|
|
60
|
+
this.logger.failure('Webhook signature verification failed');
|
|
61
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
62
|
+
res.end(JSON.stringify({ error: 'Invalid signature' }));
|
|
63
|
+
await this.analytics.recordEvent({
|
|
64
|
+
type: event.data?.type || 'unknown',
|
|
74
65
|
success: false,
|
|
75
|
-
error: 'Invalid
|
|
66
|
+
error: 'Invalid signature',
|
|
67
|
+
data: event.data?.attributes,
|
|
76
68
|
});
|
|
69
|
+
return;
|
|
77
70
|
}
|
|
78
|
-
|
|
71
|
+
await this.logWebhookEvent(event);
|
|
72
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
73
|
+
res.end(JSON.stringify({ success: true }));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
this.logger.error('Failed to process webhook:', error.message);
|
|
77
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
78
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
79
|
+
await this.analytics.recordEvent({
|
|
80
|
+
type: 'unknown',
|
|
81
|
+
success: false,
|
|
82
|
+
error: 'Invalid JSON',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
79
85
|
}
|
|
80
|
-
logWebhookEvent(event) {
|
|
86
|
+
async logWebhookEvent(event) {
|
|
81
87
|
const timestamp = new Date().toLocaleTimeString();
|
|
82
88
|
const eventType = event.data?.type || 'unknown';
|
|
83
89
|
const eventId = event.data?.id || 'unknown';
|
|
84
|
-
this.analytics.recordEvent({
|
|
90
|
+
await this.analytics.recordEvent({
|
|
85
91
|
type: eventType,
|
|
86
92
|
success: true,
|
|
87
93
|
data: event.data?.attributes,
|
|
@@ -101,23 +107,23 @@ export class DevServer {
|
|
|
101
107
|
}
|
|
102
108
|
verifyWebhookSignature(req, body) {
|
|
103
109
|
if (!this.config.dev.verifyWebhookSignatures) {
|
|
104
|
-
|
|
110
|
+
this.logger.warn('Webhook signature verification disabled in config');
|
|
105
111
|
return true;
|
|
106
112
|
}
|
|
107
113
|
const signatureHeader = req.headers['paymongo-signature'];
|
|
108
114
|
if (!signatureHeader) {
|
|
109
|
-
|
|
115
|
+
this.logger.failure('Signature verification required but no signature header found');
|
|
110
116
|
return false;
|
|
111
117
|
}
|
|
112
118
|
const signatureParts = signatureHeader.split(',');
|
|
113
119
|
if (signatureParts.length < 2) {
|
|
114
|
-
|
|
120
|
+
this.logger.failure('Invalid signature format');
|
|
115
121
|
return false;
|
|
116
122
|
}
|
|
117
123
|
const timestamp = signatureParts.find((part) => part.startsWith('t='))?.split('=')[1];
|
|
118
124
|
const signature = signatureParts.find((part) => part.startsWith('te='))?.split('=')[1];
|
|
119
125
|
if (!timestamp || !signature) {
|
|
120
|
-
|
|
126
|
+
this.logger.failure('Missing timestamp or signature in header');
|
|
121
127
|
return false;
|
|
122
128
|
}
|
|
123
129
|
const webhookId = signatureParts.find((part) => part.startsWith('li='))?.split('=')[1];
|
|
@@ -130,12 +136,12 @@ export class DevServer {
|
|
|
130
136
|
else {
|
|
131
137
|
secretKeys = Object.values(webhookSecrets).filter((secret) => typeof secret === 'string' && secret.length > 0);
|
|
132
138
|
if (webhookId && secretKeys.length > 0) {
|
|
133
|
-
|
|
139
|
+
this.logger.warning(`No webhook secret found for id ${webhookId}. Update your configuration.`);
|
|
134
140
|
return false;
|
|
135
141
|
}
|
|
136
142
|
}
|
|
137
143
|
if (secretKeys.length === 0) {
|
|
138
|
-
|
|
144
|
+
this.logger.warning('Signature verification enabled but no webhook secrets configured');
|
|
139
145
|
return true;
|
|
140
146
|
}
|
|
141
147
|
let isValid = false;
|
|
@@ -155,11 +161,11 @@ export class DevServer {
|
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
if (isValid) {
|
|
158
|
-
|
|
164
|
+
this.logger.success('Signature verified successfully');
|
|
159
165
|
return true;
|
|
160
166
|
}
|
|
161
167
|
else {
|
|
162
|
-
|
|
168
|
+
this.logger.failure('Signature verification failed');
|
|
163
169
|
return false;
|
|
164
170
|
}
|
|
165
171
|
}
|
package/dist/utils/bulk.js
CHANGED
|
@@ -32,8 +32,24 @@ export class BulkOperations {
|
|
|
32
32
|
return filename;
|
|
33
33
|
}
|
|
34
34
|
static async importWebhooks(filename) {
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
let content;
|
|
36
|
+
try {
|
|
37
|
+
content = await fs.readFile(filename, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const code = error.code;
|
|
41
|
+
if (code === 'ENOENT') {
|
|
42
|
+
throw new PayMongoError(`File not found: ${filename}`, 'FILE_NOT_FOUND', 404);
|
|
43
|
+
}
|
|
44
|
+
throw new PayMongoError(`Cannot read file: ${filename}`, 'FILE_READ_ERROR', 400);
|
|
45
|
+
}
|
|
46
|
+
let data;
|
|
47
|
+
try {
|
|
48
|
+
data = JSON.parse(content);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new PayMongoError(`Invalid JSON in ${filename}`, 'INVALID_JSON', 400);
|
|
52
|
+
}
|
|
37
53
|
this.validateImportData(data, 'webhooks');
|
|
38
54
|
return {
|
|
39
55
|
webhooks: data.data,
|
|
@@ -41,8 +57,24 @@ export class BulkOperations {
|
|
|
41
57
|
};
|
|
42
58
|
}
|
|
43
59
|
static async importPayments(filename) {
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
let content;
|
|
61
|
+
try {
|
|
62
|
+
content = await fs.readFile(filename, 'utf-8');
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const code = error.code;
|
|
66
|
+
if (code === 'ENOENT') {
|
|
67
|
+
throw new PayMongoError(`File not found: ${filename}`, 'FILE_NOT_FOUND', 404);
|
|
68
|
+
}
|
|
69
|
+
throw new PayMongoError(`Cannot read file: ${filename}`, 'FILE_READ_ERROR', 400);
|
|
70
|
+
}
|
|
71
|
+
let data;
|
|
72
|
+
try {
|
|
73
|
+
data = JSON.parse(content);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new PayMongoError(`Invalid JSON in ${filename}`, 'INVALID_JSON', 400);
|
|
77
|
+
}
|
|
46
78
|
this.validateImportData(data, 'payments');
|
|
47
79
|
return {
|
|
48
80
|
payments: data.data,
|
package/dist/utils/constants.js
CHANGED
|
@@ -12,10 +12,20 @@ export const ENVIRONMENTS = ['test', 'live'];
|
|
|
12
12
|
export const CONFIG_FILE_NAME = '.paymongo';
|
|
13
13
|
export const ENV_FILE_NAME = '.env';
|
|
14
14
|
export const CLI_NAME = 'paymongo';
|
|
15
|
-
|
|
15
|
+
import { createRequire } from 'module';
|
|
16
|
+
const _require = createRequire(import.meta.url);
|
|
17
|
+
const _pkg = _require('../../package.json');
|
|
18
|
+
export const CLI_VERSION = _pkg.version;
|
|
16
19
|
export const REQUEST_TIMEOUT = 30000;
|
|
17
20
|
export const MAX_RETRIES = 3;
|
|
18
21
|
export const RETRY_DELAY = 1000;
|
|
22
|
+
export const CACHE_TTL = 2 * 60 * 1000;
|
|
23
|
+
export const RATE_LIMIT_WINDOW_MS = 60 * 1000;
|
|
24
|
+
export const RATE_LIMIT_DEFAULT_MAX = 100;
|
|
25
|
+
export const RATE_LIMIT_WEBHOOKS_MAX = 30;
|
|
26
|
+
export const RATE_LIMIT_PAYMENTS_MAX = 60;
|
|
27
|
+
export const RATE_LIMIT_REFUNDS_MAX = 20;
|
|
28
|
+
export const RATE_LIMIT_ENV_MULTIPLIER = 0.5;
|
|
19
29
|
export const DEFAULT_DEV_PORT = 3000;
|
|
20
30
|
export const DEFAULT_WEBHOOK_PATH = '/webhook';
|
|
21
31
|
export const LOG_LEVELS = ['error', 'warn', 'info', 'debug'];
|
package/dist/utils/errors.js
CHANGED
|
@@ -56,6 +56,12 @@ export class WebhookError extends Error {
|
|
|
56
56
|
this.name = 'WebhookError';
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
export class CommandError extends Error {
|
|
60
|
+
constructor(message) {
|
|
61
|
+
super(message || 'Command failed');
|
|
62
|
+
this.name = 'CommandError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
59
65
|
export async function withRetry(operation, options = {}) {
|
|
60
66
|
const { maxRetries = 3, delayMs = 1000, backoffMultiplier = 2, silent = false, retryCondition = (error) => {
|
|
61
67
|
return (error.name === 'NetworkError' ||
|
package/dist/utils/validator.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
constructor(message, field) {
|
|
4
|
-
super(message);
|
|
5
|
-
this.field = field;
|
|
6
|
-
this.name = 'ValidationError';
|
|
7
|
-
}
|
|
8
|
-
}
|
|
1
|
+
import { ValidationError } from './errors.js';
|
|
2
|
+
export { ValidationError };
|
|
9
3
|
export function validateApiKey(key, type) {
|
|
10
4
|
if (!key || typeof key !== 'string') {
|
|
11
5
|
return false;
|
|
@@ -16,7 +10,14 @@ export function validateApiKey(key, type) {
|
|
|
16
10
|
}
|
|
17
11
|
export function validateWebhookUrl(url) {
|
|
18
12
|
try {
|
|
19
|
-
const
|
|
13
|
+
const trimmed = url.trim();
|
|
14
|
+
if (trimmed.length > 2048) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const parsedUrl = new URL(trimmed);
|
|
18
|
+
if (parsedUrl.username || parsedUrl.password) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
20
21
|
return (parsedUrl.protocol === 'https:' ||
|
|
21
22
|
parsedUrl.hostname === 'localhost' ||
|
|
22
23
|
parsedUrl.hostname === '127.0.0.1');
|