@dodopayments/opencode-plugin 0.6.0 → 0.6.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 (47) hide show
  1. package/README.md +1 -0
  2. package/opencode-plugin/README.md +1 -1
  3. package/package.json +1 -1
  4. package/skills/better-auth-integration/SKILL.md +12 -3
  5. package/skills/billing-sdk/SKILL.md +15 -167
  6. package/skills/billing-sdk/references/pricing-page.md +197 -0
  7. package/skills/checkout-integration/SKILL.md +31 -460
  8. package/skills/checkout-integration/references/overlay-checkout.md +148 -0
  9. package/skills/checkout-integration/references/server-examples.md +317 -0
  10. package/skills/credit-based-billing/SKILL.md +11 -158
  11. package/skills/credit-based-billing/references/ai-token-example.md +108 -0
  12. package/skills/credit-based-billing/references/credit-webhooks.md +51 -0
  13. package/skills/customer-management/SKILL.md +25 -17
  14. package/skills/discounts-and-promotions/SKILL.md +20 -17
  15. package/skills/dodo-best-practices/SKILL.md +18 -5
  16. package/skills/dodo-mcp-usage/SKILL.md +152 -0
  17. package/skills/framework-adapters/SKILL.md +64 -421
  18. package/skills/framework-adapters/references/astro.md +52 -0
  19. package/skills/framework-adapters/references/bun.md +45 -0
  20. package/skills/framework-adapters/references/convex.md +91 -0
  21. package/skills/framework-adapters/references/express.md +72 -0
  22. package/skills/framework-adapters/references/fastify.md +54 -0
  23. package/skills/framework-adapters/references/hono.md +63 -0
  24. package/skills/framework-adapters/references/nuxt.md +53 -0
  25. package/skills/framework-adapters/references/remix.md +65 -0
  26. package/skills/framework-adapters/references/sveltekit.md +49 -0
  27. package/skills/framework-adapters/references/tanstack-start.md +31 -0
  28. package/skills/license-keys/SKILL.md +117 -367
  29. package/skills/license-keys/references/cli-tool.md +143 -0
  30. package/skills/license-keys/references/desktop-app.md +170 -0
  31. package/skills/localized-pricing/SKILL.md +51 -29
  32. package/skills/mobile-checkout/SKILL.md +72 -310
  33. package/skills/mobile-checkout/references/android.md +69 -0
  34. package/skills/mobile-checkout/references/flutter.md +97 -0
  35. package/skills/mobile-checkout/references/ios.md +66 -0
  36. package/skills/mobile-checkout/references/react-native.md +100 -0
  37. package/skills/product-catalog-management/SKILL.md +39 -16
  38. package/skills/refunds-and-disputes/SKILL.md +91 -71
  39. package/skills/subscription-integration/SKILL.md +43 -64
  40. package/skills/subscription-integration/references/webhook-events.md +82 -0
  41. package/skills/testing-and-go-live/SKILL.md +22 -15
  42. package/skills/usage-based-billing/SKILL.md +16 -122
  43. package/skills/usage-based-billing/references/instrumentation.md +119 -0
  44. package/skills/webhook-integration/SKILL.md +31 -263
  45. package/skills/webhook-integration/references/common-mistakes.md +146 -0
  46. package/skills/webhook-integration/references/event-catalog.md +103 -0
  47. package/skills/webhook-integration/references/local-testing.md +42 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: framework-adapters
3
- description: Guide for mounting official @dodopayments/* checkout, portal, and verified-webhook route handlers in supported web frameworks; use domain skills for payment and lifecycle logic.
3
+ description: Official @dodopayments/* framework adapters that mount Checkout, CustomerPortal, and Webhooks route handlers. Use when integrating Dodo Payments into Next.js, Express, Fastify, Hono, Astro, Remix, SvelteKit, Nuxt, TanStack, Bun, or Convex, such as @dodopayments/nextjs; use domain skills for payment and subscription logic.
4
4
  ---
5
5
 
6
6
  # Framework Adapters
@@ -21,10 +21,12 @@ Dodo publishes `@dodopayments/*` packages that wrap the core SDK with framework-
21
21
 
22
22
  Each adapter exposes three handler families:
23
23
 
24
- - **Checkout:** static (GET only), dynamic (POST with cart), or session (POST with pre-built session).
24
+ - **Checkout:** static (GET only), dynamic (POST, creates a payment or subscription via the deprecated `POST /payments` / `POST /subscriptions` endpoints - existing integrations only), or session (POST with a checkout session payload - use this for new integrations).
25
25
  - **CustomerPortal:** generates a time-bound portal session link.
26
26
  - **Webhooks:** verifies webhook signatures and dispatches typed events.
27
27
 
28
+ > **Security - CustomerPortal does not authenticate.** Every `CustomerPortal` handler opens the portal for whatever `?customer_id=` it receives. Mounted as-is, any visitor can open any customer's portal by guessing or enumerating IDs. Put the route behind your own authentication and resolve the customer ID **server-side from the signed-in session**; never accept it from the client. The portal examples below do this.
29
+
28
30
  **Export names are not uniform across adapters.** Most export `Checkout` / `CustomerPortal` / `Webhooks`, but two differ, and the return shapes differ as well. Check this table before writing imports:
29
31
 
30
32
  | Adapter | Checkout export | Portal export | Handler shape |
@@ -34,7 +36,7 @@ Each adapter exposes three handler families:
34
36
  | `fastify` | `Checkout` | `CustomerPortal` | returns `{ getHandler, postHandler }` |
35
37
  | `sveltekit` | `Checkout` | `CustomerPortal` | returns `{ GET, POST }` / `{ GET }` |
36
38
  | `nuxt` | `checkoutHandler` (auto-imported) | `customerPortalHandler` | no import statement |
37
- | `convex` | `DodoPayments` component | — | `createDodoWebhookHandler` |
39
+ | `convex` | `checkout` (from `dodo.api()`) | `customerPortal` (from `dodo.api()`, uses `identify`) | `createDodoWebhookHandler` |
38
40
 
39
41
  The adapters handle raw body preservation for webhook verification, environment variable mapping, and framework-specific request/response shapes. Checkout payload design belongs to the `checkout-integration` skill; webhook business logic belongs to `webhook-integration`; portal behavior belongs to `customer-management`.
40
42
 
@@ -71,7 +73,7 @@ DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success
71
73
  NUXT_PRIVATE_BEARER_TOKEN=dodo_test_...
72
74
  NUXT_PRIVATE_WEBHOOK_KEY=your-webhook-secret
73
75
  NUXT_PRIVATE_ENVIRONMENT=test_mode
74
- NUXT_PRIVATE_RETURNURL=https://yourdomain.com/checkout/success
76
+ NUXT_PRIVATE_RETURN_URL=https://yourdomain.com/checkout/success
75
77
  ```
76
78
 
77
79
  **Convex** uses dashboard environment variables (not local `.env`):
@@ -108,6 +110,7 @@ Defaulting to `test_mode` is deliberate: a missing or misspelled variable must n
108
110
  ```typescript
109
111
  // app/api/checkout/route.ts
110
112
  import { Checkout } from "@dodopayments/nextjs";
113
+ import { dodoEnvironment } from "@/lib/dodo-env";
111
114
 
112
115
  export const GET = Checkout({
113
116
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
@@ -128,12 +131,26 @@ export const POST = Checkout({
128
131
 
129
132
  ```typescript
130
133
  // app/api/customer-portal/route.ts
134
+ import { NextRequest } from "next/server";
131
135
  import { CustomerPortal } from "@dodopayments/nextjs";
136
+ import { dodoEnvironment } from "@/lib/dodo-env";
137
+ // your auth helper
138
+ import { getSessionUser } from "@/lib/auth";
132
139
 
133
- export const GET = CustomerPortal({
140
+ const portal = CustomerPortal({
134
141
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
135
142
  environment: dodoEnvironment,
136
143
  });
144
+
145
+ export async function GET(req: NextRequest) {
146
+ const user = await getSessionUser(req);
147
+ if (!user?.dodoCustomerId) return new Response("Unauthorized", { status: 401 });
148
+
149
+ // Overwrite any client-supplied ?customer_id= with the signed-in user's ID.
150
+ const url = new URL(req.url);
151
+ url.searchParams.set("customer_id", user.dodoCustomerId);
152
+ return portal(new NextRequest(url, req));
153
+ }
137
154
  ```
138
155
 
139
156
  ### Webhooks
@@ -152,465 +169,89 @@ export const POST = Webhooks({
152
169
 
153
170
  ## Express
154
171
 
155
- **Package:** `@dodopayments/express`
156
-
157
- ### Checkout
172
+ Full guide: [references/express.md](references/express.md).
158
173
 
159
- The Express adapter names its checkout export `checkoutHandler` in lowercase, unlike every other adapter. `import { Checkout } from "@dodopayments/express"` does not resolve.
174
+ Covers:
160
175
 
161
- ```typescript
162
- import express from "express";
163
- import { checkoutHandler } from "@dodopayments/express";
164
- import { dodoEnvironment } from "./lib/dodo-env";
165
-
166
- const app = express();
167
-
168
- app.get("/api/checkout", checkoutHandler({
169
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
170
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
171
- environment: dodoEnvironment,
172
- type: "static",
173
- }));
174
-
175
- app.post("/api/checkout", checkoutHandler({
176
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
177
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
178
- environment: dodoEnvironment,
179
- type: "session",
180
- }));
181
- ```
182
-
183
- ### Customer Portal
184
-
185
- ```typescript
186
- import { CustomerPortal } from "@dodopayments/express";
187
-
188
- app.get("/api/customer-portal", CustomerPortal({
189
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
190
- environment: dodoEnvironment,
191
- }));
192
- ```
193
-
194
- ### Webhooks
195
-
196
- ```typescript
197
- import { Webhooks } from "@dodopayments/express";
198
-
199
- app.use(express.raw({ type: "application/json" }));
200
-
201
- app.post("/api/webhook", Webhooks({
202
- webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
203
- onPayload: async (payload) => {
204
- console.log("Webhook:", payload.type);
205
- },
206
- }));
207
- ```
176
+ - Checkout
177
+ - Customer Portal
178
+ - Webhooks
208
179
 
209
180
  ## Fastify
210
181
 
211
- **Package:** `@dodopayments/fastify`
212
-
213
- Fastify requires a string body parser to preserve the raw body for webhook verification.
214
-
215
- ### Checkout
216
-
217
- `Checkout(config)` returns an object with `getHandler` and `postHandler`, not a single callable. Build it once and mount each method, rather than calling the result.
218
-
219
- ```typescript
220
- import Fastify from "fastify";
221
- import { Checkout } from "@dodopayments/fastify";
222
- import { dodoEnvironment } from "./lib/dodo-env";
223
-
224
- const fastify = Fastify();
225
-
226
- const staticCheckout = Checkout({
227
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
228
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
229
- environment: dodoEnvironment,
230
- type: "static",
231
- });
232
-
233
- const sessionCheckout = Checkout({
234
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
235
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
236
- environment: dodoEnvironment,
237
- type: "session",
238
- });
239
-
240
- fastify.get("/api/checkout", staticCheckout.getHandler);
241
- fastify.post("/api/checkout", sessionCheckout.postHandler);
242
- ```
182
+ Full guide: [references/fastify.md](references/fastify.md).
243
183
 
244
- ### Webhooks
184
+ Covers:
245
185
 
246
- ```typescript
247
- import { Webhooks } from "@dodopayments/fastify";
248
-
249
- fastify.addContentTypeParser(
250
- "application/json",
251
- { parseAs: "string" },
252
- (req, body, done) => done(null, body)
253
- );
254
-
255
- fastify.post("/api/webhook", Webhooks({
256
- webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
257
- onPayload: async (payload) => {
258
- console.log("Webhook:", payload.type);
259
- },
260
- }));
261
- ```
186
+ - Checkout
187
+ - Webhooks
262
188
 
263
189
  ## Hono
264
190
 
265
- **Package:** `@dodopayments/hono`
266
-
267
- ### Checkout
268
-
269
- ```typescript
270
- import { Hono } from "hono";
271
- import { Checkout } from "@dodopayments/hono";
272
-
273
- const app = new Hono();
274
-
275
- app.get("/api/checkout", Checkout({
276
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
277
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
278
- environment: dodoEnvironment,
279
- type: "static",
280
- }));
281
-
282
- app.post("/api/checkout", Checkout({
283
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
284
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
285
- environment: dodoEnvironment,
286
- type: "session",
287
- }));
288
- ```
289
-
290
- ### Customer Portal
291
-
292
- ```typescript
293
- import { CustomerPortal } from "@dodopayments/hono";
294
-
295
- app.get("/api/customer-portal", CustomerPortal({
296
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
297
- environment: dodoEnvironment,
298
- }));
299
- ```
300
-
301
- ### Webhooks
191
+ Full guide: [references/hono.md](references/hono.md).
302
192
 
303
- ```typescript
304
- import { Webhooks } from "@dodopayments/hono";
193
+ Covers:
305
194
 
306
- app.post("/api/webhook", Webhooks({
307
- webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
308
- onPayload: async (payload) => {
309
- console.log("Webhook:", payload.type);
310
- },
311
- }));
312
- ```
195
+ - Checkout
196
+ - Customer Portal
197
+ - Webhooks
313
198
 
314
199
  ## Astro
315
200
 
316
- **Package:** `@dodopayments/astro`
317
- **Route placement:** `src/pages/api/checkout.ts`, `src/pages/api/customer-portal.ts`, `src/pages/api/webhook.ts`
318
-
319
- Disable prerendering for checkout routes.
320
-
321
- ### Checkout
322
-
323
- ```typescript
324
- // src/pages/api/checkout.ts
325
- import { Checkout } from "@dodopayments/astro";
326
-
327
- export const prerender = false;
328
-
329
- // Astro reads env from import.meta.env, which is typed as string - so it needs
330
- // the same narrowing as process.env. Define this alongside the other helper in
331
- // lib/dodo-env.ts if you use both.
332
- const dodoEnvironment =
333
- import.meta.env.DODO_PAYMENTS_ENVIRONMENT === "live_mode" ? "live_mode" : "test_mode";
334
-
335
- export const GET = Checkout({
336
- bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
337
- returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
338
- environment: dodoEnvironment,
339
- type: "static",
340
- });
341
-
342
- export const POST = Checkout({
343
- bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
344
- returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
345
- environment: dodoEnvironment,
346
- type: "session",
347
- });
348
- ```
349
-
350
- ### Webhooks
351
-
352
- ```typescript
353
- // src/pages/api/webhook.ts
354
- import { Webhooks } from "@dodopayments/astro";
201
+ Full guide: [references/astro.md](references/astro.md).
355
202
 
356
- export const prerender = false;
203
+ Covers:
357
204
 
358
- export const POST = Webhooks({
359
- webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY,
360
- onPayload: async (payload) => {
361
- console.log("Webhook:", payload.type);
362
- },
363
- });
364
- ```
205
+ - Checkout
206
+ - Webhooks
365
207
 
366
208
  ## Remix
367
209
 
368
- **Package:** `@dodopayments/remix`
210
+ Full guide: [references/remix.md](references/remix.md).
369
211
 
370
- ### Checkout
371
-
372
- ```typescript
373
- // app/routes/api.checkout.tsx
374
- import { Checkout } from "@dodopayments/remix";
375
- import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
212
+ Covers:
376
213
 
377
- const checkoutHandler = Checkout({
378
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
379
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
380
- environment: dodoEnvironment,
381
- type: "session",
382
- });
383
-
384
- export const loader = ({ request }: LoaderFunctionArgs) => checkoutHandler(request);
385
- export const action = ({ request }: ActionFunctionArgs) => checkoutHandler(request);
386
- ```
387
-
388
- ### Customer Portal
389
-
390
- ```typescript
391
- // app/routes/api.customer-portal.tsx
392
- import { CustomerPortal } from "@dodopayments/remix";
393
- import type { LoaderFunctionArgs } from "@remix-run/node";
394
-
395
- const portalHandler = CustomerPortal({
396
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
397
- environment: dodoEnvironment,
398
- });
399
-
400
- export const loader = ({ request }: LoaderFunctionArgs) => portalHandler(request);
401
- ```
402
-
403
- ### Webhooks
404
-
405
- ```typescript
406
- // app/routes/api.webhook.tsx
407
- import { Webhooks } from "@dodopayments/remix";
408
- import type { ActionFunctionArgs } from "@remix-run/node";
409
-
410
- export const action = ({ request }: ActionFunctionArgs) =>
411
- Webhooks({
412
- webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
413
- onPayload: async (payload) => {
414
- console.log("Webhook:", payload.type);
415
- },
416
- })(request);
417
- ```
214
+ - Checkout
215
+ - Customer Portal
216
+ - Webhooks
418
217
 
419
218
  ## SvelteKit
420
219
 
421
- **Package:** `@dodopayments/sveltekit`
422
- **Route placement:** `src/routes/api/checkout/+server.ts`, `src/routes/api/customer-portal/+server.ts`, `src/routes/api/webhook/+server.ts`
423
-
424
- ### Checkout
425
-
426
- ```typescript
427
- // src/routes/api/checkout/+server.ts
428
- import { Checkout } from "@dodopayments/sveltekit";
429
- import { DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_RETURN_URL, DODO_PAYMENTS_ENVIRONMENT } from "$env/static/private";
430
-
431
- const checkoutHandler = Checkout({
432
- bearerToken: DODO_PAYMENTS_API_KEY,
433
- returnUrl: DODO_PAYMENTS_RETURN_URL,
434
- environment: DODO_PAYMENTS_ENVIRONMENT,
435
- type: "session",
436
- });
437
-
438
- export const GET = checkoutHandler;
439
- export const POST = checkoutHandler;
440
- ```
441
-
442
- ### Webhooks
220
+ Full guide: [references/sveltekit.md](references/sveltekit.md).
443
221
 
444
- ```typescript
445
- // src/routes/api/webhook/+server.ts
446
- import { Webhooks } from "@dodopayments/sveltekit";
447
- import { DODO_PAYMENTS_WEBHOOK_KEY } from "$env/static/private";
222
+ Covers:
448
223
 
449
- export const POST = Webhooks({
450
- webhookKey: DODO_PAYMENTS_WEBHOOK_KEY,
451
- onPayload: async (payload) => {
452
- console.log("Webhook:", payload.type);
453
- },
454
- });
455
- ```
224
+ - Checkout
225
+ - Webhooks
456
226
 
457
227
  ## Nuxt
458
228
 
459
- **Package:** `@dodopayments/nuxt`
460
-
461
- Add the module to `nuxt.config.ts` and configure runtime variables:
462
-
463
- ```typescript
464
- // nuxt.config.ts
465
- export default defineNuxtConfig({
466
- modules: ["@dodopayments/nuxt"],
467
- runtimeConfig: {
468
- private: {
469
- bearerToken: process.env.NUXT_PRIVATE_BEARER_TOKEN,
470
- webhookKey: process.env.NUXT_PRIVATE_WEBHOOK_KEY,
471
- environment: process.env.NUXT_PRIVATE_ENVIRONMENT,
472
- returnUrl: process.env.NUXT_PRIVATE_RETURNURL,
473
- },
474
- },
475
- });
476
- ```
477
-
478
- ### Checkout
229
+ Full guide: [references/nuxt.md](references/nuxt.md).
479
230
 
480
- The Nuxt module registers its handlers with `addServerImportsDir`, so `checkoutHandler`, `customerPortalHandler`, and `Webhooks` are **auto-imported** inside `server/`. Do not import them from `@dodopayments/nuxt` — that entry point exports only the Nuxt module itself, and a named import from it will not resolve.
231
+ Covers:
481
232
 
482
- ```typescript
483
- // server/routes/api/checkout.ts
484
- // checkoutHandler and useRuntimeConfig are auto-imported by the module.
485
- const config = useRuntimeConfig();
486
-
487
- export default checkoutHandler({
488
- bearerToken: config.private.bearerToken,
489
- returnUrl: config.private.returnUrl,
490
- environment: config.private.environment,
491
- type: "session",
492
- });
493
- ```
494
-
495
- ### Webhooks
496
-
497
- ```typescript
498
- // server/routes/api/webhook.ts
499
- // Webhooks and useRuntimeConfig are auto-imported by the module.
500
- const config = useRuntimeConfig();
501
-
502
- export default Webhooks({
503
- webhookKey: config.private.webhookKey,
504
- onPayload: async (payload) => {
505
- console.log("Webhook:", payload.type);
506
- },
507
- });
508
- ```
233
+ - Checkout
234
+ - Webhooks
509
235
 
510
236
  ## TanStack Start
511
237
 
512
- **Package:** `@dodopayments/tanstack`
513
-
514
- ### Checkout
515
-
516
- `Checkout(config)` returns a plain `(request: Request) => Promise<Response>`, so export it directly as the route's method handler. The adapter's own documented usage is `export const GET = Checkout(config)`.
517
-
518
- ```typescript
519
- // src/routes/api/checkout.ts
520
- import { Checkout } from "@dodopayments/tanstack";
521
- import { dodoEnvironment } from "./lib/dodo-env";
522
-
523
- export const GET = Checkout({
524
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
525
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
526
- environment: dodoEnvironment,
527
- type: "static",
528
- });
238
+ Full guide: [references/tanstack-start.md](references/tanstack-start.md).
529
239
 
530
- export const POST = Checkout({
531
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
532
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
533
- environment: dodoEnvironment,
534
- type: "session",
535
- });
536
- ```
240
+ Covers:
537
241
 
538
- TanStack Start's server-route definition API has changed across releases (`createServerFileRoute` was removed). Wrap these exports in whatever route helper your installed version provides; the adapter handlers themselves are unaffected.
242
+ - Checkout
539
243
 
540
244
  ## Bun
541
245
 
542
- **Package:** `@dodopayments/bun`
543
-
544
- ### Checkout and Portal
545
-
546
- ```typescript
547
- import { Checkout, CustomerPortal } from "@dodopayments/bun";
548
-
549
- const checkoutHandler = Checkout({
550
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
551
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
552
- environment: dodoEnvironment,
553
- type: "session",
554
- });
246
+ Full guide: [references/bun.md](references/bun.md).
555
247
 
556
- const portalHandler = CustomerPortal({
557
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
558
- environment: dodoEnvironment,
559
- });
560
-
561
- Bun.serve({
562
- port: 3000,
563
- fetch(request) {
564
- const url = new URL(request.url);
248
+ Covers:
565
249
 
566
- if (url.pathname === "/api/checkout") {
567
- return checkoutHandler(request);
568
- }
569
- if (url.pathname === "/api/customer-portal" && request.method === "GET") {
570
- return portalHandler(request);
571
- }
572
-
573
- return new Response("Not Found", { status: 404 });
574
- },
575
- });
576
- ```
250
+ - Checkout and Portal
577
251
 
578
252
  ## Convex
579
253
 
580
- **Package:** `@dodopayments/convex`
581
-
582
- Convex uses a component-based architecture. Register the component in `convex.config.ts`:
583
-
584
- ```typescript
585
- // convex/convex.config.ts
586
- import { defineApp } from "convex/server";
587
- import dodopayments from "@dodopayments/convex/convex.config";
588
-
589
- const app = defineApp();
590
- app.use(dodopayments);
591
- export default app;
592
- ```
593
-
594
- Then use the component in your actions and HTTP routes:
595
-
596
- ```typescript
597
- // convex/checkout.ts
598
- import { mutation } from "./_generated/server";
599
- import { components } from "./_generated/server";
600
-
601
- export const createCheckoutSession = mutation({
602
- args: { customerId: v.string() },
603
- handler: async (ctx, args) => {
604
- const dodo = components.dodopayments;
605
- return dodo.checkout.createSession(ctx, {
606
- customerId: args.customerId,
607
- // ... checkout params
608
- });
609
- },
610
- });
611
- ```
612
-
613
- Convex only supports session checkout, not static or dynamic modes.
254
+ Full guide: [references/convex.md](references/convex.md).
614
255
 
615
256
  ## Common mistakes
616
257
 
@@ -618,7 +259,7 @@ Convex only supports session checkout, not static or dynamic modes.
618
259
 
619
260
  2. **Wrong webhook variable name:** Check whether your adapter uses `DODO_PAYMENTS_WEBHOOK_KEY` or `DODO_PAYMENTS_WEBHOOK_SECRET`. The docs are inconsistent; use the name your adapter actually references.
620
261
 
621
- 3. **Forgetting raw body preservation:** Webhook handlers must receive the raw request body, not a re-parsed JSON object. Fastify requires an explicit string body parser; Express needs `express.raw()`; other frameworks handle this automatically. Webhook signature verification is covered in the `webhook-integration` skill.
262
+ 3. **Forgetting raw body preservation:** Webhook handlers must receive the raw request body, not a re-parsed JSON object. Fastify requires an explicit string body parser; other frameworks handle this automatically. **Express is the exception:** its `Webhooks` handler verifies against the parsed `req.body`, so register `express.json()` before the route and do not use `express.raw()`, which makes every verification fail. Webhook signature verification is covered in the `webhook-integration` skill.
622
263
 
623
264
  4. **Mixing framework conventions:** Each framework has its own request/response shape. Don't try to use a Next.js handler in Express or vice versa. Use the adapter for your framework.
624
265
 
@@ -626,7 +267,9 @@ Convex only supports session checkout, not static or dynamic modes.
626
267
 
627
268
  6. **Skipping environment setup:** The adapters won't work without `DODO_PAYMENTS_API_KEY` and `DODO_PAYMENTS_ENVIRONMENT`. Set these before testing.
628
269
 
629
- 7. **Using `@dodopayments/core` directly:** The core package is an internal dependency, not a documented public entry point. Use the framework adapter for your stack.
270
+ 7. **Exposing CustomerPortal unauthenticated:** The portal handlers trust `?customer_id=` from the request. Resolve the ID from the signed-in session on the server, as in the examples above, or anyone can open any customer's portal.
271
+
272
+ 8. **Using `@dodopayments/core` directly:** The core package is an internal dependency, not a documented public entry point. Use the framework adapter for your stack.
630
273
 
631
274
  ## Resources
632
275
 
@@ -0,0 +1,52 @@
1
+ # Framework Adapters: Astro
2
+
3
+
4
+ **Package:** `@dodopayments/astro`
5
+ **Route placement:** `src/pages/api/checkout.ts`, `src/pages/api/customer-portal.ts`, `src/pages/api/webhook.ts`
6
+
7
+ Disable prerendering for checkout routes.
8
+
9
+ ### Checkout
10
+
11
+ ```typescript
12
+ // src/pages/api/checkout.ts
13
+ import { Checkout } from "@dodopayments/astro";
14
+
15
+ export const prerender = false;
16
+
17
+ // Astro reads env from import.meta.env, which is typed as string - so it needs
18
+ // the same narrowing as process.env. Define this alongside the other helper in
19
+ // lib/dodo-env.ts if you use both.
20
+ const dodoEnvironment =
21
+ import.meta.env.DODO_PAYMENTS_ENVIRONMENT === "live_mode" ? "live_mode" : "test_mode";
22
+
23
+ export const GET = Checkout({
24
+ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
25
+ returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
26
+ environment: dodoEnvironment,
27
+ type: "static",
28
+ });
29
+
30
+ export const POST = Checkout({
31
+ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
32
+ returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
33
+ environment: dodoEnvironment,
34
+ type: "session",
35
+ });
36
+ ```
37
+
38
+ ### Webhooks
39
+
40
+ ```typescript
41
+ // src/pages/api/webhook.ts
42
+ import { Webhooks } from "@dodopayments/astro";
43
+
44
+ export const prerender = false;
45
+
46
+ export const POST = Webhooks({
47
+ webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY,
48
+ onPayload: async (payload) => {
49
+ console.log("Webhook:", payload.type);
50
+ },
51
+ });
52
+ ```
@@ -0,0 +1,45 @@
1
+ # Framework Adapters: Bun
2
+
3
+
4
+ **Package:** `@dodopayments/bun`
5
+
6
+ ### Checkout and Portal
7
+
8
+ ```typescript
9
+ import { Checkout, CustomerPortal } from "@dodopayments/bun";
10
+ import { dodoEnvironment } from "./lib/dodo-env";
11
+ // your auth helper
12
+ import { getSessionUser } from "./lib/auth";
13
+
14
+ const checkoutHandler = Checkout({
15
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
16
+ returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
17
+ environment: dodoEnvironment,
18
+ type: "session",
19
+ });
20
+
21
+ const portalHandler = CustomerPortal({
22
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
23
+ environment: dodoEnvironment,
24
+ });
25
+
26
+ Bun.serve({
27
+ port: 3000,
28
+ async fetch(request) {
29
+ const url = new URL(request.url);
30
+
31
+ if (url.pathname === "/api/checkout") {
32
+ return checkoutHandler(request);
33
+ }
34
+ if (url.pathname === "/api/customer-portal" && request.method === "GET") {
35
+ const user = await getSessionUser(request);
36
+ if (!user?.dodoCustomerId) return new Response("Unauthorized", { status: 401 });
37
+ // Overwrite any client-supplied ?customer_id= with the signed-in user's ID.
38
+ url.searchParams.set("customer_id", user.dodoCustomerId);
39
+ return portalHandler(new Request(url, request));
40
+ }
41
+
42
+ return new Response("Not Found", { status: 404 });
43
+ },
44
+ });
45
+ ```