@dodopayments/opencode-plugin 0.3.3 → 0.5.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.
@@ -0,0 +1,471 @@
1
+ ---
2
+ name: dodo-best-practices
3
+ description: Guide for initial Dodo Payments setup, including SDK installation, test and live environments, API keys, and the canonical checkout-to-webhook architecture.
4
+ ---
5
+
6
+ # Dodo Payments Integration Guide
7
+
8
+ This skill covers the foundational concepts and setup for Dodo Payments. Use it when starting a new integration, setting up the SDK, or understanding the core payment flow.
9
+
10
+ ## When to use this skill
11
+
12
+ - You're building a new payment integration and need to understand Dodo's architecture
13
+ - You need to install and initialize the SDK in your language
14
+ - You want to understand the canonical payment flow and webhook verification
15
+ - You're choosing between framework adapters or payment methods
16
+ - You need to know what Dodo handles versus what you must build
17
+
18
+ ---
19
+
20
+ ## What is Dodo Payments
21
+
22
+ Dodo Payments is a Merchant of Record (MoR). That means Dodo is the legal seller on every transaction, handles sales tax registration and calculation across all jurisdictions, remits taxes to authorities, and manages card-network disputes and chargebacks. As a developer, you don't build a sales-tax engine or dispute-handling system. You create checkout sessions, listen to webhooks, and grant access when payment succeeds.
23
+
24
+ ---
25
+
26
+ ## Environment Setup
27
+
28
+ ### Base URLs
29
+
30
+ Only two real base URLs exist:
31
+
32
+ - **Live**: `https://live.dodopayments.com`
33
+ - **Test**: `https://test.dodopayments.com`
34
+
35
+ Never use `api.dodopayments.com` — it has no DNS record and cannot be reached.
36
+
37
+ ### API Keys
38
+
39
+ Keys have two formats:
40
+
41
+ - **Test**: `dodo_test_...`
42
+ - **Live**: `dodo_live_...`
43
+
44
+ ### Environment Variables
45
+
46
+ ```bash
47
+ DODO_PAYMENTS_API_KEY # Bearer token for API requests
48
+ DODO_PAYMENTS_WEBHOOK_KEY # Secret for webhook signature verification
49
+ ```
50
+
51
+ ### Default Environment
52
+
53
+ The `environment` parameter defaults to `live_mode` if omitted. Always pass `test_mode` explicitly during development.
54
+
55
+ ---
56
+
57
+ ## SDK Installation & Initialization
58
+
59
+ ### TypeScript/Node.js
60
+
61
+ ```bash
62
+ npm install dodopayments
63
+ ```
64
+
65
+ ```typescript
66
+ import DodoPayments from 'dodopayments';
67
+
68
+ const client = new DodoPayments({
69
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
70
+ environment: 'test_mode', // defaults to 'live_mode'
71
+ });
72
+ ```
73
+
74
+ Constructor options:
75
+
76
+ | Option | Type | Default | Notes |
77
+ |---|---|---|---|
78
+ | `bearerToken` | string | env `DODO_PAYMENTS_API_KEY` | Required for API calls |
79
+ | `environment` | `'test_mode' \| 'live_mode'` | `'live_mode'` | Explicit in dev |
80
+ | `webhookKey` | string | env `DODO_PAYMENTS_WEBHOOK_KEY` | For `webhooks.unwrap()` |
81
+ | `baseURL` | string | — | Override; mutually exclusive with `environment` |
82
+
83
+ ### Python
84
+
85
+ ```bash
86
+ pip install dodopayments
87
+ ```
88
+
89
+ ```python
90
+ import os
91
+ from dodopayments import DodoPayments
92
+
93
+ client = DodoPayments(
94
+ bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
95
+ environment="test_mode", # defaults to "live_mode"
96
+ )
97
+ ```
98
+
99
+ ### Go
100
+
101
+ ```bash
102
+ go get -u github.com/dodopayments/dodopayments-go@v1.110.0
103
+ ```
104
+
105
+ ```go
106
+ import (
107
+ "os"
108
+
109
+ "github.com/dodopayments/dodopayments-go"
110
+ "github.com/dodopayments/dodopayments-go/option"
111
+ )
112
+
113
+ client := dodopayments.NewClient(
114
+ option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
115
+ option.WithEnvironmentTestMode(), // defaults to live
116
+ )
117
+ ```
118
+
119
+ ### PHP
120
+
121
+ ```bash
122
+ composer require "dodopayments/client:6.19.0"
123
+ ```
124
+
125
+ ```php
126
+ use Dodopayments\Client;
127
+
128
+ $client = new Client(
129
+ bearerToken: getenv('DODO_PAYMENTS_API_KEY') ?: 'My Bearer Token',
130
+ environment: 'test_mode',
131
+ );
132
+ ```
133
+
134
+ ### Ruby
135
+
136
+ ```bash
137
+ gem "dodopayments", "~> 2.22.0"
138
+ ```
139
+
140
+ ```ruby
141
+ dodo_payments = Dodopayments::Client.new(
142
+ bearer_token: ENV["DODO_PAYMENTS_API_KEY"],
143
+ environment: "test_mode"
144
+ )
145
+ ```
146
+
147
+ ### Java
148
+
149
+ ```xml
150
+ <dependency>
151
+ <groupId>com.dodopayments.api</groupId>
152
+ <artifactId>dodo-payments-java</artifactId>
153
+ <version>1.110.0</version>
154
+ </dependency>
155
+ ```
156
+
157
+ ```java
158
+ DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();
159
+ // Reads DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_WEBHOOK_KEY, DODO_PAYMENTS_BASE_URL
160
+ ```
161
+
162
+ ### Kotlin
163
+
164
+ ```xml
165
+ <dependency>
166
+ <groupId>com.dodopayments.api</groupId>
167
+ <artifactId>dodo-payments-kotlin</artifactId>
168
+ <version>1.110.0</version>
169
+ </dependency>
170
+ ```
171
+
172
+ ```kotlin
173
+ val client: DodoPaymentsClient = DodoPaymentsOkHttpClient.fromEnv()
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Framework Adapters
179
+
180
+ Dodo publishes framework-specific packages under `@dodopayments/*`. Use the one matching your stack:
181
+
182
+ | Framework | Package | Use if |
183
+ |---|---|---|
184
+ | Next.js | `@dodopayments/nextjs` | Building with Next.js App Router |
185
+ | Nuxt | `@dodopayments/nuxt` | Building with Nuxt 3+ |
186
+ | Express | `@dodopayments/express` | Using Express.js |
187
+ | Fastify | `@dodopayments/fastify` | Using Fastify |
188
+ | Hono | `@dodopayments/hono` | Using Hono (Edge/Node) |
189
+ | Astro | `@dodopayments/astro` | Using Astro endpoints |
190
+ | SvelteKit | `@dodopayments/sveltekit` | Using SvelteKit server routes |
191
+ | Remix | `@dodopayments/remix` | Using Remix loaders/actions |
192
+ | TanStack Start | `@dodopayments/tanstack` | Using TanStack Start |
193
+ | Better Auth | `@dodopayments/better-auth` | Integrating with Better Auth |
194
+ | Convex | `@dodopayments/convex` | Using Convex backend |
195
+ | Bun | `@dodopayments/bun` | Using Bun.serve() |
196
+
197
+ Each adapter provides checkout, customer portal, and webhook handlers tailored to the framework's conventions.
198
+
199
+ ---
200
+
201
+ ## Core Concepts
202
+
203
+ ### Products
204
+
205
+ Items you sell. Create in the dashboard or via API. Types:
206
+
207
+ - **One-time**: Single purchase
208
+ - **Subscription**: Recurring billing
209
+ - **Usage-based**: Metered consumption
210
+
211
+ ### Customers
212
+
213
+ Represent buyers. Can have multiple payment methods, subscriptions, and credit balances. Create explicitly or implicitly during checkout.
214
+
215
+ ### Checkout Sessions
216
+
217
+ The primary payment collection method. Create a session server-side, redirect the customer to the hosted checkout URL, and listen for webhooks to confirm payment.
218
+
219
+ See the `checkout-integration` skill for detailed checkout configuration and the `subscription-integration` skill for recurring lifecycle management.
220
+
221
+ ### Subscriptions
222
+
223
+ Recurring charges on a schedule. Managed through checkout sessions or the subscriptions API. See the `subscription-integration` skill for lifecycle, trials, plan changes, and on-demand charging.
224
+
225
+ ### Webhooks
226
+
227
+ Real-time event notifications. Dodo sends events like `payment.succeeded`, `subscription.active`, `refund.succeeded`, and `credit.deducted`. Webhook signature verification is covered in the `webhook-integration` skill.
228
+
229
+ ### Credit Entitlements
230
+
231
+ Virtual balances (API calls, tokens, compute hours) attached to products. Configured per product with rollover, overage, and expiration rules. See the `credit-based-billing` skill.
232
+
233
+ ---
234
+
235
+ ## The Canonical Payment Flow
236
+
237
+ 1. **Create checkout session** on your server with product ID and customer email.
238
+ 2. **Redirect customer** to the `checkout_url` returned.
239
+ 3. **Customer pays** on the hosted checkout.
240
+ 4. **Dodo sends webhook** (e.g., `payment.succeeded`) to your endpoint.
241
+ 5. **Verify the webhook signature** using `client.webhooks.unwrap()`.
242
+ 6. **Grant access** only after webhook verification succeeds.
243
+
244
+ Never grant access based on the browser `return_url` redirect alone. The webhook is the authoritative confirmation.
245
+
246
+ ```typescript
247
+ import express from 'express';
248
+
249
+ const app = express();
250
+
251
+ // 1. Create session
252
+ const session = await client.checkoutSessions.create({
253
+ product_cart: [{ product_id: 'pdt_example', quantity: 1 }],
254
+ customer: { email: 'customer@example.com' },
255
+ return_url: 'https://yoursite.com/success',
256
+ });
257
+
258
+ // 2. Redirect to session.checkout_url
259
+
260
+ // 3. Listen for webhook with the exact raw request bytes
261
+ app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
262
+ try {
263
+ // 4. Verify signature
264
+ const webhookId = req.headers['webhook-id'] as string;
265
+ const event = client.webhooks.unwrap(req.body.toString(), {
266
+ headers: {
267
+ 'webhook-id': webhookId,
268
+ 'webhook-signature': req.headers['webhook-signature'] as string,
269
+ 'webhook-timestamp': req.headers['webhook-timestamp'] as string,
270
+ },
271
+ });
272
+
273
+ // 5. Suppress duplicates with an atomic unique insert and run side effects
274
+ // in the same database transaction.
275
+ const handled = await processWebhookOnce(webhookId, async () => {
276
+ if (event.type === 'payment.succeeded') {
277
+ // ONE-TIME purchases only. Subscription access starts on subscription.active.
278
+ const payment = event.data;
279
+ await grantOneTimeAccess(payment.customer.customer_id);
280
+ }
281
+
282
+ if (event.type === 'subscription.active') {
283
+ await grantSubscriptionAccess(event.data);
284
+ }
285
+ });
286
+
287
+ res.json({ received: true, duplicate: !handled });
288
+ } catch (error) {
289
+ res.status(401).json({ error: 'Invalid signature' });
290
+ }
291
+ });
292
+ ```
293
+
294
+ ---
295
+
296
+ ## API Foundations
297
+
298
+ ### Authentication
299
+
300
+ All requests use Bearer token authentication:
301
+
302
+ ```http
303
+ Authorization: Bearer dodo_live_...
304
+ ```
305
+
306
+ ### Pagination
307
+
308
+ List endpoints are page-numbered. They accept `page_size` and `page_number`, and the response
309
+ exposes the rows on `items`:
310
+
311
+ ```typescript
312
+ const payments = await client.payments.list({
313
+ page_size: 50,
314
+ page_number: 0,
315
+ });
316
+
317
+ for (const payment of payments.items) {
318
+ console.log(payment.payment_id);
319
+ }
320
+ ```
321
+
322
+ The SDK can also walk every page for you:
323
+
324
+ ```typescript
325
+ for await (const payment of client.payments.list()) {
326
+ console.log(payment.payment_id);
327
+ }
328
+ ```
329
+
330
+ ### Rate Limits
331
+
332
+ Dodo enforces rate limits. The SDK automatically retries `429` responses as described below.
333
+
334
+ ### SDK Error Classes
335
+
336
+ The SDK throws typed errors. Catch and inspect:
337
+
338
+ ```typescript
339
+ try {
340
+ await client.checkoutSessions.create({...});
341
+ } catch (error) {
342
+ if (error instanceof DodoPayments.APIError) {
343
+ console.error(error.status, error.message);
344
+ }
345
+ }
346
+ ```
347
+
348
+ ### Retries
349
+
350
+ The SDK retries twice by default with a short exponential backoff on connection errors and `408`, `409`, `429`, and `5xx` responses. Do not add an unconditional custom retry loop.
351
+
352
+ Override the default for all requests when constructing the client:
353
+
354
+ ```typescript
355
+ const clientWithoutRetries = new DodoPayments({
356
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
357
+ environment: 'test_mode',
358
+ maxRetries: 0,
359
+ });
360
+ ```
361
+
362
+ Or override it for one request:
363
+
364
+ ```typescript
365
+ await client.checkoutSessions.create(
366
+ {
367
+ product_cart: [{ product_id: 'pdt_example', quantity: 1 }],
368
+ customer: { email: 'customer@example.com' },
369
+ },
370
+ { maxRetries: 0 },
371
+ );
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Webhook Verification
377
+
378
+ Webhook signature verification is mandatory. Never trust the payload without verification.
379
+
380
+ Dodo implements the Standard Webhooks spec. The signed message is `webhook-id.webhook-timestamp.raw_body` (period-joined), HMAC-SHA256, base64-encoded.
381
+
382
+ **Use the SDK helper:**
383
+
384
+ ```typescript
385
+ const event = client.webhooks.unwrap(req.body.toString(), {
386
+ headers: {
387
+ 'webhook-id': req.headers['webhook-id'] as string,
388
+ 'webhook-signature': req.headers['webhook-signature'] as string,
389
+ 'webhook-timestamp': req.headers['webhook-timestamp'] as string,
390
+ },
391
+ });
392
+ ```
393
+
394
+ The `unwrap()` method verifies the signature and parses the payload. If verification fails, it throws an error.
395
+
396
+ For detailed webhook setup, event types, and testing, see the `webhook-integration` skill.
397
+
398
+ ---
399
+
400
+ ## Common Mistakes
401
+
402
+ ### 1. Granting access on `return_url` redirect
403
+
404
+ The browser redirect is not proof of payment. Always wait for the webhook.
405
+
406
+ ```typescript
407
+ // WRONG
408
+ app.get('/success', (req, res) => {
409
+ grantAccess(req.query.customer_id); // No verification!
410
+ });
411
+
412
+ // CORRECT
413
+ app.post('/webhook', async (req, res) => {
414
+ const event = client.webhooks.unwrap(...);
415
+ if (event.type === 'payment.succeeded') {
416
+ grantAccess(event.data.customer.customer_id);
417
+ }
418
+ });
419
+ ```
420
+
421
+ ### 2. Hand-rolling webhook verification
422
+
423
+ Don't implement HMAC verification yourself. Use `client.webhooks.unwrap()`.
424
+
425
+ The old approach of signing just the payload is wrong because Standard Webhooks signs `webhook-id.webhook-timestamp.raw_body`. Hand-rolled HMAC will never match.
426
+
427
+ ### 3. Re-serializing the request body
428
+
429
+ Webhook verification requires the exact raw body. If you parse JSON and re-stringify it, the signature breaks.
430
+
431
+ ```typescript
432
+ // WRONG
433
+ const body = JSON.parse(req.body);
434
+ const event = client.webhooks.unwrap(JSON.stringify(body), {...});
435
+
436
+ // CORRECT
437
+ const event = client.webhooks.unwrap(req.body.toString(), {...});
438
+ ```
439
+
440
+ ### 4. Using deprecated APIs
441
+
442
+ Don't use `client.payments.create()` or `client.subscriptions.create()` for new integrations. Both are deprecated. Use `client.checkoutSessions.create()`.
443
+
444
+ ### 5. Forgetting to set `environment: 'test_mode'`
445
+
446
+ The default is `live_mode`. Always pass `test_mode` explicitly during development to avoid charging real cards.
447
+
448
+ ### 6. Storing API keys in code
449
+
450
+ Never hardcode keys. Always use environment variables.
451
+
452
+ ### 7. Ignoring the `webhook-timestamp` header
453
+
454
+ The timestamp prevents replay attacks. `client.webhooks.unwrap()` validates it automatically, but if you hand-roll verification, check that the timestamp is recent (within a few minutes).
455
+
456
+ ### 8. Using `unsafeUnwrap()` in production
457
+
458
+ `unsafeUnwrap()` skips signature verification. Use it only for unsigned test payloads from `dodo wh trigger`. Never use it for production webhooks.
459
+
460
+ ---
461
+
462
+ ## Resources
463
+
464
+ - [Dodo Payments Docs](https://docs.dodopayments.com)
465
+ - [API Reference](https://docs.dodopayments.com/api-reference/introduction)
466
+ - [SDK Repositories](https://github.com/dodopayments)
467
+ - [Checkout Sessions Guide](https://docs.dodopayments.com/developer-resources/checkout-session)
468
+ - [Webhook Guide](https://docs.dodopayments.com/developer-resources/webhooks)
469
+ - [Customer Portal](https://docs.dodopayments.com/features/customer-portal)
470
+ - [Subscription Integration](https://docs.dodopayments.com/developer-resources/subscription-integration-guide)
471
+ - [Credit-Based Billing](https://docs.dodopayments.com/features/credit-based-billing)