paymongo-cli 1.4.11 → 1.4.13

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +4 -3
  3. package/biome.json +72 -0
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/commands/config/actions.js +13 -5
  6. package/dist/commands/config/analytics.js +75 -0
  7. package/dist/commands/config/helpers.js +14 -25
  8. package/dist/commands/config/rate-limit.js +3 -3
  9. package/dist/commands/config.js +7 -1
  10. package/dist/commands/dev/logs.js +13 -4
  11. package/dist/commands/dev/status.js +1 -1
  12. package/dist/commands/dev/stop.js +2 -2
  13. package/dist/commands/dev.js +10 -258
  14. package/dist/commands/doctor.js +241 -0
  15. package/dist/commands/env.js +10 -19
  16. package/dist/commands/generate/templates/index.js +3 -3
  17. package/dist/commands/generate.js +6 -6
  18. package/dist/commands/init.js +22 -36
  19. package/dist/commands/login.js +18 -29
  20. package/dist/commands/payments/actions.js +15 -15
  21. package/dist/commands/payments/helpers.js +6 -24
  22. package/dist/commands/payments.js +1 -1
  23. package/dist/commands/shared/auth.js +23 -0
  24. package/dist/commands/shared/runtime.js +35 -0
  25. package/dist/commands/team/index.js +3 -3
  26. package/dist/commands/trigger/actions.js +2 -2
  27. package/dist/commands/trigger/helpers.js +13 -9
  28. package/dist/commands/trigger.js +2 -2
  29. package/dist/commands/webhooks/actions.js +11 -11
  30. package/dist/commands/webhooks/helpers.js +5 -23
  31. package/dist/commands/webhooks.js +1 -1
  32. package/dist/index.js +37 -14
  33. package/dist/services/analytics/service.js +3 -3
  34. package/dist/services/api/client.js +8 -4
  35. package/dist/services/config/manager.js +3 -3
  36. package/dist/services/dev/process-manager.js +4 -4
  37. package/dist/services/dev/server.js +4 -6
  38. package/dist/services/dev/session.js +353 -0
  39. package/dist/services/team/service.js +1 -1
  40. package/dist/utils/bulk.js +11 -11
  41. package/dist/utils/cache.js +5 -5
  42. package/dist/utils/constants.js +1 -1
  43. package/dist/utils/webhook-store.js +3 -3
  44. package/package.json +11 -25
  45. package/vitest.config.ts +18 -0
  46. package/eslint.config.ts +0 -70
@@ -0,0 +1,241 @@
1
+ import chalk from 'chalk';
2
+ import { Command } from 'commander';
3
+ import { ApiKeyError, CommandError, NetworkError, PayMongoError } from '../utils/errors.js';
4
+ import { validateApiKey, validateWebhookUrl } from '../utils/validator.js';
5
+ import { createApiClient, createCommandContext } from './shared/runtime.js';
6
+ function statusIcon(status) {
7
+ switch (status) {
8
+ case 'pass':
9
+ return chalk.green('✓');
10
+ case 'warn':
11
+ return chalk.yellow('⚠');
12
+ case 'fail':
13
+ return chalk.red('✖');
14
+ }
15
+ }
16
+ function printCheck(check) {
17
+ console.log(`${statusIcon(check.status)} ${chalk.bold(check.name)}: ${check.message}`);
18
+ if (check.fix) {
19
+ console.log(chalk.gray(` Fix: ${check.fix}`));
20
+ }
21
+ }
22
+ function hasNgrokToken() {
23
+ const token = process.env.NGROK_AUTHTOKEN;
24
+ return typeof token === 'string' && token.trim().length > 0;
25
+ }
26
+ async function runDoctor(options) {
27
+ const { configManager } = createCommandContext();
28
+ const checks = [];
29
+ const config = await configManager.load();
30
+ if (!config) {
31
+ checks.push({
32
+ name: 'Configuration',
33
+ status: 'fail',
34
+ message: 'No .paymongo configuration found.',
35
+ fix: 'Run `paymongo init` to create project configuration.',
36
+ });
37
+ return checks;
38
+ }
39
+ checks.push({
40
+ name: 'Configuration',
41
+ status: 'pass',
42
+ message: `Loaded configuration for project "${config.projectName}".`,
43
+ });
44
+ const env = config.environment;
45
+ const envKeys = config.apiKeys[env];
46
+ if (!envKeys?.secret) {
47
+ checks.push({
48
+ name: 'Secret API Key',
49
+ status: 'fail',
50
+ message: `No secret key configured for ${env} environment.`,
51
+ fix: `Run \`paymongo config set apiKeys.${env}.secret YOUR_SECRET_KEY\`.`,
52
+ });
53
+ }
54
+ else if (!validateApiKey(envKeys.secret, 'secret')) {
55
+ checks.push({
56
+ name: 'Secret API Key',
57
+ status: 'fail',
58
+ message: `Secret key format is invalid for ${env} environment.`,
59
+ fix: 'Use a valid PayMongo secret key starting with `sk_test_` or `sk_live_`.',
60
+ });
61
+ }
62
+ else {
63
+ checks.push({
64
+ name: 'Secret API Key',
65
+ status: 'pass',
66
+ message: `Secret key format looks valid for ${env} environment.`,
67
+ });
68
+ }
69
+ if (!envKeys?.public) {
70
+ checks.push({
71
+ name: 'Public API Key',
72
+ status: 'warn',
73
+ message: `No public key configured for ${env} environment.`,
74
+ fix: `Run \`paymongo config set apiKeys.${env}.public YOUR_PUBLIC_KEY\` if your integration needs it.`,
75
+ });
76
+ }
77
+ else if (!validateApiKey(envKeys.public, 'public')) {
78
+ checks.push({
79
+ name: 'Public API Key',
80
+ status: 'fail',
81
+ message: `Public key format is invalid for ${env} environment.`,
82
+ fix: 'Use a valid PayMongo public key starting with `pk_test_` or `pk_live_`.',
83
+ });
84
+ }
85
+ else {
86
+ checks.push({
87
+ name: 'Public API Key',
88
+ status: 'pass',
89
+ message: `Public key format looks valid for ${env} environment.`,
90
+ });
91
+ }
92
+ if (config.webhooks.url) {
93
+ if (validateWebhookUrl(config.webhooks.url)) {
94
+ checks.push({
95
+ name: 'Webhook URL',
96
+ status: 'pass',
97
+ message: `Webhook URL is valid: ${config.webhooks.url}`,
98
+ });
99
+ }
100
+ else {
101
+ checks.push({
102
+ name: 'Webhook URL',
103
+ status: 'fail',
104
+ message: `Webhook URL is invalid: ${config.webhooks.url}`,
105
+ fix: 'Set a valid HTTPS or localhost webhook URL.',
106
+ });
107
+ }
108
+ }
109
+ if (config.dev.autoRegisterWebhook) {
110
+ if (hasNgrokToken()) {
111
+ checks.push({
112
+ name: 'ngrok Token',
113
+ status: 'pass',
114
+ message: 'NGROK_AUTHTOKEN is configured for `paymongo dev`.',
115
+ });
116
+ }
117
+ else {
118
+ checks.push({
119
+ name: 'ngrok Token',
120
+ status: 'warn',
121
+ message: '`paymongo dev` auto-registration may fail because NGROK_AUTHTOKEN is not set.',
122
+ fix: 'Set `NGROK_AUTHTOKEN` or use `paymongo dev --ngrok-token YOUR_TOKEN`.',
123
+ });
124
+ }
125
+ }
126
+ const secretCount = Object.keys(config.webhookSecrets || {}).length;
127
+ if (config.dev.verifyWebhookSignatures) {
128
+ if (secretCount > 0) {
129
+ checks.push({
130
+ name: 'Webhook Signatures',
131
+ status: 'pass',
132
+ message: `Signature verification is enabled and ${secretCount} webhook secret(s) are stored.`,
133
+ });
134
+ }
135
+ else {
136
+ checks.push({
137
+ name: 'Webhook Signatures',
138
+ status: 'warn',
139
+ message: 'Signature verification is enabled, but no webhook secrets are stored yet.',
140
+ fix: 'Create or auto-register a webhook so PayMongo returns and stores a webhook secret.',
141
+ });
142
+ }
143
+ }
144
+ else {
145
+ checks.push({
146
+ name: 'Webhook Signatures',
147
+ status: 'warn',
148
+ message: 'Signature verification is disabled.',
149
+ fix: 'Enable it with `paymongo config set dev.verifySignatures true` for safer local testing.',
150
+ });
151
+ }
152
+ const registeredCount = config.registeredWebhooks?.length || 0;
153
+ checks.push({
154
+ name: 'Registered Webhooks',
155
+ status: registeredCount > 0 ? 'pass' : 'warn',
156
+ message: registeredCount > 0
157
+ ? `${registeredCount} project-managed webhook(s) tracked for cleanup.`
158
+ : 'No project-managed webhooks are currently tracked.',
159
+ });
160
+ if (options.network !== false && envKeys?.secret && validateApiKey(envKeys.secret, 'secret')) {
161
+ const apiClient = createApiClient(config);
162
+ try {
163
+ await apiClient.validateApiKey();
164
+ checks.push({
165
+ name: 'PayMongo API',
166
+ status: 'pass',
167
+ message: `Secret API key authenticated successfully against the ${env} environment.`,
168
+ });
169
+ }
170
+ catch (error) {
171
+ const err = error;
172
+ let message = err.message;
173
+ let fix;
174
+ if (error instanceof ApiKeyError) {
175
+ message = 'API authentication failed.';
176
+ fix = 'Check your configured secret key in the PayMongo dashboard.';
177
+ }
178
+ else if (error instanceof NetworkError) {
179
+ message = 'Could not reach the PayMongo API.';
180
+ fix = 'Check your internet connection and firewall settings.';
181
+ }
182
+ else if (error instanceof PayMongoError && error.statusCode === 429) {
183
+ message = 'PayMongo API rate limit reached during validation.';
184
+ fix = 'Wait briefly, then rerun `paymongo doctor`.';
185
+ }
186
+ const check = {
187
+ name: 'PayMongo API',
188
+ status: 'fail',
189
+ message,
190
+ };
191
+ if (fix !== undefined) {
192
+ check.fix = fix;
193
+ }
194
+ checks.push(check);
195
+ }
196
+ }
197
+ else if (options.network === false) {
198
+ checks.push({
199
+ name: 'PayMongo API',
200
+ status: 'warn',
201
+ message: 'Skipped live API validation because `--no-network` was used.',
202
+ });
203
+ }
204
+ return checks;
205
+ }
206
+ export async function doctorAction(options) {
207
+ try {
208
+ const checks = await runDoctor(options);
209
+ if (options.json) {
210
+ console.log(JSON.stringify({ checks }, null, 2));
211
+ const hasFailure = checks.some((check) => check.status === 'fail');
212
+ if (hasFailure) {
213
+ throw new CommandError();
214
+ }
215
+ return;
216
+ }
217
+ console.log(chalk.bold('\nPayMongo CLI Doctor'));
218
+ console.log(chalk.gray('─'.repeat(50)));
219
+ checks.forEach(printCheck);
220
+ const failed = checks.filter((check) => check.status === 'fail').length;
221
+ const warned = checks.filter((check) => check.status === 'warn').length;
222
+ console.log('');
223
+ console.log(`${chalk.bold('Summary:')} ${chalk.red(failed.toString())} failed, ${chalk.yellow(warned.toString())} warning(s)`);
224
+ if (failed > 0) {
225
+ throw new CommandError();
226
+ }
227
+ }
228
+ catch (error) {
229
+ if (error instanceof CommandError) {
230
+ throw error;
231
+ }
232
+ console.error(chalk.red('❌ Failed to run doctor:'), error.message);
233
+ throw new CommandError();
234
+ }
235
+ }
236
+ const command = new Command('doctor')
237
+ .description('Run diagnostics for local PayMongo integration setup')
238
+ .option('-j, --json', 'Output checks as JSON')
239
+ .option('--no-network', 'Skip live PayMongo API validation')
240
+ .action(async (options) => doctorAction(options));
241
+ export default command;
@@ -1,9 +1,7 @@
1
- import { Command } from 'commander';
2
1
  import chalk from 'chalk';
3
- import ConfigManager from '../services/config/manager.js';
4
- import ApiClient from '../services/api/client.js';
5
- import Spinner from '../utils/spinner.js';
6
- import { ApiKeyError, NetworkError, PayMongoError, CommandError } from '../utils/errors.js';
2
+ import { Command } from 'commander';
3
+ import { ApiKeyError, CommandError, NetworkError, PayMongoError } from '../utils/errors.js';
4
+ import { createApiClient, createCommandContext, loadCommandConfig, showNoConfigMessage, } from './shared/runtime.js';
7
5
  const command = new Command('env');
8
6
  command
9
7
  .description('Manage PayMongo environments')
@@ -12,22 +10,16 @@ command
12
10
  .arguments('<environment>')
13
11
  .option('-f, --force', 'Skip API key validation')
14
12
  .action(async (environment, options) => {
15
- const spinner = new Spinner();
16
- const configManager = new ConfigManager();
13
+ const { spinner, configManager } = createCommandContext();
17
14
  try {
18
15
  if (!['test', 'live'].includes(environment)) {
19
16
  console.error(chalk.red('❌ Invalid environment. Must be "test" or "live"'));
20
17
  throw new CommandError();
21
18
  }
22
- spinner.start('Loading configuration...');
23
- const config = await configManager.load();
19
+ const config = await loadCommandConfig(spinner, configManager);
24
20
  if (!config) {
25
- spinner.fail('No configuration found');
26
- console.log(chalk.yellow('No PayMongo configuration found.'));
27
- console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
28
21
  return;
29
22
  }
30
- spinner.succeed('Configuration loaded');
31
23
  const envConfig = config.apiKeys[environment];
32
24
  if (!envConfig?.secret || !envConfig?.public) {
33
25
  spinner.fail(`Missing API keys for ${environment} environment`);
@@ -41,7 +33,7 @@ command
41
33
  if (!options.force) {
42
34
  spinner.start('Validating API keys...');
43
35
  const testConfig = { ...config, environment: environment };
44
- const apiClient = new ApiClient({ config: testConfig });
36
+ const apiClient = createApiClient(testConfig);
45
37
  try {
46
38
  await apiClient.validateApiKey();
47
39
  spinner.succeed('API keys validated');
@@ -102,20 +94,19 @@ command
102
94
  }
103
95
  }))
104
96
  .addCommand(new Command('current').description('Show current environment').action(async () => {
105
- const configManager = new ConfigManager();
97
+ const { configManager } = createCommandContext();
106
98
  try {
107
99
  const config = await configManager.load();
108
100
  if (!config) {
109
- console.log(chalk.yellow('No PayMongo configuration found.'));
110
- console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
101
+ showNoConfigMessage();
111
102
  return;
112
103
  }
113
104
  const env = config.environment;
114
105
  const envConfig = config.apiKeys[env];
115
106
  console.log(chalk.bold('Current Environment:'));
116
107
  console.log(`Environment: ${chalk.cyan(env.toUpperCase())}`);
117
- console.log(`Public Key: ${envConfig?.public ? chalk.gray(envConfig.public.substring(0, 10) + '...') : chalk.red('Not set')}`);
118
- console.log(`Secret Key: ${envConfig?.secret ? chalk.gray(envConfig.secret.substring(0, 10) + '...') : chalk.red('Not set')}`);
108
+ console.log(`Public Key: ${envConfig?.public ? chalk.gray(`${envConfig.public.substring(0, 10)}...`) : chalk.red('Not set')}`);
109
+ console.log(`Secret Key: ${envConfig?.secret ? chalk.gray(`${envConfig.secret.substring(0, 10)}...`) : chalk.red('Not set')}`);
119
110
  if (env === 'live') {
120
111
  console.log('');
121
112
  console.log(chalk.yellow('⚠️ You are using LIVE environment!'));
@@ -1,5 +1,5 @@
1
- export { getWebhookHandlerTemplate as getJavaScriptWebhookHandler } from './webhook-handler/javascript.js';
2
- export { getWebhookHandlerTemplate as getTypeScriptWebhookHandler } from './webhook-handler/typescript.js';
1
+ export { getCheckoutPageTemplate, getHtmlTemplate, getReactTemplate, getVueTemplate, } from './checkout-page/index.js';
3
2
  export { getPaymentIntentTemplate as getJavaScriptPaymentIntent } from './payment-intent/javascript.js';
4
3
  export { getPaymentIntentTemplate as getTypeScriptPaymentIntent } from './payment-intent/typescript.js';
5
- export { getCheckoutPageTemplate, getHtmlTemplate, getReactTemplate, getVueTemplate, } from './checkout-page/index.js';
4
+ export { getWebhookHandlerTemplate as getJavaScriptWebhookHandler } from './webhook-handler/javascript.js';
5
+ export { getWebhookHandlerTemplate as getTypeScriptWebhookHandler } from './webhook-handler/typescript.js';
@@ -1,9 +1,9 @@
1
- import { Command } from 'commander';
1
+ import fs from 'node:fs/promises';
2
2
  import chalk from 'chalk';
3
- import fs from 'fs/promises';
3
+ import { Command } from 'commander';
4
4
  import ConfigManager from '../services/config/manager.js';
5
5
  import Spinner from '../utils/spinner.js';
6
- import { getJavaScriptWebhookHandler, getTypeScriptWebhookHandler, getJavaScriptPaymentIntent, getTypeScriptPaymentIntent, getCheckoutPageTemplate, } from './generate/templates/index.js';
6
+ import { getCheckoutPageTemplate, getJavaScriptPaymentIntent, getJavaScriptWebhookHandler, getTypeScriptPaymentIntent, getTypeScriptWebhookHandler, } from './generate/templates/index.js';
7
7
  const command = new Command('generate');
8
8
  command
9
9
  .description('Generate boilerplate code for PayMongo integrations')
@@ -126,7 +126,7 @@ async function generateWebhookHandler(options) {
126
126
  spinner.start(`Generating webhook handler...`);
127
127
  await fs.writeFile(outputFile, code, 'utf-8');
128
128
  spinner.succeed(`Webhook handler generated: ${outputFile}`);
129
- console.log('\n' + chalk.green('✅ Webhook handler generated successfully!'));
129
+ console.log(`\n${chalk.green('✅ Webhook handler generated successfully!')}`);
130
130
  console.log(chalk.gray(`Events handled: ${events.join(', ')}`));
131
131
  console.log(chalk.gray(`Language: ${options.language}`));
132
132
  console.log(chalk.gray(`Framework: ${options.framework}`));
@@ -162,7 +162,7 @@ async function generatePaymentIntent(options) {
162
162
  spinner.start(`Generating payment intent code...`);
163
163
  await fs.writeFile(outputFile, code, 'utf-8');
164
164
  spinner.succeed(`Payment intent code generated: ${outputFile}`);
165
- console.log('\n' + chalk.green('✅ Payment intent code generated successfully!'));
165
+ console.log(`\n${chalk.green('✅ Payment intent code generated successfully!')}`);
166
166
  console.log(chalk.gray(`Payment methods: ${methods.join(', ')}`));
167
167
  console.log(chalk.gray(`Language: ${options.language}`));
168
168
  }
@@ -187,7 +187,7 @@ async function generateCheckoutPage(options) {
187
187
  spinner.start(`Generating checkout page...`);
188
188
  await fs.writeFile(outputFile, code, 'utf-8');
189
189
  spinner.succeed(`Checkout page generated: ${outputFile}`);
190
- console.log('\n' + chalk.green('✅ Checkout page generated successfully!'));
190
+ console.log(`\n${chalk.green('✅ Checkout page generated successfully!')}`);
191
191
  console.log(chalk.gray(`Framework: ${options.language}`));
192
192
  }
193
193
  catch (error) {
@@ -1,15 +1,13 @@
1
- import { Command } from 'commander';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
4
3
  import chalk from 'chalk';
5
- import ConfigManager from '../services/config/manager.js';
6
- import ApiClient from '../services/api/client.js';
4
+ import { Command } from 'commander';
5
+ import { ApiKeyError, CommandError, NetworkError, PayMongoError } from '../utils/errors.js';
7
6
  import { validateApiKey } from '../utils/validator.js';
8
- import Spinner from '../utils/spinner.js';
9
- import { ApiKeyError, NetworkError, PayMongoError, CommandError } from '../utils/errors.js';
7
+ import { createCredentialValidationConfig } from './shared/auth.js';
8
+ import { createApiClient, createCommandContext } from './shared/runtime.js';
10
9
  export async function initAction(options) {
11
- const spinner = new Spinner();
12
- const configManager = new ConfigManager();
10
+ const { spinner, configManager } = createCommandContext();
13
11
  try {
14
12
  if (await configManager.exists()) {
15
13
  const { confirm } = await import('@inquirer/prompts');
@@ -107,28 +105,16 @@ export async function initAction(options) {
107
105
  };
108
106
  }
109
107
  spinner.start('Validating API keys...');
110
- const tempConfig = {
111
- version: '1.0',
108
+ const tempConfig = createCredentialValidationConfig({
112
109
  projectName: answers.projectName,
113
110
  environment: answers.environment,
114
- apiKeys: {
115
- [answers.environment]: {
116
- public: answers.publicKey,
117
- secret: answers.secretKey,
118
- },
119
- },
120
- webhooks: {
121
- url: answers.webhookUrl || `http://localhost:${answers.port}/webhook`,
122
- events: answers.events,
123
- },
124
- webhookSecrets: {},
125
- dev: {
126
- port: answers.port,
127
- autoRegisterWebhook: true,
128
- verifyWebhookSignatures: true,
129
- },
130
- };
131
- const apiClient = new ApiClient({ config: tempConfig });
111
+ publicKey: answers.publicKey,
112
+ secretKey: answers.secretKey,
113
+ webhookUrl: answers.webhookUrl || `http://localhost:${answers.port}/webhook`,
114
+ events: answers.events,
115
+ port: answers.port,
116
+ });
117
+ const apiClient = createApiClient(tempConfig);
132
118
  try {
133
119
  await apiClient.validateApiKey();
134
120
  spinner.succeed('API keys validated');
@@ -191,19 +177,19 @@ PAYMONGO_ENVIRONMENT=${answers.environment}
191
177
  if (needsPaymongo) {
192
178
  lines.push('.paymongo');
193
179
  }
194
- gitignoreContent += lines.join('\n') + '\n';
180
+ gitignoreContent += `${lines.join('\n')}\n`;
195
181
  fs.writeFileSync(gitignorePath, gitignoreContent);
196
182
  console.log(chalk.green('✓ Added .env and .paymongo to .gitignore'));
197
183
  }
198
- console.log('\n' + chalk.green('🎉 PayMongo project initialized!'));
199
- console.log('\n' + chalk.bold('Configuration saved to .paymongo'));
184
+ console.log(`\n${chalk.green('🎉 PayMongo project initialized!')}`);
185
+ console.log(`\n${chalk.bold('Configuration saved to .paymongo')}`);
200
186
  console.log(chalk.bold('Environment variables saved to .env'));
201
- console.log('\n' + chalk.bold('Next steps:'));
202
- console.log(' 1. Run ' + chalk.cyan('paymongo dev') + ' to start development server');
187
+ console.log(`\n${chalk.bold('Next steps:')}`);
188
+ console.log(` 1. Run ${chalk.cyan('paymongo dev')} to start development server`);
203
189
  console.log(' 2. Configure your webhook handler at ' +
204
190
  chalk.cyan(`http://localhost:${answers.port}/webhook`));
205
- console.log(' 3. Visit ' + chalk.cyan('https://dashboard.paymongo.com') + ' to view transactions');
206
- console.log('\n' + chalk.yellow('Happy building! 🚀'));
191
+ console.log(` 3. Visit ${chalk.cyan('https://dashboard.paymongo.com')} to view transactions`);
192
+ console.log(`\n${chalk.yellow('Happy building! 🚀')}`);
207
193
  }
208
194
  catch (error) {
209
195
  spinner.stop();
@@ -1,14 +1,13 @@
1
- import { Command } from 'commander';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as crypto from 'crypto';
5
- import * as os from 'os';
1
+ import * as crypto from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
6
5
  import chalk from 'chalk';
7
- import ConfigManager from '../services/config/manager.js';
8
- import ApiClient from '../services/api/client.js';
6
+ import { Command } from 'commander';
7
+ import { ApiKeyError, CommandError, NetworkError, PayMongoError } from '../utils/errors.js';
9
8
  import { validateApiKey } from '../utils/validator.js';
10
- import { ApiKeyError, NetworkError, PayMongoError, CommandError } from '../utils/errors.js';
11
- import Spinner from '../utils/spinner.js';
9
+ import { createCredentialValidationConfig } from './shared/auth.js';
10
+ import { createApiClient, createCommandContext } from './shared/runtime.js';
12
11
  class CredentialManager {
13
12
  credentialsPath;
14
13
  encryptionKey;
@@ -105,8 +104,7 @@ command
105
104
  .option('-e, --env <environment>', 'Environment (test or live)', 'test')
106
105
  .option('--logout', 'Clear stored credentials')
107
106
  .action(async (options) => {
108
- const spinner = new Spinner();
109
- const configManager = new ConfigManager();
107
+ const { spinner, configManager } = createCommandContext();
110
108
  const credentialManager = new CredentialManager();
111
109
  try {
112
110
  if (options.logout) {
@@ -171,21 +169,12 @@ command
171
169
  };
172
170
  }
173
171
  spinner.start('Validating API key...');
174
- const tempConfig = {
175
- version: '1.0',
176
- projectName: 'temp',
172
+ const tempConfig = createCredentialValidationConfig({
177
173
  environment: answers.environment,
178
- apiKeys: {
179
- [answers.environment]: {
180
- public: answers.publicKey || '',
181
- secret: answers.secretKey,
182
- },
183
- },
184
- webhooks: { url: '', events: [] },
185
- webhookSecrets: {},
186
- dev: { port: 3000, autoRegisterWebhook: true, verifyWebhookSignatures: true },
187
- };
188
- const apiClient = new ApiClient({ config: tempConfig });
174
+ publicKey: answers.publicKey || '',
175
+ secretKey: answers.secretKey,
176
+ });
177
+ const apiClient = createApiClient(tempConfig);
189
178
  try {
190
179
  await apiClient.validateApiKey();
191
180
  spinner.succeed('API key validated');
@@ -244,14 +233,14 @@ command
244
233
  };
245
234
  await configManager.save(config);
246
235
  }
247
- console.log('\n' + chalk.green('🔐 PayMongo Login Successful'));
248
- console.log('\n' + chalk.bold('Current configuration:'));
236
+ console.log(`\n${chalk.green('🔐 PayMongo Login Successful')}`);
237
+ console.log(`\n${chalk.bold('Current configuration:')}`);
249
238
  console.log(` Environment: ${answers.environment}`);
250
239
  console.log(` Secret Key: ${'*'.repeat(20)}...${answers.secretKey.slice(-4)}`);
251
240
  if (answers.publicKey) {
252
241
  console.log(` Public Key: ${'*'.repeat(20)}...${answers.publicKey.slice(-4)}`);
253
242
  }
254
- console.log('\n' + chalk.gray("Use 'paymongo config show' to view settings"));
243
+ console.log(`\n${chalk.gray("Use 'paymongo config show' to view settings")}`);
255
244
  console.log(chalk.gray("Use 'paymongo login --logout' to clear credentials"));
256
245
  }
257
246
  catch (error) {
@@ -288,4 +277,4 @@ command
288
277
  throw new CommandError();
289
278
  }
290
279
  });
291
- export { command, CredentialManager };
280
+ export { CredentialManager, command };
@@ -1,5 +1,5 @@
1
- import Table from 'cli-table3';
2
1
  import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
3
  import { BulkOperations } from '../../utils/bulk.js';
4
4
  import { createApiClient, createPaymentSimulator, createPaymentsContext, getStatusColor, handlePaymentsError, loadPaymentsConfig, parseBoundedInt, } from './helpers.js';
5
5
  export async function exportAction(options) {
@@ -23,7 +23,7 @@ export async function exportAction(options) {
23
23
  spinner.start(`Exporting to ${filename}...`);
24
24
  await BulkOperations.exportPayments(payments, filename, config.environment);
25
25
  spinner.succeed('Export completed');
26
- console.log('\n' + chalk.green('✅ Payments exported successfully!'));
26
+ console.log(`\n${chalk.green('✅ Payments exported successfully!')}`);
27
27
  console.log('');
28
28
  console.log(`${chalk.bold('File:')} ${filename}`);
29
29
  console.log(`${chalk.bold('Payments:')} ${payments.length}`);
@@ -44,18 +44,18 @@ export async function importAction(filename, options) {
44
44
  console.log(JSON.stringify({ payments, metadata }, null, 2));
45
45
  return;
46
46
  }
47
- console.log('\n' + chalk.green('✅ Payments imported successfully!'));
47
+ console.log(`\n${chalk.green('✅ Payments imported successfully!')}`);
48
48
  console.log('');
49
49
  console.log(`${chalk.bold('Source:')} ${filename}`);
50
50
  console.log(`${chalk.bold('Payments:')} ${payments.length}`);
51
51
  console.log(`${chalk.bold('Exported from:')} ${metadata.environment} environment`);
52
52
  console.log(`${chalk.bold('Export date:')} ${new Date(metadata.exported_at).toLocaleString()}`);
53
- console.log('\n' + chalk.yellow('⚠️ Important Notes:'));
53
+ console.log(`\n${chalk.yellow('⚠️ Important Notes:')}`);
54
54
  console.log(chalk.gray('• Payment data imported for reference only'));
55
55
  console.log(chalk.gray('• Actual payments cannot be recreated through the API'));
56
56
  console.log(chalk.gray('• Use this for data analysis, migration planning, or testing'));
57
57
  if (payments.length > 0) {
58
- console.log('\n' + chalk.bold('Sample Payment IDs:'));
58
+ console.log(`\n${chalk.bold('Sample Payment IDs:')}`);
59
59
  payments.slice(0, 5).forEach((payment, index) => {
60
60
  const amount = (payment.attributes.amount / 100).toFixed(2);
61
61
  console.log(` ${index + 1}. ${payment.id} - ₱${amount} ${payment.attributes.currency}`);
@@ -111,10 +111,10 @@ export async function listAction(options) {
111
111
  chalk.yellow(amount),
112
112
  getStatusColor(status)(status),
113
113
  chalk.gray(created),
114
- chalk.white(description.length > 25 ? description.substring(0, 22) + '...' : description),
114
+ chalk.white(description.length > 25 ? `${description.substring(0, 22)}...` : description),
115
115
  ]);
116
116
  });
117
- console.log('\n' + chalk.bold('Recent Payments'));
117
+ console.log(`\n${chalk.bold('Recent Payments')}`);
118
118
  console.log(chalk.gray('─'.repeat(95)));
119
119
  console.log(table.toString());
120
120
  console.log(chalk.gray(`Total: ${payments.length} payments`));
@@ -142,7 +142,7 @@ export async function showAction(id, options) {
142
142
  const amount = (attrs.amount / 100).toFixed(2);
143
143
  const fees = attrs.fees ? (attrs.fees / 100).toFixed(2) : '0.00';
144
144
  const netAmount = attrs.net_amount ? (attrs.net_amount / 100).toFixed(2) : '0.00';
145
- console.log('\n' + chalk.bold('Payment Details'));
145
+ console.log(`\n${chalk.bold('Payment Details')}`);
146
146
  console.log(chalk.gray('─'.repeat(50)));
147
147
  console.log(`${chalk.bold('ID:')} ${payment.id}`);
148
148
  console.log(`${chalk.bold('Amount:')} ₱${amount} ${attrs.currency}`);
@@ -183,7 +183,7 @@ export async function createIntentAction(options) {
183
183
  return;
184
184
  }
185
185
  const attrs = paymentIntent.attributes;
186
- console.log('\n' + chalk.bold('Payment Intent Created'));
186
+ console.log(`\n${chalk.bold('Payment Intent Created')}`);
187
187
  console.log(chalk.gray('─'.repeat(50)));
188
188
  console.log(`${chalk.bold('ID:')} ${paymentIntent.id}`);
189
189
  console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
@@ -222,10 +222,10 @@ export async function attachAction(intentId, options) {
222
222
  throw new Error(`Invalid simulation outcome. Must be one of: ${validOutcomes.join(', ')}`);
223
223
  }
224
224
  const delayMs = options.delay ? parseInt(options.delay, 10) : undefined;
225
- if (options.delay && (delayMs === undefined || isNaN(delayMs) || delayMs <= 0)) {
225
+ if (options.delay && (delayMs === undefined || Number.isNaN(delayMs) || delayMs <= 0)) {
226
226
  throw new Error('Simulation delay must be a positive number in milliseconds');
227
227
  }
228
- console.log('\n' + chalk.bold('🧪 Payment Simulation Mode'));
228
+ console.log(`\n${chalk.bold('🧪 Payment Simulation Mode')}`);
229
229
  console.log(chalk.gray('─'.repeat(50)));
230
230
  console.log(`${chalk.bold('Method:')} ${options.method.toUpperCase()}`);
231
231
  console.log(`${chalk.bold('Outcome:')} ${options.outcome}`);
@@ -243,7 +243,7 @@ export async function attachAction(intentId, options) {
243
243
  return;
244
244
  }
245
245
  const attrs = result.paymentIntent.attributes;
246
- console.log('\n' + chalk.bold('Payment Intent Confirmed (Simulated)'));
246
+ console.log(`\n${chalk.bold('Payment Intent Confirmed (Simulated)')}`);
247
247
  console.log(chalk.gray('─'.repeat(50)));
248
248
  console.log(`${chalk.bold('ID:')} ${result.paymentIntent.id}`);
249
249
  console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
@@ -264,7 +264,7 @@ export async function attachAction(intentId, options) {
264
264
  return;
265
265
  }
266
266
  const attrs = result.attributes;
267
- console.log('\n' + chalk.bold('Payment Method Attached'));
267
+ console.log(`\n${chalk.bold('Payment Method Attached')}`);
268
268
  console.log(chalk.gray('─'.repeat(50)));
269
269
  console.log(`${chalk.bold('ID:')} ${result.id}`);
270
270
  console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
@@ -295,7 +295,7 @@ export async function captureAction(intentId, options) {
295
295
  return;
296
296
  }
297
297
  const attrs = result.attributes;
298
- console.log('\n' + chalk.bold('Payment Intent Captured'));
298
+ console.log(`\n${chalk.bold('Payment Intent Captured')}`);
299
299
  console.log(chalk.gray('─'.repeat(50)));
300
300
  console.log(`${chalk.bold('ID:')} ${result.id}`);
301
301
  console.log(`${chalk.bold('Amount:')} ₱${(attrs.amount / 100).toFixed(2)} ${attrs.currency}`);
@@ -331,7 +331,7 @@ export async function refundAction(paymentId, options) {
331
331
  return;
332
332
  }
333
333
  const attrs = refund.attributes;
334
- console.log('\n' + chalk.bold('Refund Created'));
334
+ console.log(`\n${chalk.bold('Refund Created')}`);
335
335
  console.log(chalk.gray('─'.repeat(50)));
336
336
  console.log(`${chalk.bold('ID:')} ${refund.id}`);
337
337
  console.log(`${chalk.bold('Payment ID:')} ${attrs.payment_id}`);