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,346 @@
1
+ import Table from 'cli-table3';
2
+ import chalk from 'chalk';
3
+ import { BulkOperations } from '../../utils/bulk.js';
4
+ import { createApiClient, createPaymentSimulator, createPaymentsContext, getStatusColor, handlePaymentsError, loadPaymentsConfig, parseBoundedInt, } from './helpers.js';
5
+ export async function exportAction(options) {
6
+ const { spinner, configManager } = createPaymentsContext();
7
+ try {
8
+ const config = await loadPaymentsConfig(spinner, configManager);
9
+ if (!config) {
10
+ return;
11
+ }
12
+ const limit = parseBoundedInt(options.limit, '100', 'Limit must be a number between 1 and 1000', (parsed) => parsed >= 1 && parsed <= 1000);
13
+ spinner.start(`Fetching up to ${limit} payments...`);
14
+ const payments = await createApiClient(config).listPayments(limit);
15
+ spinner.succeed(`Found ${payments.length} payments`);
16
+ if (payments.length === 0) {
17
+ console.log(chalk.yellow('No payments found to export.'));
18
+ return;
19
+ }
20
+ const filename = options.file
21
+ ? BulkOperations.ensureJsonExtension(options.file)
22
+ : BulkOperations.generateFilename('payments', config.environment);
23
+ spinner.start(`Exporting to ${filename}...`);
24
+ await BulkOperations.exportPayments(payments, filename, config.environment);
25
+ spinner.succeed('Export completed');
26
+ console.log('\n' + chalk.green('✅ Payments exported successfully!'));
27
+ console.log('');
28
+ console.log(`${chalk.bold('File:')} ${filename}`);
29
+ console.log(`${chalk.bold('Payments:')} ${payments.length}`);
30
+ console.log(`${chalk.bold('Environment:')} ${config.environment}`);
31
+ console.log(`${chalk.bold('Total size:')} ${payments.length} payments`);
32
+ }
33
+ catch (error) {
34
+ handlePaymentsError('❌ Failed to export payments:', spinner, error);
35
+ }
36
+ }
37
+ export async function importAction(filename, options) {
38
+ const { spinner } = createPaymentsContext();
39
+ try {
40
+ spinner.start(`Importing payments from ${filename}...`);
41
+ const { payments, metadata } = await BulkOperations.importPayments(filename);
42
+ spinner.succeed(`Loaded ${payments.length} payments from export`);
43
+ if (options.json) {
44
+ console.log(JSON.stringify({ payments, metadata }, null, 2));
45
+ return;
46
+ }
47
+ console.log('\n' + chalk.green('✅ Payments imported successfully!'));
48
+ console.log('');
49
+ console.log(`${chalk.bold('Source:')} ${filename}`);
50
+ console.log(`${chalk.bold('Payments:')} ${payments.length}`);
51
+ console.log(`${chalk.bold('Exported from:')} ${metadata.environment} environment`);
52
+ console.log(`${chalk.bold('Export date:')} ${new Date(metadata.exported_at).toLocaleString()}`);
53
+ console.log('\n' + chalk.yellow('⚠️ Important Notes:'));
54
+ console.log(chalk.gray('• Payment data imported for reference only'));
55
+ console.log(chalk.gray('• Actual payments cannot be recreated through the API'));
56
+ console.log(chalk.gray('• Use this for data analysis, migration planning, or testing'));
57
+ if (payments.length > 0) {
58
+ console.log('\n' + chalk.bold('Sample Payment IDs:'));
59
+ payments.slice(0, 5).forEach((payment, index) => {
60
+ const amount = (payment.attributes.amount / 100).toFixed(2);
61
+ console.log(` ${index + 1}. ${payment.id} - ₱${amount} ${payment.attributes.currency}`);
62
+ });
63
+ if (payments.length > 5) {
64
+ console.log(chalk.gray(` ... and ${payments.length - 5} more`));
65
+ }
66
+ }
67
+ }
68
+ catch (error) {
69
+ handlePaymentsError('❌ Failed to import payments:', spinner, error);
70
+ }
71
+ }
72
+ export async function listAction(options) {
73
+ const { spinner, configManager } = createPaymentsContext();
74
+ try {
75
+ const config = await loadPaymentsConfig(spinner, configManager);
76
+ if (!config) {
77
+ return;
78
+ }
79
+ spinner.start('Fetching payments...');
80
+ const payments = await createApiClient(config).listPayments(parseInt(options.limit || '10', 10));
81
+ spinner.succeed(`Found ${payments.length} payments`);
82
+ if (options.json) {
83
+ console.log(JSON.stringify(payments, null, 2));
84
+ return;
85
+ }
86
+ if (payments.length === 0) {
87
+ console.log(chalk.gray('No payments found.'));
88
+ return;
89
+ }
90
+ const table = new Table({
91
+ head: [
92
+ chalk.bold('ID'),
93
+ chalk.bold('Amount'),
94
+ chalk.bold('Status'),
95
+ chalk.bold('Created'),
96
+ chalk.bold('Description'),
97
+ ],
98
+ colWidths: [25, 12, 12, 12, 30],
99
+ style: {
100
+ head: [],
101
+ border: [],
102
+ },
103
+ });
104
+ payments.forEach((payment) => {
105
+ const amount = `₱${(payment.attributes.amount / 100).toFixed(2)}`;
106
+ const status = payment.attributes.status;
107
+ const created = new Date(payment.attributes.created_at * 1000).toLocaleDateString();
108
+ const description = payment.attributes.description || 'N/A';
109
+ table.push([
110
+ chalk.cyan(payment.id.substring(0, 20) + (payment.id.length > 20 ? '...' : '')),
111
+ chalk.yellow(amount),
112
+ getStatusColor(status)(status),
113
+ chalk.gray(created),
114
+ chalk.white(description.length > 25 ? description.substring(0, 22) + '...' : description),
115
+ ]);
116
+ });
117
+ console.log('\n' + chalk.bold('Recent Payments'));
118
+ console.log(chalk.gray('─'.repeat(95)));
119
+ console.log(table.toString());
120
+ console.log(chalk.gray(`Total: ${payments.length} payments`));
121
+ console.log('');
122
+ }
123
+ catch (error) {
124
+ handlePaymentsError('❌ Failed to fetch payments:', spinner, error);
125
+ }
126
+ }
127
+ export async function showAction(id, options) {
128
+ const { spinner, configManager } = createPaymentsContext();
129
+ try {
130
+ const config = await loadPaymentsConfig(spinner, configManager);
131
+ if (!config) {
132
+ return;
133
+ }
134
+ spinner.start('Fetching payment details...');
135
+ const payment = await createApiClient(config).getPayment(id);
136
+ spinner.succeed('Payment details loaded');
137
+ if (options.json) {
138
+ console.log(JSON.stringify(payment, null, 2));
139
+ return;
140
+ }
141
+ const attrs = payment.attributes;
142
+ const amount = (attrs.amount / 100).toFixed(2);
143
+ const fees = attrs.fees ? (attrs.fees / 100).toFixed(2) : '0.00';
144
+ const netAmount = attrs.net_amount ? (attrs.net_amount / 100).toFixed(2) : '0.00';
145
+ console.log('\n' + chalk.bold('Payment Details'));
146
+ console.log(chalk.gray('─'.repeat(50)));
147
+ console.log(`${chalk.bold('ID:')} ${payment.id}`);
148
+ console.log(`${chalk.bold('Amount:')} ₱${amount} ${attrs.currency}`);
149
+ console.log(`${chalk.bold('Status:')} ${getStatusColor(attrs.status)(attrs.status)}`);
150
+ console.log(`${chalk.bold('Description:')} ${attrs.description || 'N/A'}`);
151
+ console.log(`${chalk.bold('External Reference:')} ${attrs.external_reference_number || 'N/A'}`);
152
+ console.log(`${chalk.bold('Paid At:')} ${attrs.paid_at ? new Date(attrs.paid_at * 1000).toLocaleString() : 'N/A'}`);
153
+ console.log(`${chalk.bold('Created:')} ${new Date(attrs.created_at * 1000).toLocaleString()}`);
154
+ console.log(`${chalk.bold('Updated:')} ${new Date(attrs.updated_at * 1000).toLocaleString()}`);
155
+ if (attrs.fees) {
156
+ console.log(`${chalk.bold('Fees:')} ₱${fees}`);
157
+ console.log(`${chalk.bold('Net Amount:')} ₱${netAmount}`);
158
+ }
159
+ if (attrs.source) {
160
+ console.log(`${chalk.bold('Payment Method:')} ${attrs.source.attributes.type}`);
161
+ }
162
+ console.log(`${chalk.bold('Payment Intent:')} ${attrs.payment_intent_id}`);
163
+ console.log('');
164
+ console.log(chalk.gray(`View in dashboard: https://dashboard.paymongo.com/payments/${payment.id}`));
165
+ }
166
+ catch (error) {
167
+ handlePaymentsError('❌ Failed to fetch payment:', spinner, error);
168
+ }
169
+ }
170
+ export async function createIntentAction(options) {
171
+ const { spinner, configManager } = createPaymentsContext();
172
+ try {
173
+ const config = await loadPaymentsConfig(spinner, configManager);
174
+ if (!config) {
175
+ return;
176
+ }
177
+ const amount = parseBoundedInt(options.amount, '10000', 'Amount must be a positive number in centavos', (parsed) => parsed > 0);
178
+ spinner.start('Creating payment intent...');
179
+ const paymentIntent = await createApiClient(config).createPaymentIntent(amount, options.currency || 'PHP', options.description);
180
+ spinner.succeed('Payment intent created');
181
+ if (options.json) {
182
+ console.log(JSON.stringify(paymentIntent, null, 2));
183
+ return;
184
+ }
185
+ const attrs = paymentIntent.attributes;
186
+ console.log('\n' + chalk.bold('Payment Intent Created'));
187
+ console.log(chalk.gray('─'.repeat(50)));
188
+ console.log(`${chalk.bold('ID:')} ${paymentIntent.id}`);
189
+ console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
190
+ console.log(`${chalk.bold('Status:')} ${getStatusColor(attrs.status)(attrs.status)}`);
191
+ console.log(`${chalk.bold('Description:')} ${attrs.description || 'N/A'}`);
192
+ console.log(`${chalk.bold('Created:')} ${new Date(attrs.created_at * 1000).toLocaleString()}`);
193
+ console.log('');
194
+ console.log(chalk.gray('Use this ID to attach a payment method and confirm the payment.'));
195
+ }
196
+ catch (error) {
197
+ handlePaymentsError('❌ Failed to create payment intent:', spinner, error);
198
+ }
199
+ }
200
+ export async function confirmAction(intentId, options) {
201
+ const { spinner, configManager } = createPaymentsContext();
202
+ try {
203
+ const config = await loadPaymentsConfig(spinner, configManager);
204
+ if (!config) {
205
+ return;
206
+ }
207
+ if (options.simulate) {
208
+ if (!options.method) {
209
+ throw new Error('Payment method is required for simulation. Use --method <gcash|maya|grabpay>');
210
+ }
211
+ }
212
+ else if (!options.paymentMethod) {
213
+ throw new Error('Payment method ID is required. Use --payment-method <id>');
214
+ }
215
+ if (options.simulate) {
216
+ const validMethods = ['gcash', 'maya', 'grabpay'];
217
+ const validOutcomes = ['success', 'failure', 'timeout'];
218
+ if (!options.method || !validMethods.includes(options.method)) {
219
+ throw new Error(`Invalid or missing payment method for simulation. Must be one of: ${validMethods.join(', ')}`);
220
+ }
221
+ if (!validOutcomes.includes(options.outcome || 'success')) {
222
+ throw new Error(`Invalid simulation outcome. Must be one of: ${validOutcomes.join(', ')}`);
223
+ }
224
+ const delayMs = options.delay ? parseInt(options.delay, 10) : undefined;
225
+ if (options.delay && (delayMs === undefined || isNaN(delayMs) || delayMs <= 0)) {
226
+ throw new Error('Simulation delay must be a positive number in milliseconds');
227
+ }
228
+ console.log('\n' + chalk.bold('🧪 Payment Simulation Mode'));
229
+ console.log(chalk.gray('─'.repeat(50)));
230
+ console.log(`${chalk.bold('Method:')} ${options.method.toUpperCase()}`);
231
+ console.log(`${chalk.bold('Outcome:')} ${options.outcome}`);
232
+ console.log(`${chalk.bold('Delay:')} ${delayMs ? `${delayMs}ms` : 'Default for method/outcome'}`);
233
+ spinner.start(`Simulating ${options.method} payment...`);
234
+ const simulationOptions = {
235
+ paymentMethod: options.method,
236
+ outcome: (options.outcome || 'success'),
237
+ ...(delayMs !== undefined && { delayMs }),
238
+ };
239
+ const result = await createPaymentSimulator().simulatePaymentConfirmation(intentId, simulationOptions);
240
+ spinner.succeed(`Simulation completed (${result.delayApplied}ms)`);
241
+ if (options.json) {
242
+ console.log(JSON.stringify(result.paymentIntent, null, 2));
243
+ return;
244
+ }
245
+ const attrs = result.paymentIntent.attributes;
246
+ console.log('\n' + chalk.bold('Payment Intent Confirmed (Simulated)'));
247
+ console.log(chalk.gray('─'.repeat(50)));
248
+ console.log(`${chalk.bold('ID:')} ${result.paymentIntent.id}`);
249
+ console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
250
+ console.log(`${chalk.bold('Status:')} ${getStatusColor(attrs.status)(attrs.status)}`);
251
+ console.log(`${chalk.bold('Description:')} ${attrs.description || 'N/A'}`);
252
+ console.log(`${chalk.bold('Created:')} ${new Date(attrs.created_at * 1000).toLocaleString()}`);
253
+ console.log(`${chalk.bold('Updated:')} ${new Date(attrs.updated_at * 1000).toLocaleString()}`);
254
+ console.log('');
255
+ console.log(chalk.yellow('⚠️ This was a simulation - no real payment was processed'));
256
+ console.log(chalk.gray(`Simulation type: ${result.simulationType} (${result.delayApplied}ms delay)`));
257
+ return;
258
+ }
259
+ spinner.start('Confirming payment intent...');
260
+ const result = await createApiClient(config).confirmPaymentIntent(intentId, options.paymentMethod ?? '', options.returnUrl);
261
+ spinner.succeed('Payment intent confirmed');
262
+ if (options.json) {
263
+ console.log(JSON.stringify(result, null, 2));
264
+ return;
265
+ }
266
+ const attrs = result.attributes;
267
+ console.log('\n' + chalk.bold('Payment Intent Confirmed'));
268
+ console.log(chalk.gray('─'.repeat(50)));
269
+ console.log(`${chalk.bold('ID:')} ${result.id}`);
270
+ console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
271
+ console.log(`${chalk.bold('Status:')} ${getStatusColor(attrs.status)(attrs.status)}`);
272
+ console.log(`${chalk.bold('Description:')} ${attrs.description || 'N/A'}`);
273
+ console.log(`${chalk.bold('Created:')} ${new Date(attrs.created_at * 1000).toLocaleString()}`);
274
+ console.log(`${chalk.bold('Updated:')} ${new Date(attrs.updated_at * 1000).toLocaleString()}`);
275
+ console.log('');
276
+ console.log(chalk.gray(`Payment will be processed. Check status with: paymongo payments show-intent ${result.id}`));
277
+ }
278
+ catch (error) {
279
+ handlePaymentsError('❌ Failed to confirm payment intent:', spinner, error);
280
+ }
281
+ }
282
+ export async function captureAction(intentId, options) {
283
+ const { spinner, configManager } = createPaymentsContext();
284
+ try {
285
+ const config = await loadPaymentsConfig(spinner, configManager);
286
+ if (!config) {
287
+ return;
288
+ }
289
+ spinner.start('Capturing payment intent...');
290
+ const result = await createApiClient(config).capturePaymentIntent(intentId);
291
+ spinner.succeed('Payment intent captured');
292
+ if (options.json) {
293
+ console.log(JSON.stringify(result, null, 2));
294
+ return;
295
+ }
296
+ const attrs = result.attributes;
297
+ console.log('\n' + chalk.bold('Payment Intent Captured'));
298
+ console.log(chalk.gray('─'.repeat(50)));
299
+ console.log(`${chalk.bold('ID:')} ${result.id}`);
300
+ console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
301
+ console.log(`${chalk.bold('Status:')} ${getStatusColor(attrs.status)(attrs.status)}`);
302
+ console.log(`${chalk.bold('Description:')} ${attrs.description || 'N/A'}`);
303
+ console.log(`${chalk.bold('Updated:')} ${new Date(attrs.updated_at * 1000).toLocaleString()}`);
304
+ console.log('');
305
+ console.log(chalk.green('✅ Payment has been captured and will be settled'));
306
+ }
307
+ catch (error) {
308
+ handlePaymentsError('❌ Failed to capture payment intent:', spinner, error);
309
+ }
310
+ }
311
+ export async function refundAction(paymentId, options) {
312
+ const { spinner, configManager } = createPaymentsContext();
313
+ try {
314
+ const config = await loadPaymentsConfig(spinner, configManager);
315
+ if (!config) {
316
+ return;
317
+ }
318
+ const validReasons = ['duplicate', 'fraudulent', 'requested_by_customer'];
319
+ if (options.reason && !validReasons.includes(options.reason)) {
320
+ throw new Error(`Invalid reason. Must be one of: ${validReasons.join(', ')}`);
321
+ }
322
+ const amount = options.amount ? parseBoundedInt(options.amount, options.amount, 'Refund amount must be a positive number in centavos', (parsed) => parsed > 0) : undefined;
323
+ spinner.start('Creating refund...');
324
+ const refund = await createApiClient(config).createRefund(paymentId, amount, options.reason);
325
+ spinner.succeed('Refund created');
326
+ if (options.json) {
327
+ console.log(JSON.stringify(refund, null, 2));
328
+ return;
329
+ }
330
+ const attrs = refund.attributes;
331
+ console.log('\n' + chalk.bold('Refund Created'));
332
+ console.log(chalk.gray('─'.repeat(50)));
333
+ console.log(`${chalk.bold('ID:')} ${refund.id}`);
334
+ console.log(`${chalk.bold('Payment ID:')} ${attrs.payment_id}`);
335
+ console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
336
+ console.log(`${chalk.bold('Status:')} ${getStatusColor(attrs.status)(attrs.status)}`);
337
+ console.log(`${chalk.bold('Reason:')} ${attrs.reason || 'N/A'}`);
338
+ console.log(`${chalk.bold('Created:')} ${new Date(attrs.created_at * 1000).toLocaleString()}`);
339
+ console.log('');
340
+ console.log(chalk.yellow('⚠️ Refund processing may take a few minutes'));
341
+ console.log(chalk.gray(`Check status: paymongo payments show-refund ${refund.id}`));
342
+ }
343
+ catch (error) {
344
+ handlePaymentsError('❌ Failed to create refund:', spinner, error);
345
+ }
346
+ }
@@ -0,0 +1,62 @@
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 { PaymentSimulator } from '../../services/payments/simulator.js';
6
+ import { CommandError } from '../../utils/errors.js';
7
+ export function createPaymentsContext() {
8
+ return {
9
+ spinner: new Spinner(),
10
+ configManager: new ConfigManager(),
11
+ };
12
+ }
13
+ export function getStatusColor(status) {
14
+ switch (status) {
15
+ case 'paid':
16
+ case 'succeeded':
17
+ case 'processed':
18
+ return chalk.green;
19
+ case 'pending':
20
+ case 'awaiting_payment_method':
21
+ case 'awaiting_next_action':
22
+ case 'processing':
23
+ return chalk.yellow;
24
+ case 'failed':
25
+ return chalk.red;
26
+ case 'cancelled':
27
+ return chalk.gray;
28
+ default:
29
+ return chalk.white;
30
+ }
31
+ }
32
+ export async function loadPaymentsConfig(spinner, configManager) {
33
+ spinner.start('Loading configuration...');
34
+ const config = await configManager.load();
35
+ if (!config) {
36
+ spinner.fail('No configuration found');
37
+ console.log(chalk.yellow('No PayMongo configuration found.'));
38
+ console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
39
+ return null;
40
+ }
41
+ spinner.succeed('Configuration loaded');
42
+ return config;
43
+ }
44
+ export function createApiClient(config) {
45
+ return new ApiClient({ config });
46
+ }
47
+ export function createPaymentSimulator() {
48
+ return new PaymentSimulator();
49
+ }
50
+ export function handlePaymentsError(prefix, spinner, error) {
51
+ spinner.stop();
52
+ const err = error;
53
+ console.error(chalk.red(prefix), err.message);
54
+ throw new CommandError();
55
+ }
56
+ export function parseBoundedInt(value, fallback, errorMessage, validate) {
57
+ const parsed = parseInt(value || fallback, 10);
58
+ if (isNaN(parsed) || !validate(parsed)) {
59
+ throw new Error(errorMessage);
60
+ }
61
+ return parsed;
62
+ }