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.
- package/AGENTS.md +8 -6
- package/CHANGELOG.md +19 -0
- 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 -566
- package/dist/commands/dev.js +4 -0
- package/dist/commands/init.js +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/payments/actions.js +346 -0
- package/dist/commands/payments/helpers.js +62 -0
- package/dist/commands/payments.js +2 -459
- package/dist/commands/trigger/actions.js +293 -0
- package/dist/commands/trigger/helpers.js +230 -0
- package/dist/commands/trigger.js +3 -526
- package/dist/commands/webhooks/actions.js +426 -0
- package/dist/commands/webhooks/helpers.js +42 -0
- package/dist/commands/webhooks.js +2 -494
- package/dist/services/config/manager.js +1 -1
- package/dist/services/dev/server.js +2 -2
- package/dist/types/schemas.js +12 -0
- package/package.json +1 -1
package/dist/commands/trigger.js
CHANGED
|
@@ -1,42 +1,7 @@
|
|
|
1
|
-
import Table from 'cli-table3';
|
|
2
1
|
import { Command } from 'commander';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import ConfigManager from '../services/config/manager.js';
|
|
5
|
-
import Spinner from '../utils/spinner.js';
|
|
6
|
-
import Logger from '../utils/logger.js';
|
|
7
2
|
import WebhookEventStore from '../utils/webhook-store.js';
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import { CommandError } from '../utils/errors.js';
|
|
11
|
-
function buildSignatureHeader(config, webhookUrl, body) {
|
|
12
|
-
if (!config?.webhookSecrets || Object.keys(config.webhookSecrets).length === 0) {
|
|
13
|
-
return undefined;
|
|
14
|
-
}
|
|
15
|
-
const registered = config.registeredWebhooks || [];
|
|
16
|
-
const match = registered.find((w) => w.url === webhookUrl);
|
|
17
|
-
const webhookId = match?.id;
|
|
18
|
-
let secret;
|
|
19
|
-
if (webhookId && config.webhookSecrets[webhookId]) {
|
|
20
|
-
secret = config.webhookSecrets[webhookId];
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
const secrets = Object.values(config.webhookSecrets).filter((value) => typeof value === 'string' && value.length > 0);
|
|
24
|
-
secret = secrets[0];
|
|
25
|
-
}
|
|
26
|
-
if (!secret) {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
30
|
-
const signature = crypto
|
|
31
|
-
.createHmac('sha256', secret)
|
|
32
|
-
.update(`${timestamp}.${body}`)
|
|
33
|
-
.digest('hex');
|
|
34
|
-
const parts = [`t=${timestamp}`, `te=${signature}`];
|
|
35
|
-
if (webhookId) {
|
|
36
|
-
parts.push(`li=${webhookId}`);
|
|
37
|
-
}
|
|
38
|
-
return parts.join(',');
|
|
39
|
-
}
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { replayWebhookEvent, sendWebhookEvent } from './trigger/actions.js';
|
|
40
5
|
const command = new Command('trigger');
|
|
41
6
|
command
|
|
42
7
|
.description('Simulate webhook events locally')
|
|
@@ -75,493 +40,5 @@ command
|
|
|
75
40
|
command.help();
|
|
76
41
|
}
|
|
77
42
|
});
|
|
78
|
-
|
|
79
|
-
const spinner = new Spinner();
|
|
80
|
-
const configManager = new ConfigManager();
|
|
81
|
-
const logger = new Logger();
|
|
82
|
-
try {
|
|
83
|
-
const config = await configManager.load();
|
|
84
|
-
const availableEvents = [
|
|
85
|
-
'payment.paid',
|
|
86
|
-
'payment.failed',
|
|
87
|
-
'payment.refunded',
|
|
88
|
-
'payment.refund.updated',
|
|
89
|
-
'source.chargeable',
|
|
90
|
-
'checkout_session.payment.paid',
|
|
91
|
-
'link.payment.paid',
|
|
92
|
-
'qrph.expired',
|
|
93
|
-
];
|
|
94
|
-
let selectedEvent = options.event;
|
|
95
|
-
let webhookUrl = options.url || config?.webhooks?.url;
|
|
96
|
-
if (!selectedEvent) {
|
|
97
|
-
spinner.stop();
|
|
98
|
-
const { select, input } = await import('@inquirer/prompts');
|
|
99
|
-
const eventChoice = await select({
|
|
100
|
-
message: 'Select webhook event to trigger:',
|
|
101
|
-
choices: availableEvents.map((event) => ({
|
|
102
|
-
name: event,
|
|
103
|
-
value: event,
|
|
104
|
-
})),
|
|
105
|
-
});
|
|
106
|
-
const urlInput = await input({
|
|
107
|
-
message: 'Webhook URL:',
|
|
108
|
-
default: webhookUrl || '',
|
|
109
|
-
validate: (value) => {
|
|
110
|
-
try {
|
|
111
|
-
new URL(value);
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return 'Please enter a valid URL';
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
selectedEvent = eventChoice;
|
|
120
|
-
webhookUrl = urlInput || webhookUrl;
|
|
121
|
-
}
|
|
122
|
-
if (!webhookUrl) {
|
|
123
|
-
console.error(chalk.red('❌ No webhook URL provided. Use --url option or configure in .paymongo file'));
|
|
124
|
-
throw new CommandError();
|
|
125
|
-
}
|
|
126
|
-
if (!selectedEvent) {
|
|
127
|
-
console.error(chalk.red('❌ No event selected'));
|
|
128
|
-
throw new CommandError();
|
|
129
|
-
}
|
|
130
|
-
spinner.start('Generating webhook payload...');
|
|
131
|
-
const webhookPayload = generateWebhookPayload(selectedEvent);
|
|
132
|
-
if (options.json) {
|
|
133
|
-
console.log(JSON.stringify(webhookPayload, null, 2));
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
spinner.succeed('Webhook event generated');
|
|
137
|
-
console.log(chalk.bold.blue('\n🚀 Webhook Event Trigger'));
|
|
138
|
-
console.log(chalk.gray('─'.repeat(50)));
|
|
139
|
-
console.log(`${chalk.bold('Event:')} ${chalk.cyan(selectedEvent)}`);
|
|
140
|
-
console.log(`${chalk.bold('URL:')} ${chalk.yellow(webhookUrl)}`);
|
|
141
|
-
console.log(`${chalk.bold('Timestamp:')} ${new Date().toISOString()}`);
|
|
142
|
-
console.log(chalk.gray('\nPayload:'));
|
|
143
|
-
console.log(chalk.gray('─'.repeat(30)));
|
|
144
|
-
console.log(JSON.stringify(webhookPayload, null, 2));
|
|
145
|
-
const store = new WebhookEventStore();
|
|
146
|
-
await store.storeEvent({
|
|
147
|
-
id: webhookPayload.data.id,
|
|
148
|
-
event: selectedEvent,
|
|
149
|
-
url: webhookUrl,
|
|
150
|
-
payload: webhookPayload,
|
|
151
|
-
timestamp: Math.floor(Date.now() / 1000),
|
|
152
|
-
status: 'delivered',
|
|
153
|
-
});
|
|
154
|
-
spinner.start('Sending webhook...');
|
|
155
|
-
try {
|
|
156
|
-
const { request } = await import('undici');
|
|
157
|
-
const body = JSON.stringify(webhookPayload);
|
|
158
|
-
const signatureHeader = buildSignatureHeader(config, webhookUrl, body);
|
|
159
|
-
const response = await request(webhookUrl, {
|
|
160
|
-
method: 'POST',
|
|
161
|
-
headers: {
|
|
162
|
-
'Content-Type': 'application/json',
|
|
163
|
-
'User-Agent': `PayMongo-CLI/${CLI_VERSION}`,
|
|
164
|
-
...(signatureHeader ? { 'paymongo-signature': signatureHeader } : {}),
|
|
165
|
-
},
|
|
166
|
-
body,
|
|
167
|
-
signal: AbortSignal.timeout(10000),
|
|
168
|
-
});
|
|
169
|
-
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
170
|
-
spinner.succeed(`Webhook delivered successfully (HTTP ${response.statusCode})`);
|
|
171
|
-
const contentType = response.headers['content-type'];
|
|
172
|
-
if (contentType && contentType.includes('application/json')) {
|
|
173
|
-
const responseData = await response.body.json();
|
|
174
|
-
console.log(chalk.gray('\nResponse:'));
|
|
175
|
-
console.log(chalk.gray('─'.repeat(30)));
|
|
176
|
-
console.log(JSON.stringify(responseData, null, 2));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
else if (response.statusCode === 404) {
|
|
180
|
-
spinner.fail(`Webhook endpoint not found (HTTP 404)`);
|
|
181
|
-
console.log('');
|
|
182
|
-
console.log(chalk.red('❌ The webhook URL returned 404 Not Found'));
|
|
183
|
-
console.log('');
|
|
184
|
-
console.log(chalk.yellow('💡 Possible causes:'));
|
|
185
|
-
console.log(chalk.gray(' • The webhook endpoint path is incorrect'));
|
|
186
|
-
console.log(chalk.gray(' • Your server is not running'));
|
|
187
|
-
console.log(chalk.gray(' • The route is not registered in your application'));
|
|
188
|
-
console.log('');
|
|
189
|
-
console.log(chalk.yellow('💡 To fix:'));
|
|
190
|
-
console.log(chalk.gray(` • Verify your server has a POST handler at: ${webhookUrl}`));
|
|
191
|
-
console.log(chalk.gray(' • Check that your server is running and accessible'));
|
|
192
|
-
throw new CommandError();
|
|
193
|
-
}
|
|
194
|
-
else if (response.statusCode >= 400 && response.statusCode < 500) {
|
|
195
|
-
spinner.fail(`Webhook rejected by server (HTTP ${response.statusCode})`);
|
|
196
|
-
console.log('');
|
|
197
|
-
console.log(chalk.red(`❌ Server returned client error: ${response.statusCode}`));
|
|
198
|
-
const contentType = response.headers['content-type'];
|
|
199
|
-
if (contentType && contentType.includes('application/json')) {
|
|
200
|
-
const responseData = await response.body.json();
|
|
201
|
-
console.log(chalk.gray('\nServer response:'));
|
|
202
|
-
console.log(JSON.stringify(responseData, null, 2));
|
|
203
|
-
}
|
|
204
|
-
console.log('');
|
|
205
|
-
console.log(chalk.yellow('💡 Common causes:'));
|
|
206
|
-
console.log(chalk.gray(' • Invalid request format or headers'));
|
|
207
|
-
console.log(chalk.gray(' • Authentication/authorization failure'));
|
|
208
|
-
console.log(chalk.gray(' • Webhook signature verification failed'));
|
|
209
|
-
throw new CommandError();
|
|
210
|
-
}
|
|
211
|
-
else if (response.statusCode >= 500) {
|
|
212
|
-
spinner.fail(`Webhook endpoint error (HTTP ${response.statusCode})`);
|
|
213
|
-
console.log('');
|
|
214
|
-
console.log(chalk.red(`❌ Server returned error: ${response.statusCode}`));
|
|
215
|
-
const contentType = response.headers['content-type'];
|
|
216
|
-
if (contentType && contentType.includes('application/json')) {
|
|
217
|
-
const responseData = await response.body.json();
|
|
218
|
-
console.log(chalk.gray('\nServer response:'));
|
|
219
|
-
console.log(JSON.stringify(responseData, null, 2));
|
|
220
|
-
}
|
|
221
|
-
console.log('');
|
|
222
|
-
console.log(chalk.yellow('💡 This is a server-side error. Check:'));
|
|
223
|
-
console.log(chalk.gray(' • Server logs for the specific error'));
|
|
224
|
-
console.log(chalk.gray(' • Webhook handler code for exceptions'));
|
|
225
|
-
throw new CommandError();
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
const err = error;
|
|
230
|
-
if (err.code === 'ECONNREFUSED') {
|
|
231
|
-
spinner.fail('Connection refused');
|
|
232
|
-
console.log('');
|
|
233
|
-
console.log(chalk.red('❌ Could not connect to webhook URL'));
|
|
234
|
-
console.log('');
|
|
235
|
-
console.log(chalk.yellow('💡 Possible causes:'));
|
|
236
|
-
console.log(chalk.gray(' • Server is not running'));
|
|
237
|
-
console.log(chalk.gray(' • Wrong port number'));
|
|
238
|
-
console.log(chalk.gray(' • Firewall blocking the connection'));
|
|
239
|
-
console.log('');
|
|
240
|
-
console.log(chalk.yellow('💡 To fix:'));
|
|
241
|
-
console.log(chalk.gray(' • Start your local server'));
|
|
242
|
-
console.log(chalk.gray(` • Verify the server is listening on the correct port`));
|
|
243
|
-
throw new CommandError();
|
|
244
|
-
}
|
|
245
|
-
else if (err.code === 'ENOTFOUND') {
|
|
246
|
-
spinner.fail('Host not found');
|
|
247
|
-
console.log('');
|
|
248
|
-
console.log(chalk.red('❌ Could not resolve webhook URL hostname'));
|
|
249
|
-
console.log('');
|
|
250
|
-
console.log(chalk.yellow('💡 Check:'));
|
|
251
|
-
console.log(chalk.gray(' • The URL is spelled correctly'));
|
|
252
|
-
console.log(chalk.gray(' • Your internet connection is working'));
|
|
253
|
-
console.log(chalk.gray(' • DNS is resolving correctly'));
|
|
254
|
-
throw new CommandError();
|
|
255
|
-
}
|
|
256
|
-
else if (err.code === 'ETIMEDOUT' || err.message.includes('timeout')) {
|
|
257
|
-
spinner.fail('Request timed out');
|
|
258
|
-
console.log('');
|
|
259
|
-
console.log(chalk.red('❌ Webhook request timed out after 10 seconds'));
|
|
260
|
-
console.log('');
|
|
261
|
-
console.log(chalk.yellow('💡 Possible causes:'));
|
|
262
|
-
console.log(chalk.gray(' • Server is taking too long to respond'));
|
|
263
|
-
console.log(chalk.gray(' • Network latency issues'));
|
|
264
|
-
console.log(chalk.gray(' • Server is stuck or deadlocked'));
|
|
265
|
-
console.log('');
|
|
266
|
-
console.log(chalk.yellow('💡 To fix:'));
|
|
267
|
-
console.log(chalk.gray(' • Check your webhook handler for slow operations'));
|
|
268
|
-
console.log(chalk.gray(' • Ensure async operations are handled properly'));
|
|
269
|
-
throw new CommandError();
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
spinner.fail(`Webhook delivery failed: ${err.message}`);
|
|
273
|
-
console.log('');
|
|
274
|
-
console.log(chalk.red('❌ Unexpected error occurred'));
|
|
275
|
-
console.log(chalk.gray(` Error: ${err.message}`));
|
|
276
|
-
if (err.code) {
|
|
277
|
-
console.log(chalk.gray(` Code: ${err.code}`));
|
|
278
|
-
}
|
|
279
|
-
throw new CommandError();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
const err = error;
|
|
285
|
-
spinner.fail('Failed to trigger webhook event');
|
|
286
|
-
logger.error('Trigger command error:', err.message);
|
|
287
|
-
throw new CommandError();
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
function generateWebhookPayload(eventType) {
|
|
291
|
-
const basePayload = {
|
|
292
|
-
data: {
|
|
293
|
-
id: `evt_${generateId()}`,
|
|
294
|
-
type: 'event',
|
|
295
|
-
attributes: {
|
|
296
|
-
type: eventType,
|
|
297
|
-
livemode: false,
|
|
298
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
299
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
300
|
-
data: {},
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
};
|
|
304
|
-
switch (eventType) {
|
|
305
|
-
case 'payment.paid':
|
|
306
|
-
basePayload.data.attributes.data = {
|
|
307
|
-
id: `pay_${generateId()}`,
|
|
308
|
-
type: 'payment',
|
|
309
|
-
attributes: {
|
|
310
|
-
amount: 100000,
|
|
311
|
-
currency: 'PHP',
|
|
312
|
-
description: 'Test Payment',
|
|
313
|
-
status: 'paid',
|
|
314
|
-
external_reference_number: null,
|
|
315
|
-
paid_at: Math.floor(Date.now() / 1000),
|
|
316
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
317
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
318
|
-
fees: 2950,
|
|
319
|
-
net_amount: 97050,
|
|
320
|
-
payment_intent_id: `pi_${generateId()}`,
|
|
321
|
-
source: {
|
|
322
|
-
id: `src_${generateId()}`,
|
|
323
|
-
type: 'source',
|
|
324
|
-
attributes: {
|
|
325
|
-
amount: 100000,
|
|
326
|
-
currency: 'PHP',
|
|
327
|
-
status: 'paid',
|
|
328
|
-
type: 'gcash',
|
|
329
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
330
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
};
|
|
335
|
-
break;
|
|
336
|
-
case 'payment.failed':
|
|
337
|
-
basePayload.data.attributes.data = {
|
|
338
|
-
id: `pay_${generateId()}`,
|
|
339
|
-
type: 'payment',
|
|
340
|
-
attributes: {
|
|
341
|
-
amount: 50000,
|
|
342
|
-
currency: 'PHP',
|
|
343
|
-
description: 'Failed Test Payment',
|
|
344
|
-
status: 'failed',
|
|
345
|
-
external_reference_number: null,
|
|
346
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
347
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
348
|
-
fees: 0,
|
|
349
|
-
net_amount: 0,
|
|
350
|
-
payment_intent_id: `pi_${generateId()}`,
|
|
351
|
-
source: {
|
|
352
|
-
id: `src_${generateId()}`,
|
|
353
|
-
type: 'source',
|
|
354
|
-
attributes: {
|
|
355
|
-
amount: 50000,
|
|
356
|
-
currency: 'PHP',
|
|
357
|
-
status: 'failed',
|
|
358
|
-
type: 'card',
|
|
359
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
360
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
361
|
-
},
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
};
|
|
365
|
-
break;
|
|
366
|
-
case 'source.chargeable':
|
|
367
|
-
basePayload.data.attributes.data = {
|
|
368
|
-
id: `src_${generateId()}`,
|
|
369
|
-
type: 'source',
|
|
370
|
-
attributes: {
|
|
371
|
-
amount: 150000,
|
|
372
|
-
currency: 'PHP',
|
|
373
|
-
status: 'chargeable',
|
|
374
|
-
type: 'gcash',
|
|
375
|
-
billing: {
|
|
376
|
-
address: {
|
|
377
|
-
city: 'Manila',
|
|
378
|
-
country: 'PH',
|
|
379
|
-
line1: '123 Test Street',
|
|
380
|
-
line2: null,
|
|
381
|
-
postal_code: '1000',
|
|
382
|
-
state: 'Metro Manila',
|
|
383
|
-
},
|
|
384
|
-
email: 'test@example.com',
|
|
385
|
-
name: 'Test User',
|
|
386
|
-
phone: '+639123456789',
|
|
387
|
-
},
|
|
388
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
389
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
390
|
-
},
|
|
391
|
-
};
|
|
392
|
-
break;
|
|
393
|
-
case 'checkout_session.payment.paid':
|
|
394
|
-
basePayload.data.attributes.data = {
|
|
395
|
-
id: `cs_${generateId()}`,
|
|
396
|
-
type: 'checkout_session',
|
|
397
|
-
attributes: {
|
|
398
|
-
amount: 200000,
|
|
399
|
-
currency: 'PHP',
|
|
400
|
-
description: 'Test Checkout Session',
|
|
401
|
-
status: 'paid',
|
|
402
|
-
payment_intent_id: `pi_${generateId()}`,
|
|
403
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
404
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
405
|
-
},
|
|
406
|
-
};
|
|
407
|
-
break;
|
|
408
|
-
case 'link.payment.paid':
|
|
409
|
-
basePayload.data.attributes.data = {
|
|
410
|
-
id: `plink_${generateId()}`,
|
|
411
|
-
type: 'link',
|
|
412
|
-
attributes: {
|
|
413
|
-
amount: 75000,
|
|
414
|
-
currency: 'PHP',
|
|
415
|
-
description: 'Test Payment Link',
|
|
416
|
-
status: 'paid',
|
|
417
|
-
archived: false,
|
|
418
|
-
payment_intent_id: `pi_${generateId()}`,
|
|
419
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
420
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
421
|
-
},
|
|
422
|
-
};
|
|
423
|
-
break;
|
|
424
|
-
default:
|
|
425
|
-
basePayload.data.attributes.data = {
|
|
426
|
-
id: `${eventType.split('.')[1]}_${generateId()}`,
|
|
427
|
-
type: eventType.split('.')[0],
|
|
428
|
-
attributes: {
|
|
429
|
-
status: 'test',
|
|
430
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
431
|
-
updated_at: Math.floor(Date.now() / 1000),
|
|
432
|
-
},
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
return basePayload;
|
|
436
|
-
}
|
|
437
|
-
async function replayWebhookEvent(eventId, options) {
|
|
438
|
-
const store = new WebhookEventStore();
|
|
439
|
-
const configManager = new ConfigManager();
|
|
440
|
-
const config = await configManager.load();
|
|
441
|
-
try {
|
|
442
|
-
if (options.list || (!eventId && !options.event)) {
|
|
443
|
-
const events = await store.loadEvents();
|
|
444
|
-
if (options.json) {
|
|
445
|
-
console.log(JSON.stringify(events, null, 2));
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
if (events.length === 0) {
|
|
449
|
-
console.log(chalk.yellow('No webhook events stored yet.'));
|
|
450
|
-
console.log(chalk.gray('Use "paymongo trigger send" to send events first.'));
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
console.log(chalk.bold.blue('\n📋 Stored Webhook Events'));
|
|
454
|
-
console.log(chalk.gray('─'.repeat(95)));
|
|
455
|
-
const table = new Table({
|
|
456
|
-
head: [chalk.bold('ID'), chalk.bold('Event'), chalk.bold('Timestamp')],
|
|
457
|
-
colWidths: [25, 30, 25],
|
|
458
|
-
style: {
|
|
459
|
-
head: [],
|
|
460
|
-
border: [],
|
|
461
|
-
},
|
|
462
|
-
});
|
|
463
|
-
events.slice(0, 10).forEach((event) => {
|
|
464
|
-
const id = event.id.substring(0, 22) + (event.id.length > 22 ? '...' : '');
|
|
465
|
-
const eventType = event.event;
|
|
466
|
-
const timestamp = new Date(event.timestamp * 1000).toLocaleString();
|
|
467
|
-
table.push([chalk.cyan(id), chalk.yellow(eventType), chalk.gray(timestamp)]);
|
|
468
|
-
});
|
|
469
|
-
console.log(table.toString());
|
|
470
|
-
if (events.length > 10) {
|
|
471
|
-
console.log(chalk.gray(`\n... and ${events.length - 10} more events. Use --list to see all.`));
|
|
472
|
-
}
|
|
473
|
-
console.log(chalk.gray('\n💡 Use "paymongo trigger replay <eventId>" to replay a specific event'));
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
if (options.event && !eventId) {
|
|
477
|
-
const events = await store.loadEvents();
|
|
478
|
-
const matchingEvents = events.filter((e) => e.event === options.event);
|
|
479
|
-
if (matchingEvents.length === 0) {
|
|
480
|
-
console.log(chalk.yellow(`No events found for type: ${options.event}`));
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
if (options.json) {
|
|
484
|
-
console.log(JSON.stringify(matchingEvents, null, 2));
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
console.log(chalk.bold.blue(`\n📋 Recent "${options.event}" Events`));
|
|
488
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
489
|
-
matchingEvents.slice(0, 5).forEach((event, index) => {
|
|
490
|
-
const id = event.id;
|
|
491
|
-
const timestamp = new Date(event.timestamp * 1000).toLocaleString();
|
|
492
|
-
console.log(`${chalk.cyan((index + 1).toString() + '.')} ${chalk.yellow(id)} - ${chalk.gray(timestamp)}`);
|
|
493
|
-
});
|
|
494
|
-
console.log(chalk.gray('\n💡 Use "paymongo trigger replay <eventId>" to replay a specific event'));
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
if (eventId) {
|
|
498
|
-
const event = await store.getEventById(eventId);
|
|
499
|
-
if (!event) {
|
|
500
|
-
console.log(chalk.red(`❌ Event not found: ${eventId}`));
|
|
501
|
-
console.log(chalk.gray('Use "paymongo trigger replay --list" to see available events.'));
|
|
502
|
-
throw new CommandError();
|
|
503
|
-
}
|
|
504
|
-
const webhookUrl = options.url || event.url;
|
|
505
|
-
console.log(chalk.bold.blue('\n🔄 Replaying Webhook Event'));
|
|
506
|
-
console.log(chalk.gray('─'.repeat(50)));
|
|
507
|
-
console.log(`${chalk.bold('Event ID:')} ${chalk.cyan(event.id)}`);
|
|
508
|
-
console.log(`${chalk.bold('Event Type:')} ${chalk.yellow(event.event)}`);
|
|
509
|
-
console.log(`${chalk.bold('URL:')} ${chalk.yellow(webhookUrl)}`);
|
|
510
|
-
console.log(`${chalk.bold('Original Time:')} ${chalk.gray(new Date(event.timestamp * 1000).toISOString())}`);
|
|
511
|
-
const spinner = new Spinner();
|
|
512
|
-
spinner.start('Sending webhook...');
|
|
513
|
-
try {
|
|
514
|
-
const { request } = await import('undici');
|
|
515
|
-
const body = JSON.stringify(event.payload);
|
|
516
|
-
const signatureHeader = buildSignatureHeader(config, webhookUrl, body);
|
|
517
|
-
const response = await request(webhookUrl, {
|
|
518
|
-
method: 'POST',
|
|
519
|
-
headers: {
|
|
520
|
-
'Content-Type': 'application/json',
|
|
521
|
-
'User-Agent': `PayMongo-CLI/${CLI_VERSION}`,
|
|
522
|
-
...(signatureHeader ? { 'paymongo-signature': signatureHeader } : {}),
|
|
523
|
-
},
|
|
524
|
-
body,
|
|
525
|
-
signal: AbortSignal.timeout(10000),
|
|
526
|
-
});
|
|
527
|
-
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
528
|
-
spinner.succeed(`Webhook replayed successfully (HTTP ${response.statusCode})`);
|
|
529
|
-
const contentType = response.headers['content-type'];
|
|
530
|
-
if (contentType && contentType.includes('application/json')) {
|
|
531
|
-
const responseData = await response.body.json();
|
|
532
|
-
if (!options.json) {
|
|
533
|
-
console.log(chalk.gray('\nResponse:'));
|
|
534
|
-
console.log(chalk.gray('─'.repeat(30)));
|
|
535
|
-
console.log(JSON.stringify(responseData, null, 2));
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
spinner.fail(`Webhook replay failed (HTTP ${response.statusCode})`);
|
|
541
|
-
console.log(chalk.red(`Server responded with: ${response.statusCode}`));
|
|
542
|
-
throw new CommandError();
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
catch (error) {
|
|
546
|
-
const err = error;
|
|
547
|
-
spinner.fail('Webhook replay failed');
|
|
548
|
-
if (err.code === 'ECONNREFUSED') {
|
|
549
|
-
console.log(chalk.red('❌ Could not connect to webhook URL'));
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
console.log(chalk.red(`❌ Error: ${err.message}`));
|
|
553
|
-
}
|
|
554
|
-
throw new CommandError();
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
catch (error) {
|
|
559
|
-
const err = error;
|
|
560
|
-
console.error(chalk.red(`❌ Failed to replay webhook: ${err.message}`));
|
|
561
|
-
throw new CommandError();
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
function generateId() {
|
|
565
|
-
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
566
|
-
}
|
|
43
|
+
export { sendWebhookEvent, replayWebhookEvent };
|
|
567
44
|
export default command;
|