@dodopayments/opencode-plugin 0.6.1 → 0.6.3

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 (37) hide show
  1. package/package.json +1 -1
  2. package/skills/billing-sdk/SKILL.md +8 -193
  3. package/skills/billing-sdk/references/pricing-page.md +197 -0
  4. package/skills/checkout-integration/SKILL.md +14 -449
  5. package/skills/checkout-integration/references/overlay-checkout.md +148 -0
  6. package/skills/checkout-integration/references/server-examples.md +317 -0
  7. package/skills/credit-based-billing/SKILL.md +5 -151
  8. package/skills/credit-based-billing/references/ai-token-example.md +108 -0
  9. package/skills/credit-based-billing/references/credit-webhooks.md +51 -0
  10. package/skills/framework-adapters/SKILL.md +38 -527
  11. package/skills/framework-adapters/references/astro.md +52 -0
  12. package/skills/framework-adapters/references/bun.md +45 -0
  13. package/skills/framework-adapters/references/convex.md +91 -0
  14. package/skills/framework-adapters/references/express.md +72 -0
  15. package/skills/framework-adapters/references/fastify.md +54 -0
  16. package/skills/framework-adapters/references/hono.md +63 -0
  17. package/skills/framework-adapters/references/nuxt.md +53 -0
  18. package/skills/framework-adapters/references/remix.md +65 -0
  19. package/skills/framework-adapters/references/sveltekit.md +49 -0
  20. package/skills/framework-adapters/references/tanstack-start.md +31 -0
  21. package/skills/license-keys/SKILL.md +8 -303
  22. package/skills/license-keys/references/cli-tool.md +143 -0
  23. package/skills/license-keys/references/desktop-app.md +170 -0
  24. package/skills/mobile-checkout/SKILL.md +25 -313
  25. package/skills/mobile-checkout/references/android.md +69 -0
  26. package/skills/mobile-checkout/references/flutter.md +97 -0
  27. package/skills/mobile-checkout/references/ios.md +66 -0
  28. package/skills/mobile-checkout/references/react-native.md +100 -0
  29. package/skills/refunds-and-disputes/SKILL.md +1 -1
  30. package/skills/subscription-integration/SKILL.md +3 -77
  31. package/skills/subscription-integration/references/webhook-events.md +82 -0
  32. package/skills/usage-based-billing/SKILL.md +5 -114
  33. package/skills/usage-based-billing/references/instrumentation.md +119 -0
  34. package/skills/webhook-integration/SKILL.md +28 -277
  35. package/skills/webhook-integration/references/common-mistakes.md +146 -0
  36. package/skills/webhook-integration/references/event-catalog.md +103 -0
  37. package/skills/webhook-integration/references/local-testing.md +42 -0
@@ -219,316 +219,21 @@ Optional query fields: `page_size` (default 10, max 100), `page_number` (default
219
219
 
220
220
  ## Desktop App Integration
221
221
 
222
- ### Electron app example
222
+ Full guide: [references/desktop-app.md](references/desktop-app.md).
223
223
 
224
- ```typescript
225
- // main/license.ts
226
- import Store from 'electron-store';
227
- import DodoPayments from 'dodopayments';
228
- import os from 'os';
229
-
230
- const store = new Store();
231
- const client = new DodoPayments({ bearerToken: 'public', environment: 'test_mode' }); // public endpoints: placeholder token; use 'live_mode' in production
232
-
233
- interface LicenseInfo {
234
- key: string;
235
- instanceId: string;
236
- activatedAt: string;
237
- lastValidatedAt?: string; // last time the server confirmed the license (absent on records saved before it was added)
238
- }
239
-
240
- export async function activateLicense(licenseKey: string): Promise<boolean> {
241
- try {
242
- const deviceName = `${os.hostname()} - ${os.platform()}`;
243
-
244
- const response = await client.licenses.activate({
245
- license_key: licenseKey,
246
- name: deviceName,
247
- });
248
-
249
- const now = new Date().toISOString();
250
- const licenseInfo: LicenseInfo = {
251
- key: licenseKey,
252
- instanceId: response.id,
253
- activatedAt: now,
254
- lastValidatedAt: now,
255
- };
224
+ Covers:
256
225
 
257
- store.set('license', licenseInfo);
258
- return true;
259
- } catch (error) {
260
- console.error('Activation failed:', error);
261
- return false;
262
- }
263
- }
264
-
265
- export async function checkLicense(): Promise<boolean> {
266
- const license = store.get('license') as LicenseInfo | undefined;
267
-
268
- if (!license) {
269
- return false;
270
- }
271
-
272
- try {
273
- const response = await client.licenses.validate({
274
- license_key: license.key,
275
- license_key_instance_id: license.instanceId,
276
- });
277
-
278
- if (!response.valid) {
279
- // An explicit "invalid" from the server (revoked key, deactivated
280
- // instance) ends the license: drop the cache so a later offline start
281
- // cannot fall back to the grace period.
282
- store.delete('license');
283
- return false;
284
- }
285
- store.set('license', { ...license, lastValidatedAt: new Date().toISOString() });
286
- return true;
287
- } catch (error) {
288
- // Only a connection failure (offline, DNS, timeout) earns the grace period.
289
- // Any other API error - a revoked key, a deactivated instance - is a real
290
- // answer from the server, so fail closed.
291
- if (!(error instanceof DodoPayments.APIConnectionError)) {
292
- return false;
293
- }
294
-
295
- // Trust the cached result only within a grace period measured from the
296
- // LAST SUCCESSFUL validation, not from activation - otherwise a key revoked
297
- // yesterday keeps working offline for 30 days after activation, and an old
298
- // activation gets no grace at all.
299
- // Records saved before lastValidatedAt existed fall back to activatedAt
300
- // until their first successful validation.
301
- const lastValidated = new Date(license.lastValidatedAt ?? license.activatedAt);
302
- const daysSinceValidation = (Date.now() - lastValidated.getTime()) / (1000 * 60 * 60 * 24);
303
-
304
- // Allow 30-day offline grace period
305
- return daysSinceValidation < 30;
306
- }
307
- }
308
-
309
- export async function deactivateLicense(): Promise<boolean> {
310
- const license = store.get('license') as LicenseInfo | undefined;
311
-
312
- if (!license) {
313
- return true;
314
- }
315
-
316
- try {
317
- await client.licenses.deactivate({
318
- license_key: license.key,
319
- license_key_instance_id: license.instanceId,
320
- });
321
-
322
- store.delete('license');
323
- return true;
324
- } catch (error) {
325
- console.error('Deactivation failed:', error);
326
- return false;
327
- }
328
- }
329
- ```
330
-
331
- ### React component for license input
332
-
333
- ```tsx
334
- // components/LicenseActivation.tsx
335
- import { useState } from 'react';
336
-
337
- interface Props {
338
- onActivated: () => void;
339
- }
340
-
341
- export function LicenseActivation({ onActivated }: Props) {
342
- const [licenseKey, setLicenseKey] = useState('');
343
- const [loading, setLoading] = useState(false);
344
- const [error, setError] = useState<string | null>(null);
345
-
346
- const handleActivate = async () => {
347
- setLoading(true);
348
- setError(null);
349
-
350
- try {
351
- const success = await window.electronAPI.activateLicense(licenseKey);
352
-
353
- if (success) {
354
- onActivated();
355
- } else {
356
- setError('Invalid license key. Please check and try again.');
357
- }
358
- } catch (err) {
359
- setError('Activation failed. Please try again.');
360
- } finally {
361
- setLoading(false);
362
- }
363
- };
364
-
365
- return (
366
- <div className="license-form">
367
- <h2>Activate Your License</h2>
368
- <p>Enter your license key to unlock all features.</p>
369
-
370
- <input
371
- type="text"
372
- value={licenseKey}
373
- onChange={(e) => setLicenseKey(e.target.value)}
374
- placeholder="XXXX-XXXX-XXXX-XXXX"
375
- disabled={loading}
376
- />
377
-
378
- {error && <p className="error">{error}</p>}
379
-
380
- <button onClick={handleActivate} disabled={loading || !licenseKey}>
381
- {loading ? 'Activating...' : 'Activate License'}
382
- </button>
383
- </div>
384
- );
385
- }
386
- ```
387
-
388
- ---
226
+ - Electron app example
227
+ - React component for license input
389
228
 
390
229
  ## CLI Tool Integration
391
230
 
392
- ### Node.js CLI example
393
-
394
- ```typescript
395
- // src/license.ts
396
- import Conf from 'conf';
397
- import DodoPayments from 'dodopayments';
398
- import { machineIdSync } from 'node-machine-id';
399
-
400
- interface StoredLicense {
401
- key: string;
402
- instanceId: string;
403
- machineId: string;
404
- }
405
-
406
- interface CliStore {
407
- license?: StoredLicense;
408
- }
409
-
410
- // Typing the store keeps `config.get('license')` strongly typed at every call site.
411
- const config = new Conf<CliStore>({ projectName: 'your-cli' });
412
- const client = new DodoPayments({ bearerToken: 'public', environment: 'test_mode' }); // public endpoints: placeholder token; use 'live_mode' in production
231
+ Full guide: [references/cli-tool.md](references/cli-tool.md).
413
232
 
414
- export async function activate(licenseKey: string): Promise<void> {
415
- const machineId = machineIdSync();
416
- const deviceName = `CLI - ${process.platform} - ${machineId.substring(0, 8)}`;
417
-
418
- try {
419
- const response = await client.licenses.activate({
420
- license_key: licenseKey,
421
- name: deviceName,
422
- });
423
-
424
- config.set('license', {
425
- key: licenseKey,
426
- instanceId: response.id,
427
- machineId,
428
- });
429
-
430
- console.log('License activated successfully!');
431
- } catch (error: any) {
432
- if (error.status === 422) {
433
- console.error('Activation limit reached. Deactivate another device first.');
434
- } else {
435
- console.error('Activation failed:', error.message);
436
- }
437
- process.exit(1);
438
- }
439
- }
440
-
441
- export async function checkLicense(): Promise<boolean> {
442
- const license = config.get('license');
443
-
444
- if (!license) {
445
- return false;
446
- }
447
-
448
- try {
449
- const response = await client.licenses.validate({
450
- license_key: license.key,
451
- license_key_instance_id: license.instanceId,
452
- });
233
+ Covers:
453
234
 
454
- return response.valid;
455
- } catch {
456
- return false;
457
- }
458
- }
459
-
460
- export async function deactivate(): Promise<void> {
461
- const license = config.get('license');
462
-
463
- if (!license) {
464
- console.log('No active license found.');
465
- return;
466
- }
467
-
468
- try {
469
- await client.licenses.deactivate({
470
- license_key: license.key,
471
- license_key_instance_id: license.instanceId,
472
- });
473
-
474
- config.delete('license');
475
- console.log('License deactivated.');
476
- } catch (error: any) {
477
- console.error('Deactivation failed:', error.message);
478
- }
479
- }
480
-
481
- export function requireLicense() {
482
- return async () => {
483
- const valid = await checkLicense();
484
- if (!valid) {
485
- console.error('This command requires a valid license.');
486
- console.error('Run: your-cli activate <license-key>');
487
- process.exit(1);
488
- }
489
- };
490
- }
491
- ```
492
-
493
- ### CLI commands
494
-
495
- ```typescript
496
- // src/cli.ts
497
- import { Command } from 'commander';
498
- import { activate, deactivate, checkLicense, requireLicense } from './license';
499
-
500
- const program = new Command();
501
-
502
- program
503
- .command('activate <license-key>')
504
- .description('Activate your license')
505
- .action(activate);
506
-
507
- program
508
- .command('deactivate')
509
- .description('Deactivate license on this device')
510
- .action(deactivate);
511
-
512
- program
513
- .command('status')
514
- .description('Check license status')
515
- .action(async () => {
516
- const valid = await checkLicense();
517
- console.log(valid ? 'License: Active' : 'License: Not activated');
518
- });
519
-
520
- program
521
- .command('generate')
522
- .description('Generate something (requires license)')
523
- .hook('preAction', requireLicense())
524
- .action(async () => {
525
- // Premium feature
526
- });
527
-
528
- program.parse();
529
- ```
530
-
531
- ---
235
+ - Node.js CLI example
236
+ - CLI commands
532
237
 
533
238
  ## Webhook Integration
534
239
 
@@ -0,0 +1,143 @@
1
+ # Dodo Payments License Keys: CLI Tool Integration
2
+
3
+
4
+ ### Node.js CLI example
5
+
6
+ ```typescript
7
+ // src/license.ts
8
+ import Conf from 'conf';
9
+ import DodoPayments from 'dodopayments';
10
+ import { machineIdSync } from 'node-machine-id';
11
+
12
+ interface StoredLicense {
13
+ key: string;
14
+ instanceId: string;
15
+ machineId: string;
16
+ }
17
+
18
+ interface CliStore {
19
+ license?: StoredLicense;
20
+ }
21
+
22
+ // Typing the store keeps `config.get('license')` strongly typed at every call site.
23
+ const config = new Conf<CliStore>({ projectName: 'your-cli' });
24
+ const client = new DodoPayments({ bearerToken: 'public', environment: 'test_mode' }); // public endpoints: placeholder token; use 'live_mode' in production
25
+
26
+ export async function activate(licenseKey: string): Promise<void> {
27
+ const machineId = machineIdSync();
28
+ const deviceName = `CLI - ${process.platform} - ${machineId.substring(0, 8)}`;
29
+
30
+ try {
31
+ const response = await client.licenses.activate({
32
+ license_key: licenseKey,
33
+ name: deviceName,
34
+ });
35
+
36
+ config.set('license', {
37
+ key: licenseKey,
38
+ instanceId: response.id,
39
+ machineId,
40
+ });
41
+
42
+ console.log('License activated successfully!');
43
+ } catch (error: any) {
44
+ if (error.status === 422) {
45
+ console.error('Activation limit reached. Deactivate another device first.');
46
+ } else {
47
+ console.error('Activation failed:', error.message);
48
+ }
49
+ process.exit(1);
50
+ }
51
+ }
52
+
53
+ export async function checkLicense(): Promise<boolean> {
54
+ const license = config.get('license');
55
+
56
+ if (!license) {
57
+ return false;
58
+ }
59
+
60
+ try {
61
+ const response = await client.licenses.validate({
62
+ license_key: license.key,
63
+ license_key_instance_id: license.instanceId,
64
+ });
65
+
66
+ return response.valid;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ export async function deactivate(): Promise<void> {
73
+ const license = config.get('license');
74
+
75
+ if (!license) {
76
+ console.log('No active license found.');
77
+ return;
78
+ }
79
+
80
+ try {
81
+ await client.licenses.deactivate({
82
+ license_key: license.key,
83
+ license_key_instance_id: license.instanceId,
84
+ });
85
+
86
+ config.delete('license');
87
+ console.log('License deactivated.');
88
+ } catch (error: any) {
89
+ console.error('Deactivation failed:', error.message);
90
+ }
91
+ }
92
+
93
+ export function requireLicense() {
94
+ return async () => {
95
+ const valid = await checkLicense();
96
+ if (!valid) {
97
+ console.error('This command requires a valid license.');
98
+ console.error('Run: your-cli activate <license-key>');
99
+ process.exit(1);
100
+ }
101
+ };
102
+ }
103
+ ```
104
+
105
+ ### CLI commands
106
+
107
+ ```typescript
108
+ // src/cli.ts
109
+ import { Command } from 'commander';
110
+ import { activate, deactivate, checkLicense, requireLicense } from './license';
111
+
112
+ const program = new Command();
113
+
114
+ program
115
+ .command('activate <license-key>')
116
+ .description('Activate your license')
117
+ .action(activate);
118
+
119
+ program
120
+ .command('deactivate')
121
+ .description('Deactivate license on this device')
122
+ .action(deactivate);
123
+
124
+ program
125
+ .command('status')
126
+ .description('Check license status')
127
+ .action(async () => {
128
+ const valid = await checkLicense();
129
+ console.log(valid ? 'License: Active' : 'License: Not activated');
130
+ });
131
+
132
+ program
133
+ .command('generate')
134
+ .description('Generate something (requires license)')
135
+ .hook('preAction', requireLicense())
136
+ .action(async () => {
137
+ // Premium feature
138
+ });
139
+
140
+ program.parse();
141
+ ```
142
+
143
+ ---
@@ -0,0 +1,170 @@
1
+ # Dodo Payments License Keys: Desktop App Integration
2
+
3
+
4
+ ### Electron app example
5
+
6
+ ```typescript
7
+ // main/license.ts
8
+ import Store from 'electron-store';
9
+ import DodoPayments from 'dodopayments';
10
+ import os from 'os';
11
+
12
+ const store = new Store();
13
+ const client = new DodoPayments({ bearerToken: 'public', environment: 'test_mode' }); // public endpoints: placeholder token; use 'live_mode' in production
14
+
15
+ interface LicenseInfo {
16
+ key: string;
17
+ instanceId: string;
18
+ activatedAt: string;
19
+ lastValidatedAt?: string; // last time the server confirmed the license (absent on records saved before it was added)
20
+ }
21
+
22
+ export async function activateLicense(licenseKey: string): Promise<boolean> {
23
+ try {
24
+ const deviceName = `${os.hostname()} - ${os.platform()}`;
25
+
26
+ const response = await client.licenses.activate({
27
+ license_key: licenseKey,
28
+ name: deviceName,
29
+ });
30
+
31
+ const now = new Date().toISOString();
32
+ const licenseInfo: LicenseInfo = {
33
+ key: licenseKey,
34
+ instanceId: response.id,
35
+ activatedAt: now,
36
+ lastValidatedAt: now,
37
+ };
38
+
39
+ store.set('license', licenseInfo);
40
+ return true;
41
+ } catch (error) {
42
+ console.error('Activation failed:', error);
43
+ return false;
44
+ }
45
+ }
46
+
47
+ export async function checkLicense(): Promise<boolean> {
48
+ const license = store.get('license') as LicenseInfo | undefined;
49
+
50
+ if (!license) {
51
+ return false;
52
+ }
53
+
54
+ try {
55
+ const response = await client.licenses.validate({
56
+ license_key: license.key,
57
+ license_key_instance_id: license.instanceId,
58
+ });
59
+
60
+ if (!response.valid) {
61
+ // An explicit "invalid" from the server (revoked key, deactivated
62
+ // instance) ends the license: drop the cache so a later offline start
63
+ // cannot fall back to the grace period.
64
+ store.delete('license');
65
+ return false;
66
+ }
67
+ store.set('license', { ...license, lastValidatedAt: new Date().toISOString() });
68
+ return true;
69
+ } catch (error) {
70
+ // Only a connection failure (offline, DNS, timeout) earns the grace period.
71
+ // Any other API error - a revoked key, a deactivated instance - is a real
72
+ // answer from the server, so fail closed.
73
+ if (!(error instanceof DodoPayments.APIConnectionError)) {
74
+ return false;
75
+ }
76
+
77
+ // Trust the cached result only within a grace period measured from the
78
+ // LAST SUCCESSFUL validation, not from activation - otherwise a key revoked
79
+ // yesterday keeps working offline for 30 days after activation, and an old
80
+ // activation gets no grace at all.
81
+ // Records saved before lastValidatedAt existed fall back to activatedAt
82
+ // until their first successful validation.
83
+ const lastValidated = new Date(license.lastValidatedAt ?? license.activatedAt);
84
+ const daysSinceValidation = (Date.now() - lastValidated.getTime()) / (1000 * 60 * 60 * 24);
85
+
86
+ // Allow 30-day offline grace period
87
+ return daysSinceValidation < 30;
88
+ }
89
+ }
90
+
91
+ export async function deactivateLicense(): Promise<boolean> {
92
+ const license = store.get('license') as LicenseInfo | undefined;
93
+
94
+ if (!license) {
95
+ return true;
96
+ }
97
+
98
+ try {
99
+ await client.licenses.deactivate({
100
+ license_key: license.key,
101
+ license_key_instance_id: license.instanceId,
102
+ });
103
+
104
+ store.delete('license');
105
+ return true;
106
+ } catch (error) {
107
+ console.error('Deactivation failed:', error);
108
+ return false;
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### React component for license input
114
+
115
+ ```tsx
116
+ // components/LicenseActivation.tsx
117
+ import { useState } from 'react';
118
+
119
+ interface Props {
120
+ onActivated: () => void;
121
+ }
122
+
123
+ export function LicenseActivation({ onActivated }: Props) {
124
+ const [licenseKey, setLicenseKey] = useState('');
125
+ const [loading, setLoading] = useState(false);
126
+ const [error, setError] = useState<string | null>(null);
127
+
128
+ const handleActivate = async () => {
129
+ setLoading(true);
130
+ setError(null);
131
+
132
+ try {
133
+ const success = await window.electronAPI.activateLicense(licenseKey);
134
+
135
+ if (success) {
136
+ onActivated();
137
+ } else {
138
+ setError('Invalid license key. Please check and try again.');
139
+ }
140
+ } catch (err) {
141
+ setError('Activation failed. Please try again.');
142
+ } finally {
143
+ setLoading(false);
144
+ }
145
+ };
146
+
147
+ return (
148
+ <div className="license-form">
149
+ <h2>Activate Your License</h2>
150
+ <p>Enter your license key to unlock all features.</p>
151
+
152
+ <input
153
+ type="text"
154
+ value={licenseKey}
155
+ onChange={(e) => setLicenseKey(e.target.value)}
156
+ placeholder="XXXX-XXXX-XXXX-XXXX"
157
+ disabled={loading}
158
+ />
159
+
160
+ {error && <p className="error">{error}</p>}
161
+
162
+ <button onClick={handleActivate} disabled={loading || !licenseKey}>
163
+ {loading ? 'Activating...' : 'Activate License'}
164
+ </button>
165
+ </div>
166
+ );
167
+ }
168
+ ```
169
+
170
+ ---