dodopayments-cli 2.0.3 → 2.1.0

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/index.ts DELETED
@@ -1,490 +0,0 @@
1
- #!/usr/bin/env node
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import DodoPayments from 'dodopayments';
5
- import { input, select } from '@inquirer/prompts';
6
- import open from 'open';
7
- import { CurrencyToSymbolMap } from './utils/currency-to-symbol-map';
8
- import fs from 'node:fs';
9
-
10
- const DodoCliLogo = `
11
- /$$$$$$$ /$$ /$$$$$$ /$$ /$$$$$$
12
- | $$__ $$ | $$ /$$__ $$| $$ |_ $$_/
13
- | $$ \\ $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ \\__/| $$ | $$
14
- | $$ | $$ /$$__ $$ /$$__ $$ /$$__ $$ | $$ | $$ | $$
15
- | $$ | $$| $$ \\ $$| $$ | $$| $$ \\ $$ | $$ | $$ | $$
16
- | $$ | $$| $$ | $$| $$ | $$| $$ | $$ | $$ $$| $$ | $$
17
- | $$$$$$$/| $$$$$$/| $$$$$$$| $$$$$$/ | $$$$$$/| $$$$$$$$ /$$$$$$
18
- |_______/ \\______/ \\_______/ \\______/ \\______/ |________/|______/
19
-
20
- The CLI to manage Dodo Payments!
21
- `;
22
-
23
- // The below is used to check if the error is a Dodo Payments error or not in the API Request
24
- type DodoPaymentsAPIError = {
25
- error: {
26
- code: 'NOT_FOUND';
27
- message: string;
28
- }
29
- }
30
-
31
- function isDodoPaymentsAPIError(e: unknown): e is DodoPaymentsAPIError {
32
- return (
33
- typeof e === "object" &&
34
- e !== null &&
35
- "error" in e &&
36
- typeof (e as any).error?.code === "string"
37
- );
38
- }
39
-
40
- // For help commands
41
- const usage: {
42
- [key: string]: {
43
- command: string,
44
- description: string
45
- }[]
46
- } = {
47
- products: [
48
- { command: 'list', description: 'List your products' },
49
- { command: 'create', description: 'Create a new product' },
50
- { command: 'info', description: 'Get info about a product' }
51
- ],
52
- payments: [
53
- { command: 'list', description: 'List your payments' },
54
- { command: 'info', description: 'Information about a payment' },
55
- ],
56
- customers: [
57
- { command: 'list', description: 'List your customers' },
58
- { command: 'create', description: 'Create a customer' },
59
- { command: 'update', description: 'Update a customer' },
60
- ],
61
- discounts: [
62
- { command: 'list', description: 'List your discounts' },
63
- { command: 'create', description: 'Create a discount' },
64
- { command: 'delete', description: 'Remove a discount' },
65
- ],
66
- wh: [
67
- { command: '', description: 'Send a webhook event' },
68
- ]
69
- }
70
-
71
-
72
- const args = process.argv;
73
- const category = args[2];
74
- const subCommand = args[3];
75
- const homedir = os.homedir();
76
-
77
- const RenderHelp = () => {
78
- console.log(DodoCliLogo);
79
- // List all available methods
80
- // todo: Add more comments to make it clear what's being done
81
- Object.keys(usage).forEach(e => {
82
- console.log(`Category: ${e}`);
83
- (usage as any)[e].forEach((y: { command: string, description: string }) => {
84
- console.log(`dodo ${e} ${y.command} - ${y.description}`)
85
- });
86
- // Blank space as a separator
87
- console.log("");
88
- });
89
- }
90
-
91
- // Added this to the top so that it can bypass all further auth that happens for the login route
92
- if (category === 'login') {
93
- open('https://app.dodopayments.com/developer/api-keys');
94
- const API_KEY = await input({ message: 'Enter your Dodo Payments API Key:', required: true });
95
- const MODE = await select({
96
- choices: [{ name: "Test Mode", value: 'test_mode' }, { name: "Live Mode", value: 'live_mode' }],
97
- message: 'Choose the environment:'
98
- });
99
-
100
- // Initialize the Dodo Payment client to test the API key
101
- const newDodoClient = new DodoPayments({
102
- bearerToken: API_KEY,
103
- environment: (MODE as 'test_mode' | 'live_mode') // as 'test_mode' | 'live_mode' used to bypass ts error
104
- });
105
-
106
- console.log("Verifying Dodo Payments API Key");
107
-
108
- try {
109
- // Make this request just to confirm whether API key is correct or not
110
- await newDodoClient.products.list({ page_size: 1 });
111
- console.log('Successfully verified your Dodo Payments API Key!');
112
- } catch (err) {
113
- console.log("Something went wrong while authenticating:", err);
114
- process.exit(1);
115
- };
116
-
117
-
118
- console.log("Storing / Updating existing configuration...")
119
- let existingConfig;
120
- try {
121
- existingConfig = Object.create(JSON.parse(fs.readFileSync(path.join(homedir, '.dodopayments', 'api-key'), 'utf-8')));
122
- } catch {
123
- existingConfig = {};
124
- }
125
-
126
- existingConfig[MODE] = API_KEY;
127
- // Make the ~/.dodopayments directory if it's not present
128
- if (!fs.existsSync(path.join(homedir, '.dodopayments'))) {
129
- fs.mkdirSync(path.join(homedir, '.dodopayments'));
130
- }
131
- fs.writeFileSync(path.join(homedir, '.dodopayments', 'api-key'), JSON.stringify(existingConfig));
132
-
133
- // Mode will always be either test_mode or live_mode
134
- existingConfig[MODE] = API_KEY;
135
- console.log("Setup complete successfully!");
136
- process.exit(0);
137
- }
138
-
139
- // Webhook is managed completely by another file
140
- if (category === 'wh') {
141
- await import('./dodo-webhooks/index.ts');
142
- }
143
-
144
- // Normal functions which require the API key to be present start from here
145
- // Authentication part
146
- // Read the API key config
147
- if (!fs.existsSync(path.join(homedir, '.dodopayments', 'api-key'))) {
148
- if (category && subCommand) {
149
- console.log('Please login using `dodo login` command first!');
150
- process.exit(0);
151
- } else if (category) {
152
- if (category in usage) {
153
- console.log(`Category: ${category}`);
154
- (usage as any)[category]!.forEach((e: { command: string, description: string }) => {
155
- console.log(`dodo ${category} ${e.command} - ${e.description}`)
156
- });
157
- console.log('\nPlease login using `dodo login` command first!');
158
- } else {
159
- RenderHelp();
160
- }
161
- process.exit(0);
162
- } else {
163
- RenderHelp();
164
- console.log('Please login using `dodo login` command first!');
165
- process.exit(0);
166
- }
167
- }
168
-
169
- // Parse the API key config
170
- let existingAPIKeyConfigParsed;
171
- try {
172
- existingAPIKeyConfigParsed = JSON.parse(fs.readFileSync(path.join(homedir, '.dodopayments', 'api-key'), 'utf-8'));
173
- } catch {
174
- // Delete API config if something fails with parsing
175
- fs.rmSync(path.join(homedir, '.dodopayments', 'api-key'), { force: true });
176
- console.log("Failed to decode API Key configuration. Your config has been reset. Please log in again using `dodo login`");
177
- process.exit(0);
178
- }
179
-
180
-
181
- // Final variables
182
- let API_KEY;
183
- let MODE;
184
-
185
- // Retrive the keys of the parsed API key config to auto determine the environment if possible.
186
- const existingAPIKeyConfigParsedKeys = Object.keys(existingAPIKeyConfigParsed);
187
-
188
- // If there is only one mode auth mehtod then
189
- if (existingAPIKeyConfigParsedKeys.length === 1) {
190
- MODE = existingAPIKeyConfigParsedKeys[0]
191
- API_KEY = existingAPIKeyConfigParsed[MODE!];
192
- }
193
- else {
194
- // If there are multiple modes (i.e. both test mode & live mode) then prompt the user to select one environment to continue
195
- MODE = await select({
196
- choices: [{ name: "Test Mode", value: 'test_mode' }, { name: "Live Mode", value: 'live_mode' }],
197
- message: 'Choose the environment:'
198
- });
199
- API_KEY = existingAPIKeyConfigParsed[MODE];
200
- }
201
-
202
- // Initialize the Dodo Payments SDK to be used from now on
203
- const DodoClient = new DodoPayments({
204
- bearerToken: API_KEY,
205
- environment: (MODE as 'test_mode' | 'live_mode') // as 'test_mode' | 'live_mode' used to bypass ts error
206
- });
207
-
208
- // Continuation of other functions that require api key
209
- if (category === 'products') {
210
- if (subCommand === 'list') {
211
- const page = await input({
212
- message: 'Enter page:',
213
- default: "1",
214
- validate: (e => e.trim() !== '')
215
- });
216
-
217
- const fetchedData = await DodoClient.products.list({ page_number: parseInt(page) - 1, page_size: 100 });
218
- const table = fetchedData.items.map(e => ({
219
- name: e.name,
220
- product_id: e.product_id,
221
- created_at: new Date(e.created_at).toLocaleString(),
222
- ...e.is_recurring ? {
223
- price: `${CurrencyToSymbolMap[e.price_detail!.currency] || (e.price_detail!.currency + ' ')}${(e.price! * 0.01).toFixed(2)} Every ${e.price_detail?.payment_frequency_count} ${e.price_detail?.payment_frequency_interval}`,
224
- } : {
225
- price: `${CurrencyToSymbolMap[e.price_detail!.currency] || (e.price_detail!.currency + ' ')}${(e.price! * 0.01).toFixed(2)} (One Time)`,
226
- },
227
- }));
228
-
229
- console.table(table);
230
- console.log("To edit a product, go to https://app.dodopayments.com/products/edit?id={product_id}")
231
- } else if (subCommand === 'create') {
232
- open('https://app.dodopayments.com/products/create');
233
- } else if (subCommand === 'info') {
234
- try {
235
- const product_id = await input({
236
- message: "Enter product ID:",
237
- validate: (e => e.startsWith('pdt_') || 'Please enter a valid product ID!')
238
- });
239
-
240
- const info = await DodoClient.products.retrieve(product_id);
241
- console.table({
242
- product_id: info.product_id,
243
- name: info.name,
244
- ...info.description?.trim() !== '' && { description: info.description },
245
- created_at: new Date(info.created_at).toLocaleString(),
246
- updated_at: new Date(info.updated_at).toLocaleString(),
247
- ...info.is_recurring ? {
248
- // .fixed_price for usage based billing
249
- price: `${CurrencyToSymbolMap[info.price.currency] || (info.price.currency + ' ')}${((info.price.price || info.price.fixed_price) * 0.01).toFixed(2)} Every ${info.price.payment_frequency_count} ${info.price.payment_frequency_interval}`,
250
- } : {
251
- price: `${CurrencyToSymbolMap[info.price.currency] || (info.price.currency + ' ')}${((info.price.price || info.price.fixed_price) * 0.01).toFixed(2)} (One Time)`,
252
- },
253
- tax_category: info.tax_category,
254
- });
255
- console.log(`To edit the product, go to https://app.dodopayments.com/products/edit?id=${info.product_id}`)
256
- } catch (e) {
257
- if (isDodoPaymentsAPIError(e) && e.error.code === "NOT_FOUND") {
258
- console.log("Incorrect product ID!");
259
- } else {
260
- console.error(e);
261
- }
262
- }
263
- } else {
264
- usage.products!.forEach(e => {
265
- console.log(`dodo products ${e.command} - ${e.description}`)
266
- });
267
- }
268
- } else if (category === 'payments') {
269
- if (subCommand === 'list') {
270
- const page = await input({
271
- message: 'Enter page:',
272
- default: "1",
273
- validate: (e => e.trim() !== '')
274
- });
275
- const payments = (await DodoClient.payments.list({ page_number: parseInt(page) - 1, page_size: 100 })).items;
276
- const paymentsTable = payments.map(payment => {
277
- return {
278
- 'payment id': payment.payment_id,
279
- 'created at': new Date(payment.created_at).toLocaleString(),
280
- 'subscription id': payment.subscription_id,
281
- 'total amount': `${CurrencyToSymbolMap[payment.currency] || (payment.currency + ' ')}${(payment.total_amount * 0.01).toFixed(2)}`,
282
- status: payment.status
283
- };
284
- });
285
- console.table(paymentsTable);
286
- console.log("To view a payment, go to https://app.dodopayments.com/transactions/payments/{payment_id}")
287
- } else if (subCommand === 'info') {
288
- try {
289
- const payment_id = 'pay_0NWiGvZPWxeWeNWISbfat';
290
- const payment_info = await DodoClient.payments.retrieve(payment_id);
291
- console.log(payment_info);
292
- const payment_table = {
293
- 'payment id': payment_info.payment_id,
294
- status: payment_info.status,
295
- 'total amount': `${CurrencyToSymbolMap[payment_info.currency] || payment_info.currency + ' '}${(payment_info.total_amount * 0.01).toFixed(2)}`,
296
- 'payment method': payment_info.payment_method,
297
- createdAt: new Date(payment_info.created_at).toLocaleString(),
298
- customer: payment_info.customer.customer_id,
299
- 'customer email': payment_info.customer.email,
300
- ...payment_info.subscription_id && {
301
- 'subscription id': payment_info.subscription_id
302
- },
303
- 'billing address street': `${payment_info.billing.street}`,
304
- 'billing address state': `${payment_info.billing.state}`,
305
- 'billing address city': `${payment_info.billing.city}`,
306
- 'billing address country': `${payment_info.billing.country}`,
307
- 'billing address zipcode': `${payment_info.billing.zipcode}`,
308
- }
309
- console.table(payment_table);
310
- console.log(`To view the payment, go to https://app.dodopayments.com/transactions/payments/${payment_info.payment_id}`)
311
- } catch (e) {
312
- if (isDodoPaymentsAPIError(e) && e.error.code === 'NOT_FOUND') {
313
- console.log("Incorrect payment ID!")
314
- } else {
315
- console.error(e);
316
- }
317
- }
318
- } else {
319
- usage.payments!.forEach(e => {
320
- console.log(`dodo payments ${e.command} - ${e.description}`)
321
- });
322
- }
323
- } else if (category === 'customers') {
324
- if (subCommand === 'list') {
325
- const page = await input({
326
- message: 'Enter page:',
327
- default: "1",
328
- validate: (e => e.trim() !== '')
329
- });
330
- console.table((await DodoClient.customers.list({ page_number: parseInt(page) - 1, page_size: 100 })).items, ['customer_id', 'name', 'email', 'phone_number']);
331
- } else if (subCommand === 'create') {
332
- const name = await input({
333
- message: "Enter Name: ",
334
- validate: (e => e.trim() !== '')
335
- });
336
-
337
- const email = await input({
338
- message: "Enter Email: ",
339
- validate: (e => e.trim() !== '')
340
- });
341
-
342
- const phone = await input({
343
- message: "Enter Phone Number: ",
344
- });
345
-
346
- const creation = await DodoClient.customers.create({
347
- name,
348
- email,
349
- phone_number: phone.trim() !== '' ? phone : null
350
- });
351
-
352
- console.log('Customer Successfully Created!');
353
- console.table([creation], ['customer_id', 'name', 'email', 'phone_number']);
354
- } else if (subCommand === 'update') {
355
- const customer_id = await input({
356
- message: "Enter customer ID:",
357
- validate: (e => e.startsWith('cus_') || 'Please enter a valid customer ID!')
358
- });
359
-
360
- try {
361
- const existingInfo = await DodoClient.customers.retrieve(customer_id);
362
- const name = await input({
363
- message: "Enter customer name:",
364
- default: existingInfo.name
365
- });
366
-
367
- const phone = await input({
368
- message: "Enter customer phone:",
369
- default: existingInfo.phone_number?.toString()
370
- });
371
-
372
- const updated = await DodoClient.customers.update(customer_id, {
373
- name: name,
374
- phone_number: phone.trim() !== '' ? phone : null
375
- });
376
-
377
- console.table([updated], ['customer_id', 'name', 'email', 'phone_number']);
378
- } catch (e) {
379
- if (isDodoPaymentsAPIError(e) && e.error.code === "NOT_FOUND") {
380
- console.log("Incorrect customer ID!");
381
- } else {
382
- console.error(e);
383
- }
384
- }
385
- } else {
386
- usage.customers!.forEach(e => {
387
- console.log(`dodo customers ${e.command} - ${e.description}`)
388
- });
389
- }
390
- } else if (category === 'discounts') {
391
- if (subCommand === 'list') {
392
- const page = await input({
393
- message: 'Enter page:',
394
- default: "1",
395
- validate: (e => e.trim() !== '')
396
- });
397
- const discounts = await DodoClient.discounts.list({ page_number: parseInt(page) - 1, page_size: 100 });
398
- const discountsTable = discounts.items.map(e => (
399
- {
400
- name: e.name,
401
- code: e.code,
402
- 'discount id': e.discount_id,
403
- 'created at': new Date(e.created_at).toLocaleString(),
404
- ...e.type === 'percentage' ? {
405
- amount: `${(e.amount * 0.01).toFixed(2)}%`
406
- } : {
407
- // I just added this in case of a breaking change in the future
408
- amount: e.amount
409
- },
410
- }
411
- ));
412
-
413
- console.table(discountsTable);
414
- console.log(`To view a discount, go to https://app.dodopayments.com/sales/discounts/edit?id={discount_id}`)
415
- } else if (subCommand === 'create') {
416
- const name = await input({
417
- message: "Enter discount name:",
418
- validate: (e => e.trim() !== '')
419
- });
420
-
421
- const percentage = await input({
422
- message: "Enter discount percentage:",
423
- // Make sure user enters valid value
424
- validate: (e => {
425
- const parsed = parseFloat(e);
426
- if (!Number.isNaN(parsed) && parsed > 0 && parsed <= 100) {
427
- return true;
428
- } else {
429
- return false;
430
- }
431
- })
432
- });
433
-
434
- const code = await input({
435
- message: "Enter discount code (Optional):"
436
- });
437
-
438
- const cycles = await input({
439
- message: "Enter discount cycles (Optional):"
440
- });
441
-
442
- const newDiscount = await DodoClient.discounts.create({
443
- name,
444
- code: code.trim() !== '' ? code : null,
445
- amount: parseFloat(percentage) * 100,
446
- type: 'percentage',
447
- // If the subscription cycles is provided
448
- ...cycles.trim() !== '' && {
449
- subscription_cycles: parseInt(cycles)
450
- }
451
- });
452
-
453
- console.log('Discount created successfully!');
454
- console.table({
455
- name: newDiscount.name,
456
- code: newDiscount.code,
457
- 'discount id': newDiscount.discount_id,
458
- ...cycles.trim() !== '' && {
459
- 'subscription cycles': newDiscount.subscription_cycles
460
- }
461
- });
462
- } else if (subCommand === 'delete') {
463
- await DodoClient.discounts.delete(await input({
464
- message: "Enter discount ID to be deleted:",
465
- validate: (e => e.startsWith('dsc_'))
466
- }));
467
-
468
- console.log("Successfully deleted discount!");
469
- } else {
470
- usage.discounts!.forEach(e => {
471
- console.log(`dodo discounts ${e.command} - ${e.description}`)
472
- });
473
- }
474
- } else if (category === 'licences') {
475
- if (subCommand === 'list') {
476
- const page = await input({
477
- message: 'Enter page:',
478
- default: "1",
479
- validate: (e => e.trim() !== '')
480
- });
481
- const licences = await DodoClient.licenseKeys.list({ page_number: parseInt(page) - 1, page_size: 100 });
482
- console.log(licences.items);
483
- } else {
484
- usage.licences!.forEach(e => {
485
- console.log(`dodo licences ${e.command} - ${e.description}`)
486
- });
487
- }
488
- } else {
489
- RenderHelp();
490
- }
@@ -1,8 +0,0 @@
1
- export const CurrencyToSymbolMap: {
2
- [key: string]: string
3
- } = {
4
- "USD": "$",
5
- "INR": "₹",
6
- "EUR": "€",
7
- "GBP": "£"
8
- }