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.
@@ -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
- process.exit(1);
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
- process.exit(1);
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
- process.exit(1);
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
- process.exit(1);
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
- process.exit(1);
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
- process.exit(1);
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, promise) => {
75
- console.error(chalk.red('Unhandled Rejection at:'), promise, 'reason:', reason);
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
- if (fs.existsSync(this.dataFile)) {
22
- const data = JSON.parse(fs.readFileSync(this.dataFile, 'utf-8'));
23
- this.events = data.events || [];
24
- if (this.events.length > 1000) {
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
- if (!fs.existsSync(dir)) {
38
- fs.mkdirSync(dir, { recursive: true });
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
- const REQUEST_TIMEOUT = 30000;
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 = 'https://api.paymongo.com';
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': 'paymongo-cli/1.0.0',
19
+ 'User-Agent': `paymongo-cli/${CLI_VERSION}`,
20
20
  };
21
- this.cache = new Cache({ ttl: 2 * 60 * 1000 });
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: 100,
46
- windowMs: 60 * 1000,
47
- environmentMultiplier: 0.5,
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: 30,
52
- windowMs: 60 * 1000,
51
+ maxRequests: RATE_LIMIT_WEBHOOKS_MAX,
52
+ windowMs: RATE_LIMIT_WINDOW_MS,
53
53
  },
54
54
  '/payments': {
55
- maxRequests: 60,
56
- windowMs: 60 * 1000,
55
+ maxRequests: RATE_LIMIT_PAYMENTS_MAX,
56
+ windowMs: RATE_LIMIT_WINDOW_MS,
57
57
  },
58
58
  '/payment_intents': {
59
- maxRequests: 60,
60
- windowMs: 60 * 1000,
59
+ maxRequests: RATE_LIMIT_PAYMENTS_MAX,
60
+ windowMs: RATE_LIMIT_WINDOW_MS,
61
61
  },
62
62
  '/refunds': {
63
- maxRequests: 20,
64
- windowMs: 60 * 1000,
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 * as fs from 'fs';
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
- if (!fs.existsSync(STATE_DIR)) {
11
- fs.mkdirSync(STATE_DIR, { recursive: true });
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
- if (!fs.existsSync(STATE_FILE)) {
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
- if (fs.existsSync(STATE_FILE)) {
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
- if (!fs.existsSync(STATE_DIR)) {
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
- if (!fs.existsSync(LOG_FILE)) {
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
- if (fs.existsSync(LOG_FILE)) {
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
- console.log(chalk.green('✓'), `Webhook server listening on http://localhost:${this.port}`);
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
- console.log(chalk.yellow('✓'), 'Webhook server stopped');
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
- try {
50
- const event = JSON.parse(body);
51
- const signatureValid = this.verifyWebhookSignature(req, body);
52
- if (!signatureValid) {
53
- console.log(chalk.red('⚠️'), 'Webhook signature verification failed');
54
- res.writeHead(401, { 'Content-Type': 'application/json' });
55
- res.end(JSON.stringify({ error: 'Invalid signature' }));
56
- this.analytics.recordEvent({
57
- type: event.data?.type || 'unknown',
58
- success: false,
59
- error: 'Invalid signature',
60
- data: event.data?.attributes,
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 JSON',
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
- console.log(chalk.yellow('ℹ️'), 'Webhook signature verification disabled in config');
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
- console.log(chalk.red('⚠️'), 'Signature verification required but no signature header found');
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
- console.log(chalk.red('⚠️'), 'Invalid signature format');
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
- console.log(chalk.red('⚠️'), 'Missing timestamp or signature in header');
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
- console.log(chalk.yellow('⚠️'), `No webhook secret found for id ${webhookId}. Update your configuration.`);
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
- console.log(chalk.yellow('⚠️'), 'Signature verification enabled but no webhook secrets configured');
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
- console.log(chalk.green('✓'), 'Signature verified successfully');
164
+ this.logger.success('Signature verified successfully');
159
165
  return true;
160
166
  }
161
167
  else {
162
- console.log(chalk.red('✗'), 'Signature verification failed');
168
+ this.logger.failure('Signature verification failed');
163
169
  return false;
164
170
  }
165
171
  }
@@ -32,8 +32,24 @@ export class BulkOperations {
32
32
  return filename;
33
33
  }
34
34
  static async importWebhooks(filename) {
35
- const content = await fs.readFile(filename, 'utf-8');
36
- const data = JSON.parse(content);
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
- const content = await fs.readFile(filename, 'utf-8');
45
- const data = JSON.parse(content);
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,
@@ -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
- export const CLI_VERSION = '1.0.0';
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'];
@@ -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' ||
@@ -1,11 +1,5 @@
1
- export class ValidationError extends Error {
2
- field;
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 parsedUrl = new URL(url);
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');