paymongo-cli 1.4.6 → 1.4.8
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/AGENTS.md +8 -6
- package/CHANGELOG.md +77 -1
- package/README.md +7 -5
- package/TESTING.md +6 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/config/actions.js +233 -0
- package/dist/commands/config/helpers.js +153 -0
- package/dist/commands/config/rate-limit.js +138 -0
- package/dist/commands/config.js +5 -565
- 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 +11 -7
- package/dist/commands/env.js +6 -6
- package/dist/commands/init.js +4 -4
- package/dist/commands/login.js +4 -4
- package/dist/commands/payments/actions.js +346 -0
- package/dist/commands/payments/helpers.js +62 -0
- package/dist/commands/payments.js +2 -458
- package/dist/commands/team/index.js +7 -6
- package/dist/commands/trigger/actions.js +293 -0
- package/dist/commands/trigger/helpers.js +230 -0
- package/dist/commands/trigger.js +3 -524
- package/dist/commands/webhooks/actions.js +426 -0
- package/dist/commands/webhooks/helpers.js +42 -0
- package/dist/commands/webhooks.js +2 -493
- 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/config/manager.js +1 -1
- package/dist/services/dev/process-manager.js +30 -32
- package/dist/services/dev/server.js +46 -40
- package/dist/types/schemas.js +12 -0
- 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
|
@@ -1,487 +1,5 @@
|
|
|
1
|
-
import Table from 'cli-table3';
|
|
2
1
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
4
|
-
import ConfigManager from '../services/config/manager.js';
|
|
5
|
-
import ApiClient from '../services/api/client.js';
|
|
6
|
-
import { BulkOperations } from '../utils/bulk.js';
|
|
7
|
-
import Spinner from '../utils/spinner.js';
|
|
8
|
-
import { validateWebhookUrl, validateEventTypes } from '../utils/validator.js';
|
|
9
|
-
export async function exportAction(options) {
|
|
10
|
-
const spinner = new Spinner();
|
|
11
|
-
const configManager = new ConfigManager();
|
|
12
|
-
try {
|
|
13
|
-
spinner.start('Loading configuration...');
|
|
14
|
-
const config = await configManager.load();
|
|
15
|
-
if (!config) {
|
|
16
|
-
spinner.fail('No configuration found');
|
|
17
|
-
console.log(chalk.yellow('No PayMongo configuration found.'));
|
|
18
|
-
console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
spinner.succeed('Configuration loaded');
|
|
22
|
-
spinner.start('Fetching webhooks...');
|
|
23
|
-
const apiClient = new ApiClient({ config });
|
|
24
|
-
const webhooks = await apiClient.listWebhooks();
|
|
25
|
-
spinner.succeed(`Found ${webhooks.length} webhook${webhooks.length !== 1 ? 's' : ''}`);
|
|
26
|
-
if (webhooks.length === 0) {
|
|
27
|
-
console.log(chalk.yellow('No webhooks found to export.'));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
let filename = options.file;
|
|
31
|
-
if (!filename) {
|
|
32
|
-
filename = BulkOperations.generateFilename('webhooks', config.environment);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
filename = BulkOperations.ensureJsonExtension(filename);
|
|
36
|
-
}
|
|
37
|
-
spinner.start(`Exporting to ${filename}...`);
|
|
38
|
-
await BulkOperations.exportWebhooks(webhooks, filename, config.environment);
|
|
39
|
-
spinner.succeed('Export completed');
|
|
40
|
-
console.log('\n' + chalk.green('✅ Webhooks exported successfully!'));
|
|
41
|
-
console.log('');
|
|
42
|
-
console.log(`${chalk.bold('File:')} ${filename}`);
|
|
43
|
-
console.log(`${chalk.bold('Webhooks:')} ${webhooks.length}`);
|
|
44
|
-
console.log(`${chalk.bold('Environment:')} ${config.environment}`);
|
|
45
|
-
const statusCounts = webhooks.reduce((acc, webhook) => {
|
|
46
|
-
acc[webhook.attributes.status] = (acc[webhook.attributes.status] || 0) + 1;
|
|
47
|
-
return acc;
|
|
48
|
-
}, {});
|
|
49
|
-
console.log(`${chalk.bold('Status breakdown:')} ${Object.entries(statusCounts)
|
|
50
|
-
.map(([status, count]) => `${status}: ${count}`)
|
|
51
|
-
.join(', ')}`);
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
spinner.stop();
|
|
55
|
-
const err = error;
|
|
56
|
-
console.error(chalk.red('❌ Failed to export webhooks:'), err.message);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
export async function importAction(filename, options) {
|
|
61
|
-
const spinner = new Spinner();
|
|
62
|
-
const configManager = new ConfigManager();
|
|
63
|
-
try {
|
|
64
|
-
spinner.start(`Loading webhooks from ${filename}...`);
|
|
65
|
-
const { webhooks, metadata } = await BulkOperations.importWebhooks(filename);
|
|
66
|
-
spinner.succeed(`Loaded ${webhooks.length} webhooks from export`);
|
|
67
|
-
if (options.json) {
|
|
68
|
-
console.log(JSON.stringify({ webhooks, metadata }, null, 2));
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
console.log('\n' + chalk.green('✅ Webhooks loaded successfully!'));
|
|
72
|
-
console.log('');
|
|
73
|
-
console.log(`${chalk.bold('Source:')} ${filename}`);
|
|
74
|
-
console.log(`${chalk.bold('Webhooks:')} ${webhooks.length}`);
|
|
75
|
-
console.log(`${chalk.bold('Exported from:')} ${metadata.environment} environment`);
|
|
76
|
-
console.log(`${chalk.bold('Export date:')} ${new Date(metadata.exported_at).toLocaleString()}`);
|
|
77
|
-
if (webhooks.length === 0) {
|
|
78
|
-
console.log(chalk.yellow('No webhooks found in the export file.'));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
console.log('\n' + chalk.bold('Webhooks to import:'));
|
|
82
|
-
console.log(chalk.gray('─'.repeat(80)));
|
|
83
|
-
webhooks.forEach((webhook, index) => {
|
|
84
|
-
const status = options.dryRun ? chalk.gray('pending') : chalk.yellow('will create');
|
|
85
|
-
console.log(`${(index + 1).toString().padStart(2)}. ${webhook.attributes.url}`);
|
|
86
|
-
console.log(` Events: ${webhook.attributes.events.join(', ')}`);
|
|
87
|
-
console.log(` Status: ${status}`);
|
|
88
|
-
console.log('');
|
|
89
|
-
});
|
|
90
|
-
if (options.dryRun) {
|
|
91
|
-
console.log(chalk.blue('ℹ️ Dry run mode - no webhooks were created'));
|
|
92
|
-
console.log(chalk.gray('Remove --dry-run flag to actually import the webhooks'));
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
console.log(chalk.yellow('⚠️ This will create new webhooks in your PayMongo account'));
|
|
96
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
97
|
-
const shouldImport = await confirm({
|
|
98
|
-
message: `Import ${webhooks.length} webhook${webhooks.length !== 1 ? 's' : ''}?`,
|
|
99
|
-
default: false,
|
|
100
|
-
});
|
|
101
|
-
if (!shouldImport) {
|
|
102
|
-
console.log(chalk.yellow('Webhook import cancelled.'));
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const config = await configManager.load();
|
|
106
|
-
if (!config) {
|
|
107
|
-
throw new Error('Configuration lost during import process');
|
|
108
|
-
}
|
|
109
|
-
const apiClient = new ApiClient({ config });
|
|
110
|
-
const results = [];
|
|
111
|
-
for (const [index, webhook] of webhooks.entries()) {
|
|
112
|
-
try {
|
|
113
|
-
spinner.start(`Creating webhook ${index + 1}/${webhooks.length}: ${webhook.attributes.url}`);
|
|
114
|
-
const createdWebhook = await apiClient.createWebhook(webhook.attributes.url, webhook.attributes.events);
|
|
115
|
-
results.push({ success: true, webhook: createdWebhook, original: webhook });
|
|
116
|
-
spinner.succeed(`Created webhook: ${createdWebhook.id}`);
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
results.push({ success: false, error: error, original: webhook });
|
|
120
|
-
spinner.fail(`Failed to create webhook: ${error.message}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const successful = results.filter((r) => r.success).length;
|
|
124
|
-
const failed = results.filter((r) => !r.success).length;
|
|
125
|
-
console.log('\n' + chalk.bold('Import Results:'));
|
|
126
|
-
console.log(chalk.gray('─'.repeat(50)));
|
|
127
|
-
console.log(`${chalk.green('Successful:')} ${successful}`);
|
|
128
|
-
if (failed > 0) {
|
|
129
|
-
console.log(`${chalk.red('Failed:')} ${failed}`);
|
|
130
|
-
}
|
|
131
|
-
if (successful > 0) {
|
|
132
|
-
console.log('\n' + chalk.green('✅ Successfully created webhooks:'));
|
|
133
|
-
results
|
|
134
|
-
.filter((r) => r.success)
|
|
135
|
-
.forEach((r, index) => {
|
|
136
|
-
if (r.success && r.webhook) {
|
|
137
|
-
console.log(` ${index + 1}. ${r.webhook.id} - ${r.webhook.attributes.url}`);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
if (failed > 0) {
|
|
142
|
-
console.log('\n' + chalk.red('❌ Failed webhooks:'));
|
|
143
|
-
results
|
|
144
|
-
.filter((r) => !r.success)
|
|
145
|
-
.forEach((r, index) => {
|
|
146
|
-
if (!r.success && r.original && r.error) {
|
|
147
|
-
console.log(` ${index + 1}. ${r.original.attributes.url} - ${r.error.message}`);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
spinner.stop();
|
|
154
|
-
const err = error;
|
|
155
|
-
console.error(chalk.red('❌ Failed to import webhooks:'), err.message);
|
|
156
|
-
process.exit(1);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
export async function createAction(options) {
|
|
160
|
-
const spinner = new Spinner();
|
|
161
|
-
const configManager = new ConfigManager();
|
|
162
|
-
try {
|
|
163
|
-
spinner.start('Loading configuration...');
|
|
164
|
-
const config = await configManager.load();
|
|
165
|
-
if (!config) {
|
|
166
|
-
spinner.fail('No configuration found');
|
|
167
|
-
console.log(chalk.yellow('No PayMongo configuration found.'));
|
|
168
|
-
console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
spinner.succeed('Configuration loaded');
|
|
172
|
-
let answers;
|
|
173
|
-
if (options.url && options.events) {
|
|
174
|
-
answers = {
|
|
175
|
-
url: options.url,
|
|
176
|
-
events: options.events.split(','),
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
const { input, checkbox } = await import('@inquirer/prompts');
|
|
181
|
-
const url = await input({
|
|
182
|
-
message: 'Webhook URL:',
|
|
183
|
-
default: options.url || '',
|
|
184
|
-
validate: (value) => {
|
|
185
|
-
if (!value) {
|
|
186
|
-
return 'Webhook URL is required';
|
|
187
|
-
}
|
|
188
|
-
if (!validateWebhookUrl(value)) {
|
|
189
|
-
return 'Invalid webhook URL. Must be HTTPS or localhost';
|
|
190
|
-
}
|
|
191
|
-
return true;
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
const events = await checkbox({
|
|
195
|
-
message: 'Select events to listen for:',
|
|
196
|
-
choices: [
|
|
197
|
-
{ name: 'payment.paid - Payment successful', value: 'payment.paid', checked: true },
|
|
198
|
-
{ name: 'payment.failed - Payment failed', value: 'payment.failed', checked: true },
|
|
199
|
-
{ name: 'payment.refunded - Payment refunded', value: 'payment.refunded' },
|
|
200
|
-
{
|
|
201
|
-
name: 'source.chargeable - Source ready for charging',
|
|
202
|
-
value: 'source.chargeable',
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
name: 'checkout_session.payment.paid - Checkout payment successful',
|
|
206
|
-
value: 'checkout_session.payment.paid',
|
|
207
|
-
},
|
|
208
|
-
{ name: 'qrph.expired - QR Ph expired', value: 'qrph.expired' },
|
|
209
|
-
],
|
|
210
|
-
validate: (value) => {
|
|
211
|
-
if (value.length === 0) {
|
|
212
|
-
return 'At least one event must be selected';
|
|
213
|
-
}
|
|
214
|
-
try {
|
|
215
|
-
const eventStrings = value.map((v) => (typeof v === 'string' ? v : String(v)));
|
|
216
|
-
validateEventTypes(eventStrings);
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
return 'Invalid event types selected';
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
answers = { url, events };
|
|
225
|
-
}
|
|
226
|
-
if (!validateWebhookUrl(answers.url)) {
|
|
227
|
-
throw new Error('Invalid webhook URL');
|
|
228
|
-
}
|
|
229
|
-
try {
|
|
230
|
-
validateEventTypes(answers.events);
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
throw new Error('Invalid event types');
|
|
234
|
-
}
|
|
235
|
-
spinner.start('Creating webhook...');
|
|
236
|
-
const apiClient = new ApiClient({ config });
|
|
237
|
-
const webhook = await apiClient.createWebhook(answers.url, answers.events);
|
|
238
|
-
spinner.succeed('Webhook created successfully');
|
|
239
|
-
if (webhook.attributes?.secret) {
|
|
240
|
-
config.webhookSecrets = config.webhookSecrets || {};
|
|
241
|
-
config.webhookSecrets[webhook.id] = webhook.attributes.secret;
|
|
242
|
-
await configManager.save(config);
|
|
243
|
-
}
|
|
244
|
-
console.log('\n' + chalk.green('✓ Webhook created successfully!'));
|
|
245
|
-
console.log('');
|
|
246
|
-
console.log(chalk.bold('ID:'), webhook.id);
|
|
247
|
-
console.log(chalk.bold('URL:'), webhook.attributes.url);
|
|
248
|
-
console.log(chalk.bold('Events:'), webhook.attributes.events.join(', '));
|
|
249
|
-
console.log(chalk.bold('Status:'), webhook.attributes.status);
|
|
250
|
-
if (webhook.attributes?.secret) {
|
|
251
|
-
console.log(chalk.bold('Signature:'), 'Enabled (secret stored securely)');
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
spinner.stop();
|
|
256
|
-
const err = error;
|
|
257
|
-
if (err.message.includes('API key') || err.message.includes('unauthorized')) {
|
|
258
|
-
console.error(chalk.red('❌ Authentication failed:'), err.message);
|
|
259
|
-
console.log('');
|
|
260
|
-
console.log(chalk.yellow('💡 Solutions:'));
|
|
261
|
-
console.log(chalk.gray('• Run "paymongo login" to update your API keys'));
|
|
262
|
-
console.log(chalk.gray('• Check that your API keys are valid in the PayMongo dashboard'));
|
|
263
|
-
}
|
|
264
|
-
else if (err.message.includes('Network') || err.message.includes('connection')) {
|
|
265
|
-
console.error(chalk.red('❌ Network error:'), err.message);
|
|
266
|
-
console.log('');
|
|
267
|
-
console.log(chalk.yellow('💡 Try again:'));
|
|
268
|
-
console.log(chalk.gray('• Check your internet connection'));
|
|
269
|
-
console.log(chalk.gray('• PayMongo API might be temporarily unavailable'));
|
|
270
|
-
}
|
|
271
|
-
else if (err.message.includes('url') || err.message.includes('webhook')) {
|
|
272
|
-
console.error(chalk.red('❌ Webhook error:'), err.message);
|
|
273
|
-
console.log('');
|
|
274
|
-
console.log(chalk.yellow('💡 Check:'));
|
|
275
|
-
console.log(chalk.gray('• Webhook URL must be HTTPS in production'));
|
|
276
|
-
console.log(chalk.gray('• Localhost URLs only work with ngrok tunnels'));
|
|
277
|
-
console.log(chalk.gray("• URL must be accessible from PayMongo's servers"));
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
console.error(chalk.red('❌ Failed to create webhook:'), err.message);
|
|
281
|
-
console.log('');
|
|
282
|
-
console.log(chalk.yellow('💡 For help, visit: https://developers.paymongo.com/docs/webhooks'));
|
|
283
|
-
}
|
|
284
|
-
process.exit(1);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
export async function listAction(options) {
|
|
288
|
-
const spinner = new Spinner();
|
|
289
|
-
const configManager = new ConfigManager();
|
|
290
|
-
try {
|
|
291
|
-
spinner.start('Loading configuration...');
|
|
292
|
-
const config = await configManager.load();
|
|
293
|
-
if (!config) {
|
|
294
|
-
spinner.fail('No configuration found');
|
|
295
|
-
console.log(chalk.yellow('No PayMongo configuration found.'));
|
|
296
|
-
console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
spinner.succeed('Configuration loaded');
|
|
300
|
-
spinner.start('Fetching webhooks...');
|
|
301
|
-
const apiClient = new ApiClient({ config });
|
|
302
|
-
const webhooks = await apiClient.listWebhooks();
|
|
303
|
-
spinner.succeed(`Found ${webhooks.length} webhook${webhooks.length !== 1 ? 's' : ''}`);
|
|
304
|
-
if (webhooks.length === 0) {
|
|
305
|
-
console.log(chalk.gray('No webhooks found.'));
|
|
306
|
-
console.log(chalk.gray('Create one with: paymongo webhooks create'));
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
let filteredWebhooks = webhooks;
|
|
310
|
-
if (options.status) {
|
|
311
|
-
filteredWebhooks = webhooks.filter((w) => w.attributes.status === options.status);
|
|
312
|
-
}
|
|
313
|
-
if (options.events) {
|
|
314
|
-
const eventFilter = options.events.toLowerCase();
|
|
315
|
-
filteredWebhooks = filteredWebhooks.filter((w) => w.attributes.events.some((event) => event.toLowerCase().includes(eventFilter)));
|
|
316
|
-
}
|
|
317
|
-
if (options.json) {
|
|
318
|
-
console.log(JSON.stringify(filteredWebhooks, null, 2));
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
console.log('\n' + chalk.bold('Webhooks'));
|
|
322
|
-
console.log(chalk.gray('─'.repeat(95)));
|
|
323
|
-
const table = new Table({
|
|
324
|
-
head: [chalk.bold('ID'), chalk.bold('URL'), chalk.bold('Status'), chalk.bold('Events')],
|
|
325
|
-
colWidths: [15, 35, 12, 25],
|
|
326
|
-
style: {
|
|
327
|
-
head: [],
|
|
328
|
-
border: [],
|
|
329
|
-
},
|
|
330
|
-
});
|
|
331
|
-
filteredWebhooks.forEach((webhook) => {
|
|
332
|
-
const id = webhook.id.substring(0, 12) + (webhook.id.length > 12 ? '...' : '');
|
|
333
|
-
const url = webhook.attributes.url.length > 30
|
|
334
|
-
? webhook.attributes.url.substring(0, 27) + '...'
|
|
335
|
-
: webhook.attributes.url;
|
|
336
|
-
const status = webhook.attributes.status;
|
|
337
|
-
const events = webhook.attributes.events.length > 1
|
|
338
|
-
? `${webhook.attributes.events[0]} +${webhook.attributes.events.length - 1} more`
|
|
339
|
-
: webhook.attributes.events[0] || 'None';
|
|
340
|
-
table.push([
|
|
341
|
-
chalk.cyan(id),
|
|
342
|
-
chalk.yellow(url),
|
|
343
|
-
getStatusColor(status)(status),
|
|
344
|
-
chalk.white(events),
|
|
345
|
-
]);
|
|
346
|
-
});
|
|
347
|
-
console.log(table.toString());
|
|
348
|
-
console.log(chalk.gray(`Total: ${filteredWebhooks.length} webhooks`));
|
|
349
|
-
const hasNgrokUrls = filteredWebhooks.some((w) => w.attributes.url.includes('ngrok'));
|
|
350
|
-
if (hasNgrokUrls) {
|
|
351
|
-
console.log(chalk.yellow('ℹ️ Note: URLs containing "ngrok" are tunnels that forward to your localhost'));
|
|
352
|
-
console.log(chalk.gray(' These are created by "paymongo dev" and cleaned up when the server stops.'));
|
|
353
|
-
console.log('');
|
|
354
|
-
}
|
|
355
|
-
console.log(chalk.gray("Use 'paymongo webhooks show <id>' for details"));
|
|
356
|
-
}
|
|
357
|
-
catch (error) {
|
|
358
|
-
spinner.stop();
|
|
359
|
-
const err = error;
|
|
360
|
-
if (err.message.includes('API key') || err.message.includes('unauthorized')) {
|
|
361
|
-
console.error(chalk.red('❌ Authentication failed:'), err.message);
|
|
362
|
-
console.log('');
|
|
363
|
-
console.log(chalk.yellow('💡 Solutions:'));
|
|
364
|
-
console.log(chalk.gray('• Run "paymongo login" to update your API keys'));
|
|
365
|
-
console.log(chalk.gray('• Check that your API keys are valid in the PayMongo dashboard'));
|
|
366
|
-
}
|
|
367
|
-
else if (err.message.includes('Network') || err.message.includes('connection')) {
|
|
368
|
-
console.error(chalk.red('❌ Network error:'), err.message);
|
|
369
|
-
console.log('');
|
|
370
|
-
console.log(chalk.yellow('💡 Try again:'));
|
|
371
|
-
console.log(chalk.gray('• Check your internet connection'));
|
|
372
|
-
console.log(chalk.gray('• PayMongo API might be temporarily unavailable'));
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
console.error(chalk.red('❌ Failed to list webhooks:'), err.message);
|
|
376
|
-
}
|
|
377
|
-
process.exit(1);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
export async function deleteAction(id, options) {
|
|
381
|
-
const spinner = new Spinner();
|
|
382
|
-
const configManager = new ConfigManager();
|
|
383
|
-
try {
|
|
384
|
-
spinner.start('Loading configuration...');
|
|
385
|
-
const config = await configManager.load();
|
|
386
|
-
if (!config) {
|
|
387
|
-
spinner.fail('No configuration found');
|
|
388
|
-
console.log(chalk.yellow('No PayMongo configuration found.'));
|
|
389
|
-
console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
spinner.succeed('Configuration loaded');
|
|
393
|
-
if (!options.yes) {
|
|
394
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
395
|
-
const shouldDelete = await confirm({
|
|
396
|
-
message: `This will permanently delete webhook ${id}. Continue?`,
|
|
397
|
-
default: false,
|
|
398
|
-
});
|
|
399
|
-
if (!shouldDelete) {
|
|
400
|
-
console.log(chalk.yellow('Webhook deletion cancelled.'));
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
spinner.start('Deleting webhook...');
|
|
405
|
-
const apiClient = new ApiClient({ config });
|
|
406
|
-
await apiClient.deleteWebhook(id);
|
|
407
|
-
spinner.succeed('Webhook deleted successfully');
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
spinner.stop();
|
|
411
|
-
const err = error;
|
|
412
|
-
if (err.message.includes('API key') || err.message.includes('unauthorized')) {
|
|
413
|
-
console.error(chalk.red('❌ Authentication failed:'), err.message);
|
|
414
|
-
console.log('');
|
|
415
|
-
console.log(chalk.yellow('💡 Solutions:'));
|
|
416
|
-
console.log(chalk.gray('• Run "paymongo login" to update your API keys'));
|
|
417
|
-
console.log(chalk.gray('• Check that your API keys are valid in the PayMongo dashboard'));
|
|
418
|
-
}
|
|
419
|
-
else if (err.message.includes('not found') || err.message.includes('404')) {
|
|
420
|
-
console.error(chalk.red('❌ Webhook not found:'), err.message);
|
|
421
|
-
console.log('');
|
|
422
|
-
console.log(chalk.yellow('💡 Check:'));
|
|
423
|
-
console.log(chalk.gray('• Verify the webhook ID is correct'));
|
|
424
|
-
console.log(chalk.gray('• Use "paymongo webhooks list" to see available webhooks'));
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
console.error(chalk.red('❌ Failed to delete webhook:'), err.message);
|
|
428
|
-
}
|
|
429
|
-
process.exit(1);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
export async function showAction(id) {
|
|
433
|
-
const spinner = new Spinner();
|
|
434
|
-
const configManager = new ConfigManager();
|
|
435
|
-
try {
|
|
436
|
-
spinner.start('Loading configuration...');
|
|
437
|
-
const config = await configManager.load();
|
|
438
|
-
if (!config) {
|
|
439
|
-
spinner.fail('No configuration found');
|
|
440
|
-
console.log(chalk.yellow('No PayMongo configuration found.'));
|
|
441
|
-
console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
spinner.succeed('Configuration loaded');
|
|
445
|
-
spinner.start('Fetching webhook details...');
|
|
446
|
-
const apiClient = new ApiClient({ config });
|
|
447
|
-
const webhook = await apiClient.getWebhook(id);
|
|
448
|
-
spinner.succeed('Webhook details loaded');
|
|
449
|
-
console.log('\n' + chalk.bold('Webhook Details'));
|
|
450
|
-
console.log('═'.repeat(50));
|
|
451
|
-
console.log(chalk.bold('ID:'), webhook.id);
|
|
452
|
-
console.log(chalk.bold('URL:'), webhook.attributes.url);
|
|
453
|
-
console.log(chalk.bold('Status:'), webhook.attributes.status);
|
|
454
|
-
console.log(chalk.bold('Created:'), new Date(webhook.attributes.created_at * 1000).toLocaleString());
|
|
455
|
-
console.log(chalk.bold('Updated:'), new Date(webhook.attributes.updated_at * 1000).toLocaleString());
|
|
456
|
-
console.log('');
|
|
457
|
-
console.log(chalk.bold('Events:'));
|
|
458
|
-
webhook.attributes.events.forEach((event) => {
|
|
459
|
-
console.log(` • ${event}`);
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
catch (error) {
|
|
463
|
-
spinner.stop();
|
|
464
|
-
const err = error;
|
|
465
|
-
if (err.message.includes('API key') || err.message.includes('unauthorized')) {
|
|
466
|
-
console.error(chalk.red('❌ Authentication failed:'), err.message);
|
|
467
|
-
console.log('');
|
|
468
|
-
console.log(chalk.yellow('💡 Solutions:'));
|
|
469
|
-
console.log(chalk.gray('• Run "paymongo login" to update your API keys'));
|
|
470
|
-
console.log(chalk.gray('• Check that your API keys are valid in the PayMongo dashboard'));
|
|
471
|
-
}
|
|
472
|
-
else if (err.message.includes('not found') || err.message.includes('404')) {
|
|
473
|
-
console.error(chalk.red('❌ Webhook not found:'), err.message);
|
|
474
|
-
console.log('');
|
|
475
|
-
console.log(chalk.yellow('💡 Check:'));
|
|
476
|
-
console.log(chalk.gray('• Verify the webhook ID is correct'));
|
|
477
|
-
console.log(chalk.gray('• Use "paymongo webhooks list" to see available webhooks'));
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
console.error(chalk.red('❌ Failed to get webhook details:'), err.message);
|
|
481
|
-
}
|
|
482
|
-
process.exit(1);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
2
|
+
import { createAction, deleteAction, exportAction, importAction, listAction, showAction, } from './webhooks/actions.js';
|
|
485
3
|
const command = new Command('webhooks')
|
|
486
4
|
.description('Manage PayMongo webhooks')
|
|
487
5
|
.addCommand(new Command('export')
|
|
@@ -514,14 +32,5 @@ const command = new Command('webhooks')
|
|
|
514
32
|
.description('Show webhook details')
|
|
515
33
|
.argument('<id>', 'Webhook ID to show')
|
|
516
34
|
.action(async (id) => showAction(id)));
|
|
35
|
+
export { exportAction, importAction, createAction, listAction, deleteAction, showAction };
|
|
517
36
|
export default command;
|
|
518
|
-
function getStatusColor(status) {
|
|
519
|
-
switch (status) {
|
|
520
|
-
case 'enabled':
|
|
521
|
-
return chalk.green;
|
|
522
|
-
case 'disabled':
|
|
523
|
-
return chalk.red;
|
|
524
|
-
default:
|
|
525
|
-
return chalk.white;
|
|
526
|
-
}
|
|
527
|
-
}
|
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: {
|