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