paymongo-cli 1.2.0 → 1.4.2

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 (116) hide show
  1. package/.github/copilot-instructions.md +95 -0
  2. package/.github/workflows/ci-cd.yml +2 -46
  3. package/.github/workflows/ci.yml +1 -1
  4. package/.github/workflows/release.yml +16 -1
  5. package/AGENTS.md +418 -0
  6. package/CHANGELOG.md +331 -185
  7. package/README.md +94 -13
  8. package/TESTING.md +222 -0
  9. package/coverage/base.css +224 -0
  10. package/coverage/block-navigation.js +87 -0
  11. package/coverage/favicon.png +0 -0
  12. package/coverage/index.html +281 -0
  13. package/coverage/lcov-report/base.css +224 -0
  14. package/coverage/lcov-report/block-navigation.js +87 -0
  15. package/coverage/lcov-report/favicon.png +0 -0
  16. package/coverage/lcov-report/index.html +281 -0
  17. package/coverage/lcov-report/prettify.css +1 -0
  18. package/coverage/lcov-report/prettify.js +2 -0
  19. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  20. package/coverage/lcov-report/sorter.js +210 -0
  21. package/coverage/lcov.info +5053 -0
  22. package/coverage/prettify.css +1 -0
  23. package/coverage/prettify.js +2 -0
  24. package/coverage/sort-arrow-sprite.png +0 -0
  25. package/coverage/sorter.js +210 -0
  26. package/dist/.tsbuildinfo +1 -1
  27. package/dist/commands/config.d.ts +17 -0
  28. package/dist/commands/config.d.ts.map +1 -1
  29. package/dist/commands/config.js +268 -55
  30. package/dist/commands/config.js.map +1 -1
  31. package/dist/commands/dev.d.ts +13 -1
  32. package/dist/commands/dev.d.ts.map +1 -1
  33. package/dist/commands/dev.js +40 -56
  34. package/dist/commands/dev.js.map +1 -1
  35. package/dist/commands/env.d.ts +4 -0
  36. package/dist/commands/env.d.ts.map +1 -0
  37. package/dist/commands/env.js +106 -0
  38. package/dist/commands/env.js.map +1 -0
  39. package/dist/commands/generate.js +1184 -0
  40. package/dist/commands/init.d.ts +11 -0
  41. package/dist/commands/init.d.ts.map +1 -1
  42. package/dist/commands/init.js +33 -33
  43. package/dist/commands/init.js.map +1 -1
  44. package/dist/commands/login.d.ts +17 -1
  45. package/dist/commands/login.d.ts.map +1 -1
  46. package/dist/commands/login.js +2 -17
  47. package/dist/commands/login.js.map +1 -1
  48. package/dist/commands/payments.d.ts +37 -0
  49. package/dist/commands/payments.d.ts.map +1 -1
  50. package/dist/commands/payments.js +367 -32
  51. package/dist/commands/payments.js.map +1 -1
  52. package/dist/commands/team/index.d.ts.map +1 -1
  53. package/dist/commands/team/index.js +192 -95
  54. package/dist/commands/team/index.js.map +1 -1
  55. package/dist/commands/trigger.d.ts.map +1 -1
  56. package/dist/commands/trigger.js +239 -75
  57. package/dist/commands/trigger.js.map +1 -1
  58. package/dist/commands/webhooks.d.ts +19 -0
  59. package/dist/commands/webhooks.d.ts.map +1 -1
  60. package/dist/commands/webhooks.js +246 -70
  61. package/dist/commands/webhooks.js.map +1 -1
  62. package/dist/index.js +56 -32
  63. package/dist/index.js.map +1 -1
  64. package/dist/services/analytics/service.js +6 -8
  65. package/dist/services/api/client.d.ts +6 -8
  66. package/dist/services/api/client.d.ts.map +1 -1
  67. package/dist/services/api/client.js +20 -131
  68. package/dist/services/api/client.js.map +1 -1
  69. package/dist/services/api/rate-limiter.d.ts +64 -0
  70. package/dist/services/api/rate-limiter.d.ts.map +1 -0
  71. package/dist/services/api/rate-limiter.js +83 -0
  72. package/dist/services/api/rate-limiter.js.map +1 -0
  73. package/dist/services/api/undici-client.d.ts +39 -0
  74. package/dist/services/api/undici-client.d.ts.map +1 -0
  75. package/dist/services/api/undici-client.js +294 -0
  76. package/dist/services/api/undici-client.js.map +1 -0
  77. package/dist/services/config/manager.js +1 -16
  78. package/dist/services/dev/process-manager.js +0 -32
  79. package/dist/services/github/client.d.ts +41 -0
  80. package/dist/services/github/client.d.ts.map +1 -1
  81. package/dist/services/github/client.js +28 -0
  82. package/dist/services/github/client.js.map +1 -1
  83. package/dist/services/payments/simulator.d.ts +28 -0
  84. package/dist/services/payments/simulator.d.ts.map +1 -0
  85. package/dist/services/payments/simulator.js +115 -0
  86. package/dist/services/payments/simulator.js.map +1 -0
  87. package/dist/services/team/service.d.ts +44 -0
  88. package/dist/services/team/service.d.ts.map +1 -0
  89. package/dist/services/team/service.js +153 -0
  90. package/dist/services/team/service.js.map +1 -0
  91. package/dist/types/paymongo.d.ts +36 -3
  92. package/dist/types/paymongo.d.ts.map +1 -1
  93. package/dist/types/paymongo.js +0 -1
  94. package/dist/types/schemas.js +0 -8
  95. package/dist/utils/bulk.d.ts +62 -0
  96. package/dist/utils/bulk.d.ts.map +1 -0
  97. package/dist/utils/bulk.js +123 -0
  98. package/dist/utils/bulk.js.map +1 -0
  99. package/dist/utils/cache.js +4 -16
  100. package/dist/utils/constants.js +2 -13
  101. package/dist/utils/errors.d.ts.map +1 -1
  102. package/dist/utils/errors.js +22 -7
  103. package/dist/utils/errors.js.map +1 -1
  104. package/dist/utils/logger.d.ts +3 -1
  105. package/dist/utils/logger.d.ts.map +1 -1
  106. package/dist/utils/logger.js +38 -25
  107. package/dist/utils/logger.js.map +1 -1
  108. package/dist/utils/spinner.js +0 -1
  109. package/dist/utils/validator.js +0 -3
  110. package/dist/utils/webhook-store.d.ts +22 -0
  111. package/dist/utils/webhook-store.d.ts.map +1 -0
  112. package/dist/utils/webhook-store.js +57 -0
  113. package/dist/utils/webhook-store.js.map +1 -0
  114. package/package.json +75 -76
  115. package/jest.config.ts +0 -30
  116. package/web/index.html +0 -688
@@ -0,0 +1,1184 @@
1
+ import { Command } from 'commander';
2
+ import { input } from '@inquirer/prompts';
3
+ import chalk from 'chalk';
4
+ import fs from 'fs/promises';
5
+ import ConfigManager from '../services/config/manager.js';
6
+ import Spinner from '../utils/spinner.js';
7
+ const command = new Command('generate');
8
+ command
9
+ .description('Generate boilerplate code for PayMongo integrations')
10
+ .addHelpText('after', `
11
+ EXAMPLES
12
+ $ paymongo generate webhook-handler --events payment.paid,payment.failed
13
+ $ paymongo generate webhook-handler --language typescript --framework express
14
+ $ paymongo generate payment-intent --methods card,gcash --language typescript
15
+ $ paymongo generate checkout-page --framework react --output Checkout.jsx
16
+ `)
17
+ .addCommand(new Command('webhook-handler')
18
+ .description('Generate a webhook handler for specific events')
19
+ .option('-e, --events <events>', 'Comma-separated list of events (e.g., payment.paid,payment.failed)')
20
+ .option('-l, --language <language>', 'Programming language (javascript, typescript)', 'javascript')
21
+ .option('-f, --framework <framework>', 'Framework (express, fastify, hapi)', 'express')
22
+ .option('-o, --output <file>', 'Output file path')
23
+ .addHelpText('after', `
24
+ SUPPORTED EVENTS:
25
+ payment.paid, payment.failed, payment.refunded, payment.expired
26
+ source.chargeable, source.failed, source.cancelled
27
+ checkout.session.succeeded, checkout.session.cancelled
28
+
29
+ EXAMPLES:
30
+ $ paymongo generate webhook-handler
31
+ $ paymongo generate webhook-handler --events payment.paid,payment.failed
32
+ $ paymongo generate webhook-handler --language typescript --framework fastify
33
+ $ paymongo generate webhook-handler --output my-webhook.js
34
+ `)
35
+ .action(async (options) => {
36
+ await generateWebhookHandler(options);
37
+ }))
38
+ .addCommand(new Command('payment-intent')
39
+ .description('Generate payment intent creation code')
40
+ .option('-l, --language <language>', 'Programming language (javascript, typescript)', 'javascript')
41
+ .option('-m, --methods <methods>', 'Payment methods (card,gcash,paymaya,grab_pay,qrph)')
42
+ .option('-o, --output <file>', 'Output file path')
43
+ .addHelpText('after', `
44
+ PAYMENT METHODS:
45
+ card, gcash, paymaya, grab_pay, qrph
46
+
47
+ EXAMPLES:
48
+ $ paymongo generate payment-intent
49
+ $ paymongo generate payment-intent --methods card,gcash
50
+ $ paymongo generate payment-intent --language typescript --output create-payment.js
51
+ `)
52
+ .action(async (options) => {
53
+ await generatePaymentIntent(options);
54
+ }))
55
+ .addCommand(new Command('checkout-page')
56
+ .description('Generate a basic checkout page with PayMongo integration')
57
+ .option('-l, --language <language>', 'Frontend language/framework (html, react, vue)', 'html')
58
+ .option('-o, --output <file>', 'Output file path')
59
+ .addHelpText('after', `
60
+ FRAMEWORKS:
61
+ html (vanilla HTML/JS), react, vue
62
+
63
+ EXAMPLES:
64
+ $ paymongo generate checkout-page
65
+ $ paymongo generate checkout-page --framework react
66
+ $ paymongo generate checkout-page --language vue --output Checkout.vue
67
+ `)
68
+ .action(async (options) => {
69
+ await generateCheckoutPage(options);
70
+ }));
71
+ async function generateWebhookHandler(options) {
72
+ const spinner = new Spinner();
73
+ const configManager = new ConfigManager();
74
+ try {
75
+ spinner.start('Loading configuration...');
76
+ const config = await configManager.load();
77
+ if (!config) {
78
+ spinner.fail('No configuration found');
79
+ console.log(chalk.yellow('No PayMongo configuration found.'));
80
+ console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
81
+ return;
82
+ }
83
+ spinner.succeed('Configuration loaded');
84
+ let events = [];
85
+ if (options.events) {
86
+ events = options.events.split(',').map(e => e.trim());
87
+ }
88
+ else {
89
+ const eventInput = await input({
90
+ message: 'Enter webhook events (comma-separated):',
91
+ default: 'payment.paid,payment.failed',
92
+ });
93
+ events = eventInput.split(',').map(e => e.trim());
94
+ }
95
+ const validEvents = [
96
+ 'payment.paid', 'payment.failed', 'payment.refunded', 'payment.expired',
97
+ 'source.chargeable', 'source.failed', 'source.cancelled',
98
+ 'checkout.session.succeeded', 'checkout.session.cancelled'
99
+ ];
100
+ const invalidEvents = events.filter(e => !validEvents.includes(e));
101
+ if (invalidEvents.length > 0) {
102
+ console.log(chalk.yellow(`Warning: Unknown events: ${invalidEvents.join(', ')}`));
103
+ console.log(chalk.gray(`Valid events: ${validEvents.join(', ')}`));
104
+ }
105
+ let code;
106
+ if (options.language === 'typescript') {
107
+ code = generateTypeScriptWebhookHandler(events, options.framework);
108
+ }
109
+ else {
110
+ code = generateJavaScriptWebhookHandler(events, options.framework);
111
+ }
112
+ let outputFile = options.output;
113
+ if (!outputFile) {
114
+ const firstEvent = events[0] || 'webhook';
115
+ const defaultName = `webhook-handler-${firstEvent.replace('.', '-')}.${options.language === 'typescript' ? 'ts' : 'js'}`;
116
+ outputFile = await input({
117
+ message: 'Output file path:',
118
+ default: defaultName,
119
+ });
120
+ }
121
+ spinner.start(`Generating webhook handler...`);
122
+ await fs.writeFile(outputFile, code, 'utf-8');
123
+ spinner.succeed(`Webhook handler generated: ${outputFile}`);
124
+ console.log('\n' + chalk.green('✅ Webhook handler generated successfully!'));
125
+ console.log(chalk.gray(`Events handled: ${events.join(', ')}`));
126
+ console.log(chalk.gray(`Language: ${options.language}`));
127
+ console.log(chalk.gray(`Framework: ${options.framework}`));
128
+ }
129
+ catch (error) {
130
+ spinner.fail('Generation failed');
131
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
132
+ }
133
+ }
134
+ async function generatePaymentIntent(options) {
135
+ const spinner = new Spinner();
136
+ try {
137
+ let methods = ['card', 'gcash', 'paymaya'];
138
+ if (options.methods) {
139
+ methods = options.methods.split(',').map(m => m.trim());
140
+ }
141
+ let code;
142
+ if (options.language === 'typescript') {
143
+ code = generateTypeScriptPaymentIntent(methods);
144
+ }
145
+ else {
146
+ code = generateJavaScriptPaymentIntent(methods);
147
+ }
148
+ let outputFile = options.output;
149
+ if (!outputFile) {
150
+ const defaultName = `create-payment-intent.${options.language === 'typescript' ? 'ts' : 'js'}`;
151
+ outputFile = await input({
152
+ message: 'Output file path:',
153
+ default: defaultName,
154
+ });
155
+ }
156
+ spinner.start(`Generating payment intent code...`);
157
+ await fs.writeFile(outputFile, code, 'utf-8');
158
+ spinner.succeed(`Payment intent code generated: ${outputFile}`);
159
+ console.log('\n' + chalk.green('✅ Payment intent code generated successfully!'));
160
+ console.log(chalk.gray(`Payment methods: ${methods.join(', ')}`));
161
+ console.log(chalk.gray(`Language: ${options.language}`));
162
+ }
163
+ catch (error) {
164
+ spinner.fail('Generation failed');
165
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
166
+ }
167
+ }
168
+ async function generateCheckoutPage(options) {
169
+ const spinner = new Spinner();
170
+ try {
171
+ let code;
172
+ let fileExtension;
173
+ switch (options.language) {
174
+ case 'react':
175
+ code = generateReactCheckoutPage();
176
+ fileExtension = 'jsx';
177
+ break;
178
+ case 'vue':
179
+ code = generateVueCheckoutPage();
180
+ fileExtension = 'vue';
181
+ break;
182
+ default:
183
+ code = generateHTMLCheckoutPage();
184
+ fileExtension = 'html';
185
+ }
186
+ let outputFile = options.output;
187
+ if (!outputFile) {
188
+ const defaultName = `checkout.${fileExtension}`;
189
+ outputFile = await input({
190
+ message: 'Output file path:',
191
+ default: defaultName,
192
+ });
193
+ }
194
+ spinner.start(`Generating checkout page...`);
195
+ await fs.writeFile(outputFile, code, 'utf-8');
196
+ spinner.succeed(`Checkout page generated: ${outputFile}`);
197
+ console.log('\n' + chalk.green('✅ Checkout page generated successfully!'));
198
+ console.log(chalk.gray(`Framework: ${options.language}`));
199
+ }
200
+ catch (error) {
201
+ spinner.fail('Generation failed');
202
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
203
+ }
204
+ }
205
+ function generateJavaScriptWebhookHandler(events, framework) {
206
+ const eventHandlers = events.map(event => `
207
+ case '${event}':
208
+ console.log('Processing ${event} event:', data);
209
+ // Add your ${event} handling logic here
210
+ break;`).join('');
211
+ switch (framework) {
212
+ case 'express':
213
+ return `const express = require('express');
214
+ const crypto = require('crypto');
215
+
216
+ const app = express();
217
+ app.use(express.json());
218
+
219
+ // Webhook secret from PayMongo dashboard
220
+ const WEBHOOK_SECRET = process.env.PAYMONGO_WEBHOOK_SECRET;
221
+
222
+ function verifySignature(payload, signature, secret) {
223
+ const expectedSignature = crypto
224
+ .createHmac('sha256', secret)
225
+ .update(payload, 'utf8')
226
+ .digest('hex');
227
+
228
+ return signature === \`sha256=\${expectedSignature}\`;
229
+ }
230
+
231
+ app.post('/webhooks/paymongo', (req, res) => {
232
+ try {
233
+ const signature = req.headers['paymongo-signature'];
234
+ const payload = JSON.stringify(req.body);
235
+
236
+ // Verify webhook signature (optional but recommended)
237
+ if (WEBHOOK_SECRET && !verifySignature(payload, signature, WEBHOOK_SECRET)) {
238
+ console.log('Invalid signature');
239
+ return res.status(400).json({ error: 'Invalid signature' });
240
+ }
241
+
242
+ const { data } = req.body;
243
+ const eventType = data.attributes.type;
244
+
245
+ switch (eventType) {${eventHandlers}
246
+ default:
247
+ console.log('Unhandled event type:', eventType);
248
+ }
249
+
250
+ res.json({ received: true });
251
+ } catch (error) {
252
+ console.error('Webhook processing error:', error);
253
+ res.status(500).json({ error: 'Internal server error' });
254
+ }
255
+ });
256
+
257
+ const PORT = process.env.PORT || 3000;
258
+ app.listen(PORT, () => {
259
+ console.log(\`Webhook server running on port \${PORT}\`);
260
+ });`;
261
+ case 'fastify':
262
+ return `const fastify = require('fastify')({ logger: true });
263
+ const crypto = require('crypto');
264
+
265
+ // Webhook secret from PayMongo dashboard
266
+ const WEBHOOK_SECRET = process.env.PAYMONGO_WEBHOOK_SECRET;
267
+
268
+ function verifySignature(payload, signature, secret) {
269
+ const expectedSignature = crypto
270
+ .createHmac('sha256', secret)
271
+ .update(payload, 'utf8')
272
+ .digest('hex');
273
+
274
+ return signature === \`sha256=\${expectedSignature}\`;
275
+ }
276
+
277
+ fastify.post('/webhooks/paymongo', async (request, reply) => {
278
+ try {
279
+ const signature = request.headers['paymongo-signature'];
280
+ const payload = JSON.stringify(request.body);
281
+
282
+ // Verify webhook signature (optional but recommended)
283
+ if (WEBHOOK_SECRET && !verifySignature(payload, signature, WEBHOOK_SECRET)) {
284
+ console.log('Invalid signature');
285
+ return reply.code(400).send({ error: 'Invalid signature' });
286
+ }
287
+
288
+ const { data } = request.body;
289
+ const eventType = data.attributes.type;
290
+
291
+ switch (eventType) {${eventHandlers}
292
+ default:
293
+ console.log('Unhandled event type:', eventType);
294
+ }
295
+
296
+ return { received: true };
297
+ } catch (error) {
298
+ console.error('Webhook processing error:', error);
299
+ return reply.code(500).send({ error: 'Internal server error' });
300
+ }
301
+ });
302
+
303
+ const start = async () => {
304
+ try {
305
+ await fastify.listen({ port: process.env.PORT || 3000 });
306
+ } catch (err) {
307
+ fastify.log.error(err);
308
+ process.exit(1);
309
+ }
310
+ };
311
+
312
+ start();`;
313
+ default:
314
+ return `// Simple webhook handler for ${events.join(', ')}
315
+
316
+ const crypto = require('crypto');
317
+
318
+ // Webhook secret from PayMongo dashboard
319
+ const WEBHOOK_SECRET = process.env.PAYMONGO_WEBHOOK_SECRET;
320
+
321
+ function verifySignature(payload, signature, secret) {
322
+ const expectedSignature = crypto
323
+ .createHmac('sha256', secret)
324
+ .update(payload, 'utf8')
325
+ .digest('hex');
326
+
327
+ return signature === \`sha256=\${expectedSignature}\`;
328
+ }
329
+
330
+ function handleWebhook(request, response) {
331
+ try {
332
+ const signature = request.headers['paymongo-signature'];
333
+ const payload = JSON.stringify(request.body);
334
+
335
+ // Verify webhook signature (optional but recommended)
336
+ if (WEBHOOK_SECRET && !verifySignature(payload, signature, WEBHOOK_SECRET)) {
337
+ console.log('Invalid signature');
338
+ response.writeHead(400, { 'Content-Type': 'application/json' });
339
+ response.end(JSON.stringify({ error: 'Invalid signature' }));
340
+ return;
341
+ }
342
+
343
+ const { data } = request.body;
344
+ const eventType = data.attributes.type;
345
+
346
+ switch (eventType) {${eventHandlers}
347
+ default:
348
+ console.log('Unhandled event type:', eventType);
349
+ }
350
+
351
+ response.writeHead(200, { 'Content-Type': 'application/json' });
352
+ response.end(JSON.stringify({ received: true }));
353
+ } catch (error) {
354
+ console.error('Webhook processing error:', error);
355
+ response.writeHead(500, { 'Content-Type': 'application/json' });
356
+ response.end(JSON.stringify({ error: 'Internal server error' }));
357
+ }
358
+ }
359
+
360
+ module.exports = { handleWebhook };`;
361
+ }
362
+ }
363
+ function generateTypeScriptWebhookHandler(events, framework) {
364
+ const eventHandlers = events.map(event => `
365
+ case '${event}':
366
+ console.log('Processing ${event} event:', data);
367
+ // Add your ${event} handling logic here
368
+ break;`).join('');
369
+ switch (framework) {
370
+ case 'express':
371
+ return `import express, { Request, Response } from 'express';
372
+ import crypto from 'crypto';
373
+
374
+ const app = express();
375
+ app.use(express.json());
376
+
377
+ // Webhook secret from PayMongo dashboard
378
+ const WEBHOOK_SECRET = process.env.PAYMONGO_WEBHOOK_SECRET;
379
+
380
+ interface PayMongoWebhookPayload {
381
+ data: {
382
+ id: string;
383
+ type: string;
384
+ attributes: {
385
+ type: string;
386
+ livemode: boolean;
387
+ created_at: number;
388
+ updated_at: number;
389
+ data: any;
390
+ };
391
+ };
392
+ }
393
+
394
+ function verifySignature(payload: string, signature: string, secret: string): boolean {
395
+ const expectedSignature = crypto
396
+ .createHmac('sha256', secret)
397
+ .update(payload, 'utf8')
398
+ .digest('hex');
399
+
400
+ return signature === \`sha256=\${expectedSignature}\`;
401
+ }
402
+
403
+ app.post('/webhooks/paymongo', (req: Request, res: Response) => {
404
+ try {
405
+ const signature = req.headers['paymongo-signature'] as string;
406
+ const payload = JSON.stringify(req.body);
407
+
408
+ // Verify webhook signature (optional but recommended)
409
+ if (WEBHOOK_SECRET && !verifySignature(payload, signature, WEBHOOK_SECRET)) {
410
+ console.log('Invalid signature');
411
+ return res.status(400).json({ error: 'Invalid signature' });
412
+ }
413
+
414
+ const { data }: PayMongoWebhookPayload = req.body;
415
+ const eventType = data.attributes.type;
416
+
417
+ switch (eventType) {${eventHandlers}
418
+ default:
419
+ console.log('Unhandled event type:', eventType);
420
+ }
421
+
422
+ res.json({ received: true });
423
+ } catch (error) {
424
+ console.error('Webhook processing error:', error);
425
+ res.status(500).json({ error: 'Internal server error' });
426
+ }
427
+ });
428
+
429
+ const PORT = process.env.PORT || 3000;
430
+ app.listen(PORT, () => {
431
+ console.log(\`Webhook server running on port \${PORT}\`);
432
+ });`;
433
+ default:
434
+ return `// TypeScript webhook handler for ${events.join(', ')}
435
+
436
+ import crypto from 'crypto';
437
+
438
+ const WEBHOOK_SECRET = process.env.PAYMONGO_WEBHOOK_SECRET;
439
+
440
+ interface PayMongoWebhookPayload {
441
+ data: {
442
+ id: string;
443
+ type: string;
444
+ attributes: {
445
+ type: string;
446
+ livemode: boolean;
447
+ created_at: number;
448
+ updated_at: number;
449
+ data: any;
450
+ };
451
+ };
452
+ }
453
+
454
+ function verifySignature(payload: string, signature: string, secret: string): boolean {
455
+ const expectedSignature = crypto
456
+ .createHmac('sha256', secret)
457
+ .update(payload, 'utf8')
458
+ .digest('hex');
459
+
460
+ return signature === \`sha256=\${expectedSignature}\`;
461
+ }
462
+
463
+ export function handleWebhook(body: PayMongoWebhookPayload, signature?: string): { received: boolean } {
464
+ try {
465
+ const payload = JSON.stringify(body);
466
+
467
+ // Verify webhook signature (optional but recommended)
468
+ if (WEBHOOK_SECRET && signature && !verifySignature(payload, signature, WEBHOOK_SECRET)) {
469
+ console.log('Invalid signature');
470
+ throw new Error('Invalid signature');
471
+ }
472
+
473
+ const { data } = body;
474
+ const eventType = data.attributes.type;
475
+
476
+ switch (eventType) {${eventHandlers}
477
+ default:
478
+ console.log('Unhandled event type:', eventType);
479
+ }
480
+
481
+ return { received: true };
482
+ } catch (error) {
483
+ console.error('Webhook processing error:', error);
484
+ throw error;
485
+ }
486
+ }`;
487
+ }
488
+ }
489
+ function generateJavaScriptPaymentIntent(methods) {
490
+ return `const axios = require('axios');
491
+
492
+ // PayMongo API credentials
493
+ const PAYMONGO_SECRET_KEY = process.env.PAYMONGO_SECRET_KEY;
494
+ const PAYMONGO_PUBLIC_KEY = process.env.PAYMONGO_PUBLIC_KEY;
495
+
496
+ async function createPaymentIntent(amount, currency = 'PHP', description = '') {
497
+ try {
498
+ const response = await axios.post(
499
+ 'https://api.paymongo.com/v1/payment_intents',
500
+ {
501
+ data: {
502
+ attributes: {
503
+ amount: amount, // Amount in centavos (e.g., 10000 = ₱100.00)
504
+ currency: currency,
505
+ description: description,
506
+ payment_method_allowed: ${JSON.stringify(methods)},
507
+ }
508
+ }
509
+ },
510
+ {
511
+ headers: {
512
+ 'Authorization': \`Basic \${Buffer.from(PAYMONGO_SECRET_KEY + ':').toString('base64')}\`,
513
+ 'Content-Type': 'application/json',
514
+ }
515
+ }
516
+ );
517
+
518
+ const paymentIntent = response.data.data;
519
+
520
+ console.log('Payment Intent created:', paymentIntent.id);
521
+ console.log('Client Key:', paymentIntent.attributes.client_key);
522
+ console.log('Amount:', (paymentIntent.attributes.amount / 100).toFixed(2), paymentIntent.attributes.currency);
523
+
524
+ return {
525
+ id: paymentIntent.id,
526
+ clientKey: paymentIntent.attributes.client_key,
527
+ amount: paymentIntent.attributes.amount,
528
+ currency: paymentIntent.attributes.currency,
529
+ status: paymentIntent.attributes.status
530
+ };
531
+
532
+ } catch (error) {
533
+ console.error('Error creating payment intent:', error.response?.data || error.message);
534
+ throw error;
535
+ }
536
+ }
537
+
538
+ // Example usage
539
+ async function example() {
540
+ try {
541
+ const paymentIntent = await createPaymentIntent(
542
+ 10000, // ₱100.00
543
+ 'PHP',
544
+ 'Sample payment'
545
+ );
546
+
547
+ console.log('Use this client key in your frontend:', paymentIntent.clientKey);
548
+
549
+ } catch (error) {
550
+ console.error('Failed to create payment intent');
551
+ }
552
+ }
553
+
554
+ module.exports = { createPaymentIntent };
555
+
556
+ if (require.main === module) {
557
+ example();
558
+ }`;
559
+ }
560
+ function generateTypeScriptPaymentIntent(methods) {
561
+ return `import axios from 'axios';
562
+
563
+ interface PaymentIntent {
564
+ id: string;
565
+ clientKey: string;
566
+ amount: number;
567
+ currency: string;
568
+ status: string;
569
+ }
570
+
571
+ interface PayMongoPaymentIntentResponse {
572
+ data: {
573
+ id: string;
574
+ attributes: {
575
+ amount: number;
576
+ currency: string;
577
+ description: string;
578
+ status: string;
579
+ client_key: string;
580
+ payment_method_allowed: string[];
581
+ };
582
+ };
583
+ }
584
+
585
+ // PayMongo API credentials
586
+ const PAYMONGO_SECRET_KEY = process.env.PAYMONGO_SECRET_KEY!;
587
+ const PAYMONGO_PUBLIC_KEY = process.env.PAYMONGO_PUBLIC_KEY!;
588
+
589
+ export async function createPaymentIntent(
590
+ amount: number,
591
+ currency: string = 'PHP',
592
+ description: string = ''
593
+ ): Promise<PaymentIntent> {
594
+ try {
595
+ const response = await axios.post<PayMongoPaymentIntentResponse>(
596
+ 'https://api.paymongo.com/v1/payment_intents',
597
+ {
598
+ data: {
599
+ attributes: {
600
+ amount: amount, // Amount in centavos (e.g., 10000 = ₱100.00)
601
+ currency: currency,
602
+ description: description,
603
+ payment_method_allowed: ${JSON.stringify(methods)},
604
+ }
605
+ }
606
+ },
607
+ {
608
+ headers: {
609
+ 'Authorization': \`Basic \${Buffer.from(PAYMONGO_SECRET_KEY + ':').toString('base64')}\`,
610
+ 'Content-Type': 'application/json',
611
+ }
612
+ }
613
+ );
614
+
615
+ const paymentIntent = response.data.data;
616
+
617
+ console.log('Payment Intent created:', paymentIntent.id);
618
+ console.log('Client Key:', paymentIntent.attributes.client_key);
619
+ console.log('Amount:', (paymentIntent.attributes.amount / 100).toFixed(2), paymentIntent.attributes.currency);
620
+
621
+ return {
622
+ id: paymentIntent.id,
623
+ clientKey: paymentIntent.attributes.client_key,
624
+ amount: paymentIntent.attributes.amount,
625
+ currency: paymentIntent.attributes.currency,
626
+ status: paymentIntent.attributes.status
627
+ };
628
+
629
+ } catch (error: any) {
630
+ console.error('Error creating payment intent:', error.response?.data || error.message);
631
+ throw error;
632
+ }
633
+ }
634
+
635
+ // Example usage
636
+ async function example(): Promise<void> {
637
+ try {
638
+ const paymentIntent = await createPaymentIntent(
639
+ 10000, // ₱100.00
640
+ 'PHP',
641
+ 'Sample payment'
642
+ );
643
+
644
+ console.log('Use this client key in your frontend:', paymentIntent.clientKey);
645
+
646
+ } catch (error) {
647
+ console.error('Failed to create payment intent');
648
+ }
649
+ }
650
+
651
+ if (require.main === module) {
652
+ example();
653
+ }`;
654
+ }
655
+ function generateHTMLCheckoutPage() {
656
+ return `<!DOCTYPE html>
657
+ <html lang="en">
658
+ <head>
659
+ <meta charset="UTF-8">
660
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
661
+ <title>PayMongo Checkout</title>
662
+ <script src="https://js.paymongo.com/v1/paymongo.js"></script>
663
+ <style>
664
+ body {
665
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
666
+ max-width: 400px;
667
+ margin: 50px auto;
668
+ padding: 20px;
669
+ }
670
+ .checkout-form {
671
+ background: #f9f9f9;
672
+ padding: 20px;
673
+ border-radius: 8px;
674
+ }
675
+ .form-group {
676
+ margin-bottom: 15px;
677
+ }
678
+ label {
679
+ display: block;
680
+ margin-bottom: 5px;
681
+ font-weight: 500;
682
+ }
683
+ input, select {
684
+ width: 100%;
685
+ padding: 10px;
686
+ border: 1px solid #ddd;
687
+ border-radius: 4px;
688
+ font-size: 16px;
689
+ }
690
+ button {
691
+ width: 100%;
692
+ padding: 12px;
693
+ background: #007bff;
694
+ color: white;
695
+ border: none;
696
+ border-radius: 4px;
697
+ font-size: 16px;
698
+ cursor: pointer;
699
+ }
700
+ button:hover {
701
+ background: #0056b3;
702
+ }
703
+ button:disabled {
704
+ background: #ccc;
705
+ cursor: not-allowed;
706
+ }
707
+ </style>
708
+ </head>
709
+ <body>
710
+ <div class="checkout-form">
711
+ <h2>Complete Your Payment</h2>
712
+ <form id="payment-form">
713
+ <div class="form-group">
714
+ <label for="email">Email</label>
715
+ <input type="email" id="email" required>
716
+ </div>
717
+
718
+ <div class="form-group">
719
+ <label for="card-number">Card Number</label>
720
+ <input type="text" id="card-number" placeholder="1234 5678 9012 3456" required>
721
+ </div>
722
+
723
+ <div class="form-group">
724
+ <label for="expiry">Expiry Date</label>
725
+ <input type="text" id="expiry" placeholder="MM/YY" required>
726
+ </div>
727
+
728
+ <div class="form-group">
729
+ <label for="cvc">CVC</label>
730
+ <input type="text" id="cvc" placeholder="123" required>
731
+ </div>
732
+
733
+ <button type="submit" id="pay-button">Pay ₱100.00</button>
734
+ </form>
735
+ </div>
736
+
737
+ <script>
738
+ // Replace with your actual client key from the payment intent
739
+ const clientKey = 'YOUR_CLIENT_KEY_HERE';
740
+
741
+ const paymongo = new Paymongo(clientKey);
742
+
743
+ document.getElementById('payment-form').addEventListener('submit', async (e) => {
744
+ e.preventDefault();
745
+
746
+ const payButton = document.getElementById('pay-button');
747
+ payButton.disabled = true;
748
+ payButton.textContent = 'Processing...';
749
+
750
+ try {
751
+ // Create payment method
752
+ const paymentMethod = await paymongo.createPaymentMethod({
753
+ type: 'card',
754
+ details: {
755
+ card_number: document.getElementById('card-number').value.replace(/\\s/g, ''),
756
+ exp_month: document.getElementById('expiry').value.split('/')[0],
757
+ exp_year: '20' + document.getElementById('expiry').value.split('/')[1],
758
+ cvc: document.getElementById('cvc').value,
759
+ },
760
+ billing: {
761
+ email: document.getElementById('email').value,
762
+ },
763
+ });
764
+
765
+ // Attach payment method to payment intent
766
+ const result = await paymongo.attachPaymentIntent('YOUR_PAYMENT_INTENT_ID', {
767
+ payment_method: paymentMethod.id,
768
+ return_url: window.location.origin + '/success',
769
+ });
770
+
771
+ if (result.next_action) {
772
+ // Handle 3D Secure or other next actions
773
+ window.location.href = result.next_action.redirect.url;
774
+ } else {
775
+ // Payment succeeded
776
+ window.location.href = '/success';
777
+ }
778
+
779
+ } catch (error) {
780
+ console.error('Payment failed:', error);
781
+ alert('Payment failed. Please try again.');
782
+ payButton.disabled = false;
783
+ payButton.textContent = 'Pay ₱100.00';
784
+ }
785
+ });
786
+ </script>
787
+ </body>
788
+ </html>`;
789
+ }
790
+ function generateReactCheckoutPage() {
791
+ return `import React, { useState } from 'react';
792
+
793
+ interface CheckoutFormProps {
794
+ clientKey: string;
795
+ paymentIntentId: string;
796
+ amount: number;
797
+ onSuccess: (result: any) => void;
798
+ onError: (error: any) => void;
799
+ }
800
+
801
+ const CheckoutForm: React.FC<CheckoutFormProps> = ({
802
+ clientKey,
803
+ paymentIntentId,
804
+ amount,
805
+ onSuccess,
806
+ onError
807
+ }) => {
808
+ const [loading, setLoading] = useState(false);
809
+ const [formData, setFormData] = useState({
810
+ email: '',
811
+ cardNumber: '',
812
+ expiry: '',
813
+ cvc: ''
814
+ });
815
+
816
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
817
+ const { name, value } = e.target;
818
+ setFormData(prev => ({
819
+ ...prev,
820
+ [name]: value
821
+ }));
822
+ };
823
+
824
+ const handleSubmit = async (e: React.FormEvent) => {
825
+ e.preventDefault();
826
+ setLoading(true);
827
+
828
+ try {
829
+ // Load PayMongo script dynamically if not already loaded
830
+ if (!window.Paymongo) {
831
+ await new Promise((resolve, reject) => {
832
+ const script = document.createElement('script');
833
+ script.src = 'https://js.paymongo.com/v1/paymongo.js';
834
+ script.onload = resolve;
835
+ script.onerror = reject;
836
+ document.head.appendChild(script);
837
+ });
838
+ }
839
+
840
+ const paymongo = new (window as any).Paymongo(clientKey);
841
+
842
+ // Create payment method
843
+ const paymentMethod = await paymongo.createPaymentMethod({
844
+ type: 'card',
845
+ details: {
846
+ card_number: formData.cardNumber.replace(/\\s/g, ''),
847
+ exp_month: parseInt(formData.expiry.split('/')[0]),
848
+ exp_year: 2000 + parseInt(formData.expiry.split('/')[1]),
849
+ cvc: formData.cvc,
850
+ },
851
+ billing: {
852
+ email: formData.email,
853
+ },
854
+ });
855
+
856
+ // Attach payment method to payment intent
857
+ const result = await paymongo.attachPaymentIntent(paymentIntentId, {
858
+ payment_method: paymentMethod.id,
859
+ return_url: window.location.origin + '/success',
860
+ });
861
+
862
+ onSuccess(result);
863
+
864
+ } catch (error) {
865
+ onError(error);
866
+ } finally {
867
+ setLoading(false);
868
+ }
869
+ };
870
+
871
+ return (
872
+ <div style={{ maxWidth: '400px', margin: '50px auto', padding: '20px' }}>
873
+ <div style={{
874
+ background: '#f9f9f9',
875
+ padding: '20px',
876
+ borderRadius: '8px'
877
+ }}>
878
+ <h2>Complete Your Payment</h2>
879
+ <form onSubmit={handleSubmit}>
880
+ <div style={{ marginBottom: '15px' }}>
881
+ <label style={{ display: 'block', marginBottom: '5px', fontWeight: '500' }}>
882
+ Email
883
+ </label>
884
+ <input
885
+ type="email"
886
+ name="email"
887
+ value={formData.email}
888
+ onChange={handleInputChange}
889
+ required
890
+ style={{
891
+ width: '100%',
892
+ padding: '10px',
893
+ border: '1px solid #ddd',
894
+ borderRadius: '4px',
895
+ fontSize: '16px'
896
+ }}
897
+ />
898
+ </div>
899
+
900
+ <div style={{ marginBottom: '15px' }}>
901
+ <label style={{ display: 'block', marginBottom: '5px', fontWeight: '500' }}>
902
+ Card Number
903
+ </label>
904
+ <input
905
+ type="text"
906
+ name="cardNumber"
907
+ value={formData.cardNumber}
908
+ onChange={handleInputChange}
909
+ placeholder="1234 5678 9012 3456"
910
+ required
911
+ style={{
912
+ width: '100%',
913
+ padding: '10px',
914
+ border: '1px solid #ddd',
915
+ borderRadius: '4px',
916
+ fontSize: '16px'
917
+ }}
918
+ />
919
+ </div>
920
+
921
+ <div style={{ marginBottom: '15px' }}>
922
+ <label style={{ display: 'block', marginBottom: '5px', fontWeight: '500' }}>
923
+ Expiry Date
924
+ </label>
925
+ <input
926
+ type="text"
927
+ name="expiry"
928
+ value={formData.expiry}
929
+ onChange={handleInputChange}
930
+ placeholder="MM/YY"
931
+ required
932
+ style={{
933
+ width: '100%',
934
+ padding: '10px',
935
+ border: '1px solid #ddd',
936
+ borderRadius: '4px',
937
+ fontSize: '16px'
938
+ }}
939
+ />
940
+ </div>
941
+
942
+ <div style={{ marginBottom: '15px' }}>
943
+ <label style={{ display: 'block', marginBottom: '5px', fontWeight: '500' }}>
944
+ CVC
945
+ </label>
946
+ <input
947
+ type="text"
948
+ name="cvc"
949
+ value={formData.cvc}
950
+ onChange={handleInputChange}
951
+ placeholder="123"
952
+ required
953
+ style={{
954
+ width: '100%',
955
+ padding: '10px',
956
+ border: '1px solid #ddd',
957
+ borderRadius: '4px',
958
+ fontSize: '16px'
959
+ }}
960
+ />
961
+ </div>
962
+
963
+ <button
964
+ type="submit"
965
+ disabled={loading}
966
+ style={{
967
+ width: '100%',
968
+ padding: '12px',
969
+ background: loading ? '#ccc' : '#007bff',
970
+ color: 'white',
971
+ border: 'none',
972
+ borderRadius: '4px',
973
+ fontSize: '16px',
974
+ cursor: loading ? 'not-allowed' : 'pointer'
975
+ }}
976
+ >
977
+ {loading ? 'Processing...' : \`Pay ₱\${(amount / 100).toFixed(2)}\`}
978
+ </button>
979
+ </form>
980
+ </div>
981
+ </div>
982
+ );
983
+ };
984
+
985
+ export default CheckoutForm;`;
986
+ }
987
+ function generateVueCheckoutPage() {
988
+ return `<template>
989
+ <div class="checkout-container">
990
+ <div class="checkout-form">
991
+ <h2>Complete Your Payment</h2>
992
+ <form @submit.prevent="handleSubmit">
993
+ <div class="form-group">
994
+ <label for="email">Email</label>
995
+ <input
996
+ v-model="formData.email"
997
+ type="email"
998
+ id="email"
999
+ required
1000
+ >
1001
+ </div>
1002
+
1003
+ <div class="form-group">
1004
+ <label for="cardNumber">Card Number</label>
1005
+ <input
1006
+ v-model="formData.cardNumber"
1007
+ type="text"
1008
+ id="cardNumber"
1009
+ placeholder="1234 5678 9012 3456"
1010
+ required
1011
+ >
1012
+ </div>
1013
+
1014
+ <div class="form-group">
1015
+ <label for="expiry">Expiry Date</label>
1016
+ <input
1017
+ v-model="formData.expiry"
1018
+ type="text"
1019
+ id="expiry"
1020
+ placeholder="MM/YY"
1021
+ required
1022
+ >
1023
+ </div>
1024
+
1025
+ <div class="form-group">
1026
+ <label for="cvc">CVC</label>
1027
+ <input
1028
+ v-model="formData.cvc"
1029
+ type="text"
1030
+ id="cvc"
1031
+ placeholder="123"
1032
+ required
1033
+ >
1034
+ </div>
1035
+
1036
+ <button
1037
+ type="submit"
1038
+ :disabled="loading"
1039
+ class="pay-button"
1040
+ >
1041
+ {{ loading ? 'Processing...' : \`Pay ₱\${(amount / 100).toFixed(2)}\` }}
1042
+ </button>
1043
+ </form>
1044
+ </div>
1045
+ </div>
1046
+ </template>
1047
+
1048
+ <script>
1049
+ export default {
1050
+ name: 'CheckoutForm',
1051
+ props: {
1052
+ clientKey: {
1053
+ type: String,
1054
+ required: true
1055
+ },
1056
+ paymentIntentId: {
1057
+ type: String,
1058
+ required: true
1059
+ },
1060
+ amount: {
1061
+ type: Number,
1062
+ required: true
1063
+ }
1064
+ },
1065
+ data() {
1066
+ return {
1067
+ loading: false,
1068
+ formData: {
1069
+ email: '',
1070
+ cardNumber: '',
1071
+ expiry: '',
1072
+ cvc: ''
1073
+ }
1074
+ };
1075
+ },
1076
+ methods: {
1077
+ async handleSubmit() {
1078
+ this.loading = true;
1079
+
1080
+ try {
1081
+ // Load PayMongo script if not loaded
1082
+ if (!window.Paymongo) {
1083
+ await this.loadPayMongoScript();
1084
+ }
1085
+
1086
+ const paymongo = new window.Paymongo(this.clientKey);
1087
+
1088
+ // Create payment method
1089
+ const paymentMethod = await paymongo.createPaymentMethod({
1090
+ type: 'card',
1091
+ details: {
1092
+ card_number: this.formData.cardNumber.replace(/\\s/g, ''),
1093
+ exp_month: parseInt(this.formData.expiry.split('/')[0]),
1094
+ exp_year: 2000 + parseInt(this.formData.expiry.split('/')[1]),
1095
+ cvc: this.formData.cvc,
1096
+ },
1097
+ billing: {
1098
+ email: this.formData.email,
1099
+ },
1100
+ });
1101
+
1102
+ // Attach payment method to payment intent
1103
+ const result = await paymongo.attachPaymentIntent(this.paymentIntentId, {
1104
+ payment_method: paymentMethod.id,
1105
+ return_url: window.location.origin + '/success',
1106
+ });
1107
+
1108
+ this.$emit('success', result);
1109
+
1110
+ } catch (error) {
1111
+ console.error('Payment failed:', error);
1112
+ this.$emit('error', error);
1113
+ } finally {
1114
+ this.loading = false;
1115
+ }
1116
+ },
1117
+
1118
+ loadPayMongoScript() {
1119
+ return new Promise((resolve, reject) => {
1120
+ const script = document.createElement('script');
1121
+ script.src = 'https://js.paymongo.com/v1/paymongo.js';
1122
+ script.onload = resolve;
1123
+ script.onerror = reject;
1124
+ document.head.appendChild(script);
1125
+ });
1126
+ }
1127
+ }
1128
+ };
1129
+ </script>
1130
+
1131
+ <style scoped>
1132
+ .checkout-container {
1133
+ max-width: 400px;
1134
+ margin: 50px auto;
1135
+ padding: 20px;
1136
+ }
1137
+
1138
+ .checkout-form {
1139
+ background: #f9f9f9;
1140
+ padding: 20px;
1141
+ border-radius: 8px;
1142
+ }
1143
+
1144
+ .form-group {
1145
+ margin-bottom: 15px;
1146
+ }
1147
+
1148
+ label {
1149
+ display: block;
1150
+ margin-bottom: 5px;
1151
+ font-weight: 500;
1152
+ }
1153
+
1154
+ input {
1155
+ width: 100%;
1156
+ padding: 10px;
1157
+ border: 1px solid #ddd;
1158
+ border-radius: 4px;
1159
+ font-size: 16px;
1160
+ box-sizing: border-box;
1161
+ }
1162
+
1163
+ .pay-button {
1164
+ width: 100%;
1165
+ padding: 12px;
1166
+ background: #007bff;
1167
+ color: white;
1168
+ border: none;
1169
+ border-radius: 4px;
1170
+ font-size: 16px;
1171
+ cursor: pointer;
1172
+ }
1173
+
1174
+ .pay-button:hover:not(:disabled) {
1175
+ background: #0056b3;
1176
+ }
1177
+
1178
+ .pay-button:disabled {
1179
+ background: #ccc;
1180
+ cursor: not-allowed;
1181
+ }
1182
+ </style>`;
1183
+ }
1184
+ export default command;