paymongo-cli 1.4.4 → 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/.github/copilot-instructions.md +95 -95
- package/CHANGELOG.md +85 -1
- package/LICENSE +20 -20
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/config.js +30 -15
- 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 +9 -8
- package/dist/commands/env.js +6 -6
- package/dist/commands/generate/templates/checkout-page/index.js +520 -520
- package/dist/commands/generate/templates/payment-intent/javascript.js +68 -68
- package/dist/commands/generate/templates/payment-intent/typescript.js +92 -92
- package/dist/commands/generate/templates/webhook-handler/javascript.js +192 -147
- package/dist/commands/generate/templates/webhook-handler/typescript.js +147 -117
- package/dist/commands/generate.js +43 -37
- package/dist/commands/init.js +25 -8
- package/dist/commands/login.js +56 -19
- package/dist/commands/payments.js +9 -8
- package/dist/commands/team/index.js +11 -9
- package/dist/commands/trigger.js +58 -18
- package/dist/commands/webhooks.js +8 -7
- package/dist/index.js +9 -2
- package/dist/services/analytics/service.js +24 -19
- package/dist/services/api/client.js +16 -16
- package/dist/services/config/manager.js +6 -8
- package/dist/services/dev/process-manager.js +30 -32
- package/dist/services/dev/server.js +45 -39
- package/dist/services/team/service.js +4 -1
- package/dist/types/schemas.js +38 -9
- 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/eslint.config.ts +70 -70
- package/package.json +2 -2
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -281
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -281
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -5053
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/dist/commands/config.d.ts +0 -21
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/dev.d.ts +0 -16
- package/dist/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/env.d.ts +0 -4
- package/dist/commands/env.d.ts.map +0 -1
- package/dist/commands/env.js.map +0 -1
- package/dist/commands/init.d.ts +0 -15
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.d.ts +0 -20
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/payments.d.ts +0 -41
- package/dist/commands/payments.d.ts.map +0 -1
- package/dist/commands/payments.js.map +0 -1
- package/dist/commands/team/index.d.ts +0 -4
- package/dist/commands/team/index.d.ts.map +0 -1
- package/dist/commands/team/index.js.map +0 -1
- package/dist/commands/trigger.d.ts +0 -4
- package/dist/commands/trigger.d.ts.map +0 -1
- package/dist/commands/trigger.js.map +0 -1
- package/dist/commands/webhooks.d.ts +0 -23
- package/dist/commands/webhooks.d.ts.map +0 -1
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/services/analytics/service.d.ts +0 -35
- package/dist/services/analytics/service.d.ts.map +0 -1
- package/dist/services/analytics/service.js.map +0 -1
- package/dist/services/api/client.d.ts +0 -26
- package/dist/services/api/client.d.ts.map +0 -1
- package/dist/services/api/client.js.map +0 -1
- package/dist/services/api/rate-limiter.d.ts +0 -64
- package/dist/services/api/rate-limiter.d.ts.map +0 -1
- package/dist/services/api/rate-limiter.js.map +0 -1
- package/dist/services/api/undici-client.d.ts +0 -39
- package/dist/services/api/undici-client.d.ts.map +0 -1
- package/dist/services/api/undici-client.js +0 -288
- package/dist/services/api/undici-client.js.map +0 -1
- package/dist/services/config/manager.d.ts +0 -16
- package/dist/services/config/manager.d.ts.map +0 -1
- package/dist/services/config/manager.js.map +0 -1
- package/dist/services/dev/process-manager.d.ts +0 -50
- package/dist/services/dev/process-manager.d.ts.map +0 -1
- package/dist/services/dev/process-manager.js.map +0 -1
- package/dist/services/github/auth.d.ts +0 -15
- package/dist/services/github/auth.d.ts.map +0 -1
- package/dist/services/github/auth.js +0 -79
- package/dist/services/github/auth.js.map +0 -1
- package/dist/services/github/client.d.ts +0 -95
- package/dist/services/github/client.d.ts.map +0 -1
- package/dist/services/github/client.js +0 -130
- package/dist/services/github/client.js.map +0 -1
- package/dist/services/github/sync.d.ts +0 -26
- package/dist/services/github/sync.d.ts.map +0 -1
- package/dist/services/github/sync.js +0 -203
- package/dist/services/github/sync.js.map +0 -1
- package/dist/services/payments/simulator.d.ts +0 -28
- package/dist/services/payments/simulator.d.ts.map +0 -1
- package/dist/services/payments/simulator.js.map +0 -1
- package/dist/services/team/service.d.ts +0 -44
- package/dist/services/team/service.d.ts.map +0 -1
- package/dist/services/team/service.js.map +0 -1
- package/dist/services/web/server.d.ts +0 -31
- package/dist/services/web/server.d.ts.map +0 -1
- package/dist/services/web/server.js +0 -206
- package/dist/services/web/server.js.map +0 -1
- package/dist/types/paymongo.d.ts +0 -204
- package/dist/types/paymongo.d.ts.map +0 -1
- package/dist/types/paymongo.js.map +0 -1
- package/dist/types/schemas.d.ts +0 -80
- package/dist/types/schemas.d.ts.map +0 -1
- package/dist/types/schemas.js.map +0 -1
- package/dist/utils/bulk.d.ts +0 -62
- package/dist/utils/bulk.d.ts.map +0 -1
- package/dist/utils/bulk.js.map +0 -1
- package/dist/utils/cache.d.ts +0 -22
- package/dist/utils/cache.d.ts.map +0 -1
- package/dist/utils/cache.js.map +0 -1
- package/dist/utils/constants.d.ts +0 -32
- package/dist/utils/constants.d.ts.map +0 -1
- package/dist/utils/constants.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -34
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -20
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/spinner.d.ts +0 -17
- package/dist/utils/spinner.d.ts.map +0 -1
- package/dist/utils/spinner.js.map +0 -1
- package/dist/utils/validator.d.ts +0 -10
- package/dist/utils/validator.d.ts.map +0 -1
- package/dist/utils/validator.js.map +0 -1
- package/dist/utils/webhook-store.d.ts +0 -22
- package/dist/utils/webhook-store.d.ts.map +0 -1
- package/dist/utils/webhook-store.js.map +0 -1
|
@@ -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
|
}
|
|
@@ -41,7 +41,7 @@ export class TeamService {
|
|
|
41
41
|
await this.config.save(config);
|
|
42
42
|
return bundle;
|
|
43
43
|
}
|
|
44
|
-
async importKeyBundle(bundle, memberName) {
|
|
44
|
+
async importKeyBundle(bundle, memberName, options = {}) {
|
|
45
45
|
const config = await this.config.load();
|
|
46
46
|
if (!config) {
|
|
47
47
|
throw new PayMongoError('No configuration found. Run "paymongo init" first.', 'CONFIG_NOT_FOUND', 400);
|
|
@@ -72,6 +72,9 @@ export class TeamService {
|
|
|
72
72
|
if (!config.apiKeys[env]) {
|
|
73
73
|
config.apiKeys[env] = bundle.keys[env];
|
|
74
74
|
}
|
|
75
|
+
else if (options.force) {
|
|
76
|
+
config.apiKeys[env] = bundle.keys[env];
|
|
77
|
+
}
|
|
75
78
|
else {
|
|
76
79
|
console.log(`⚠️ ${env.toUpperCase()} keys already exist. Use --force to overwrite.`);
|
|
77
80
|
}
|
package/dist/types/schemas.js
CHANGED
|
@@ -12,23 +12,52 @@ const DevConfigSchema = z.object({
|
|
|
12
12
|
autoRegisterWebhook: z.boolean(),
|
|
13
13
|
verifyWebhookSignatures: z.boolean(),
|
|
14
14
|
});
|
|
15
|
-
const TeamConfigSchema = z.object({
|
|
16
|
-
githubToken: z.string().optional(),
|
|
17
|
-
repo: z.string().optional(),
|
|
18
|
-
branch: z.string().optional(),
|
|
19
|
-
}).optional();
|
|
20
15
|
export const PayMongoConfigSchema = z.object({
|
|
21
16
|
version: z.string().min(1, 'Version is required'),
|
|
22
17
|
projectName: z.string().min(1, 'Project name is required'),
|
|
23
18
|
environment: z.enum(['test', 'live']),
|
|
24
|
-
apiKeys: z
|
|
19
|
+
apiKeys: z
|
|
20
|
+
.object({
|
|
25
21
|
test: ApiKeysSchema.optional(),
|
|
26
22
|
live: ApiKeysSchema.optional(),
|
|
27
|
-
})
|
|
23
|
+
})
|
|
24
|
+
.optional(),
|
|
28
25
|
webhooks: WebhooksConfigSchema,
|
|
29
|
-
webhookSecrets: z.record(z.string(), z.string()),
|
|
26
|
+
webhookSecrets: z.record(z.string(), z.string()).optional(),
|
|
30
27
|
dev: DevConfigSchema,
|
|
31
|
-
team:
|
|
28
|
+
team: z
|
|
29
|
+
.object({
|
|
30
|
+
name: z.string().optional(),
|
|
31
|
+
members: z
|
|
32
|
+
.array(z.object({
|
|
33
|
+
name: z.string(),
|
|
34
|
+
email: z.string().optional(),
|
|
35
|
+
addedAt: z.number(),
|
|
36
|
+
sharedKeys: z.array(z.string()).optional(),
|
|
37
|
+
}))
|
|
38
|
+
.optional(),
|
|
39
|
+
sharedKeyBundles: z
|
|
40
|
+
.array(z.object({
|
|
41
|
+
id: z.string(),
|
|
42
|
+
createdAt: z.number(),
|
|
43
|
+
environments: z.array(z.enum(['test', 'live'])),
|
|
44
|
+
sharedWith: z.array(z.string()),
|
|
45
|
+
}))
|
|
46
|
+
.optional(),
|
|
47
|
+
})
|
|
48
|
+
.optional(),
|
|
49
|
+
registeredWebhooks: z
|
|
50
|
+
.array(z.object({
|
|
51
|
+
id: z.string(),
|
|
52
|
+
url: z.string(),
|
|
53
|
+
createdAt: z.number(),
|
|
54
|
+
}))
|
|
55
|
+
.optional(),
|
|
56
|
+
analytics: z
|
|
57
|
+
.object({
|
|
58
|
+
enabled: z.boolean(),
|
|
59
|
+
})
|
|
60
|
+
.optional(),
|
|
32
61
|
});
|
|
33
62
|
export function validateConfig(config) {
|
|
34
63
|
const result = PayMongoConfigSchema.safeParse(config);
|
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');
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
class WebhookEventStore {
|
|
5
5
|
storePath;
|
|
6
|
+
storeDir;
|
|
6
7
|
constructor() {
|
|
7
|
-
|
|
8
|
-
this.storePath = path.join(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
8
|
+
this.storeDir = path.join(os.homedir(), '.paymongo');
|
|
9
|
+
this.storePath = path.join(this.storeDir, 'webhook-events.json');
|
|
10
|
+
}
|
|
11
|
+
async ensureDir() {
|
|
12
|
+
await fs.mkdir(this.storeDir, { recursive: true });
|
|
12
13
|
}
|
|
13
14
|
async storeEvent(event) {
|
|
14
15
|
try {
|
|
16
|
+
await this.ensureDir();
|
|
15
17
|
const events = await this.loadEvents();
|
|
16
18
|
events.push(event);
|
|
17
19
|
if (events.length > 1000) {
|
|
18
20
|
events.splice(0, events.length - 1000);
|
|
19
21
|
}
|
|
20
|
-
fs.
|
|
22
|
+
await fs.writeFile(this.storePath, JSON.stringify(events, null, 2));
|
|
21
23
|
}
|
|
22
24
|
catch (error) {
|
|
23
25
|
console.warn('Failed to store webhook event:', error);
|
|
@@ -25,13 +27,13 @@ class WebhookEventStore {
|
|
|
25
27
|
}
|
|
26
28
|
async loadEvents() {
|
|
27
29
|
try {
|
|
28
|
-
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
const data = fs.readFileSync(this.storePath, 'utf-8');
|
|
30
|
+
const data = await fs.readFile(this.storePath, 'utf-8');
|
|
32
31
|
return JSON.parse(data);
|
|
33
32
|
}
|
|
34
|
-
catch (
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
35
37
|
return [];
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -45,11 +47,12 @@ class WebhookEventStore {
|
|
|
45
47
|
}
|
|
46
48
|
async clearEvents() {
|
|
47
49
|
try {
|
|
48
|
-
|
|
49
|
-
fs.unlinkSync(this.storePath);
|
|
50
|
-
}
|
|
50
|
+
await fs.unlink(this.storePath);
|
|
51
51
|
}
|
|
52
52
|
catch (error) {
|
|
53
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
53
56
|
console.warn('Failed to clear webhook events:', error);
|
|
54
57
|
}
|
|
55
58
|
}
|
package/eslint.config.ts
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
import eslint from '@eslint/js';
|
|
2
|
-
import tseslint from 'typescript-eslint';
|
|
3
|
-
import globals from 'globals';
|
|
4
|
-
|
|
5
|
-
export default tseslint.config(
|
|
6
|
-
eslint.configs.recommended,
|
|
7
|
-
...tseslint.configs.recommended,
|
|
8
|
-
{
|
|
9
|
-
files: ['src/**/*.ts'],
|
|
10
|
-
languageOptions: {
|
|
11
|
-
ecmaVersion: 2022,
|
|
12
|
-
sourceType: 'module',
|
|
13
|
-
globals: {
|
|
14
|
-
...globals.node,
|
|
15
|
-
...globals.es2022,
|
|
16
|
-
},
|
|
17
|
-
parserOptions: {
|
|
18
|
-
project: './tsconfig.json',
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
rules: {
|
|
22
|
-
'no-unused-vars': 'off',
|
|
23
|
-
'@typescript-eslint/no-unused-vars': ['warn', {
|
|
24
|
-
argsIgnorePattern: '^_',
|
|
25
|
-
varsIgnorePattern: '^_',
|
|
26
|
-
caughtErrorsIgnorePattern: '^_'
|
|
27
|
-
}],
|
|
28
|
-
'no-console': 'off',
|
|
29
|
-
'no-redeclare': 'off',
|
|
30
|
-
'@typescript-eslint/no-redeclare': 'error',
|
|
31
|
-
'no-shadow': 'off',
|
|
32
|
-
'@typescript-eslint/no-shadow': 'error',
|
|
33
|
-
'no-undef': 'off',
|
|
34
|
-
'no-empty': 'error',
|
|
35
|
-
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
36
|
-
'@typescript-eslint/no-explicit-any': 'warn',
|
|
37
|
-
'@typescript-eslint/no-non-null-assertion': 'warn',
|
|
38
|
-
eqeqeq: ['error', 'always'],
|
|
39
|
-
curly: ['error', 'all'],
|
|
40
|
-
'no-var': 'error',
|
|
41
|
-
'prefer-const': 'error',
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
files: ['tests/**/*.ts'],
|
|
46
|
-
languageOptions: {
|
|
47
|
-
ecmaVersion: 2022,
|
|
48
|
-
sourceType: 'module',
|
|
49
|
-
globals: {
|
|
50
|
-
...globals.node,
|
|
51
|
-
...globals.es2022,
|
|
52
|
-
...globals.jest,
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
rules: {
|
|
56
|
-
'no-unused-vars': 'off',
|
|
57
|
-
'@typescript-eslint/no-unused-vars': ['warn', {
|
|
58
|
-
argsIgnorePattern: '^_',
|
|
59
|
-
varsIgnorePattern: '^_',
|
|
60
|
-
caughtErrorsIgnorePattern: '^_'
|
|
61
|
-
}],
|
|
62
|
-
'@typescript-eslint/no-explicit-any': 'off', // Allow any in tests for mocking
|
|
63
|
-
'no-console': 'off',
|
|
64
|
-
'no-undef': 'off',
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
ignores: ['dist/', 'node_modules/', '*.js', '*.cjs', 'coverage/'],
|
|
69
|
-
}
|
|
70
|
-
);
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import tseslint from 'typescript-eslint';
|
|
3
|
+
import globals from 'globals';
|
|
4
|
+
|
|
5
|
+
export default tseslint.config(
|
|
6
|
+
eslint.configs.recommended,
|
|
7
|
+
...tseslint.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
files: ['src/**/*.ts'],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
ecmaVersion: 2022,
|
|
12
|
+
sourceType: 'module',
|
|
13
|
+
globals: {
|
|
14
|
+
...globals.node,
|
|
15
|
+
...globals.es2022,
|
|
16
|
+
},
|
|
17
|
+
parserOptions: {
|
|
18
|
+
project: './tsconfig.json',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
rules: {
|
|
22
|
+
'no-unused-vars': 'off',
|
|
23
|
+
'@typescript-eslint/no-unused-vars': ['warn', {
|
|
24
|
+
argsIgnorePattern: '^_',
|
|
25
|
+
varsIgnorePattern: '^_',
|
|
26
|
+
caughtErrorsIgnorePattern: '^_'
|
|
27
|
+
}],
|
|
28
|
+
'no-console': 'off',
|
|
29
|
+
'no-redeclare': 'off',
|
|
30
|
+
'@typescript-eslint/no-redeclare': 'error',
|
|
31
|
+
'no-shadow': 'off',
|
|
32
|
+
'@typescript-eslint/no-shadow': 'error',
|
|
33
|
+
'no-undef': 'off',
|
|
34
|
+
'no-empty': 'error',
|
|
35
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
36
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
37
|
+
'@typescript-eslint/no-non-null-assertion': 'warn',
|
|
38
|
+
eqeqeq: ['error', 'always'],
|
|
39
|
+
curly: ['error', 'all'],
|
|
40
|
+
'no-var': 'error',
|
|
41
|
+
'prefer-const': 'error',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
files: ['tests/**/*.ts'],
|
|
46
|
+
languageOptions: {
|
|
47
|
+
ecmaVersion: 2022,
|
|
48
|
+
sourceType: 'module',
|
|
49
|
+
globals: {
|
|
50
|
+
...globals.node,
|
|
51
|
+
...globals.es2022,
|
|
52
|
+
...globals.jest,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
rules: {
|
|
56
|
+
'no-unused-vars': 'off',
|
|
57
|
+
'@typescript-eslint/no-unused-vars': ['warn', {
|
|
58
|
+
argsIgnorePattern: '^_',
|
|
59
|
+
varsIgnorePattern: '^_',
|
|
60
|
+
caughtErrorsIgnorePattern: '^_'
|
|
61
|
+
}],
|
|
62
|
+
'@typescript-eslint/no-explicit-any': 'off', // Allow any in tests for mocking
|
|
63
|
+
'no-console': 'off',
|
|
64
|
+
'no-undef': 'off',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
ignores: ['dist/', 'node_modules/', '*.js', '*.cjs', 'coverage/'],
|
|
69
|
+
}
|
|
70
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paymongo-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "Developer-first CLI tool for PayMongo integration development with local webhook forwarding, payment testing, and team collaboration features. See USER_GUIDE.md for comprehensive documentation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -72,4 +72,4 @@
|
|
|
72
72
|
"publishConfig": {
|
|
73
73
|
"access": "public"
|
|
74
74
|
}
|
|
75
|
-
}
|
|
75
|
+
}
|