@dodopayments/opencode-plugin 0.3.3 → 0.4.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,644 @@
1
+ ---
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.
4
+ ---
5
+
6
+ # Framework Adapters
7
+
8
+ Use this skill when you need to integrate Dodo Payments into a web framework using the official adapter packages. Framework adapters handle route plumbing, environment configuration, and handler setup so you don't build checkout, portal, and webhook routes from scratch.
9
+
10
+ ## When to use this skill
11
+
12
+ - You're building a checkout flow in Next.js, Express, Fastify, Hono, Astro, Remix, SvelteKit, Nuxt, TanStack, Bun, or Convex.
13
+ - You need to set up a customer portal session endpoint.
14
+ - You're adding webhook handlers that verify signatures and dispatch events.
15
+ - You want framework-idiomatic route placement and environment variable handling.
16
+ - You need to choose between static, dynamic, or session checkout modes.
17
+
18
+ ## What framework adapters do
19
+
20
+ Dodo publishes `@dodopayments/*` packages that wrap the core SDK with framework-specific route handlers. Instead of writing your own HTTP handlers, you import the adapter's handlers and mount them directly in your routes.
21
+
22
+ Each adapter exposes three handler families:
23
+
24
+ - **Checkout:** static (GET only), dynamic (POST with cart), or session (POST with pre-built session).
25
+ - **CustomerPortal:** generates a time-bound portal session link.
26
+ - **Webhooks:** verifies webhook signatures and dispatches typed events.
27
+
28
+ **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
+
30
+ | Adapter | Checkout export | Portal export | Handler shape |
31
+ |---|---|---|---|
32
+ | `nextjs`, `hono`, `astro`, `bun`, `remix`, `tanstack` | `Checkout` | `CustomerPortal` | returns a request handler |
33
+ | `express` | `checkoutHandler` (lowercase) | `CustomerPortal` | returns `(req, res)` |
34
+ | `fastify` | `Checkout` | `CustomerPortal` | returns `{ getHandler, postHandler }` |
35
+ | `sveltekit` | `Checkout` | `CustomerPortal` | returns `{ GET, POST }` / `{ GET }` |
36
+ | `nuxt` | `checkoutHandler` (auto-imported) | `customerPortalHandler` | no import statement |
37
+ | `convex` | `DodoPayments` component | — | `createDodoWebhookHandler` |
38
+
39
+ 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
+
41
+ ## Framework selection and installation
42
+
43
+ | Framework | Package | Install |
44
+ |---|---|---|
45
+ | Next.js | `@dodopayments/nextjs` | `npm install @dodopayments/nextjs` |
46
+ | Express | `@dodopayments/express` | `npm install @dodopayments/express` |
47
+ | Fastify | `@dodopayments/fastify` | `npm install @dodopayments/fastify` |
48
+ | Hono | `@dodopayments/hono` | `npm install @dodopayments/hono` |
49
+ | Astro | `@dodopayments/astro` | `npm install @dodopayments/astro` |
50
+ | Remix | `@dodopayments/remix` | `npm install @dodopayments/remix` |
51
+ | SvelteKit | `@dodopayments/sveltekit` | `npm install @dodopayments/sveltekit` |
52
+ | Nuxt | `@dodopayments/nuxt` | `npm install @dodopayments/nuxt` |
53
+ | TanStack Start | `@dodopayments/tanstack` | `npm install @dodopayments/tanstack` |
54
+ | Bun | `@dodopayments/bun` | `bun add @dodopayments/bun` |
55
+ | Convex | `@dodopayments/convex` | `npm install @dodopayments/convex` |
56
+
57
+ ## Environment variables
58
+
59
+ Most adapters use these standard names:
60
+
61
+ ```env
62
+ DODO_PAYMENTS_API_KEY=dodo_test_...
63
+ DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
64
+ DODO_PAYMENTS_ENVIRONMENT=test_mode
65
+ DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success
66
+ ```
67
+
68
+ **Nuxt** uses private runtime config prefixes:
69
+
70
+ ```env
71
+ NUXT_PRIVATE_BEARER_TOKEN=dodo_test_...
72
+ NUXT_PRIVATE_WEBHOOK_KEY=your-webhook-secret
73
+ NUXT_PRIVATE_ENVIRONMENT=test_mode
74
+ NUXT_PRIVATE_RETURNURL=https://yourdomain.com/checkout/success
75
+ ```
76
+
77
+ **Convex** uses dashboard environment variables (not local `.env`):
78
+
79
+ ```env
80
+ DODO_PAYMENTS_API_KEY=dodo_test_...
81
+ DODO_PAYMENTS_ENVIRONMENT=test_mode
82
+ DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret
83
+ ```
84
+
85
+ **Webhook key naming:** Some adapters reference `DODO_PAYMENTS_WEBHOOK_KEY`, others use `DODO_PAYMENTS_WEBHOOK_SECRET`. Check your framework's adapter docs and dashboard configuration to use the correct variable name.
86
+
87
+ ### Narrowing `environment`
88
+
89
+ Adapter configs type `environment` as `Pick<ClientOptions, "environment">`, i.e. the literal union `"test_mode" | "live_mode"`. `process.env.X` is `string | undefined`, which does **not** assign to it — passing it directly is a type error in every adapter.
90
+
91
+ Define this helper once and import it wherever you construct an adapter config:
92
+
93
+ ```typescript
94
+ // lib/dodo-env.ts
95
+ export const dodoEnvironment =
96
+ process.env.DODO_PAYMENTS_ENVIRONMENT === "live_mode" ? "live_mode" : "test_mode";
97
+ ```
98
+
99
+ Defaulting to `test_mode` is deliberate: a missing or misspelled variable must never silently resolve to live mode. Every example below uses `dodoEnvironment`.
100
+
101
+ ## Next.js
102
+
103
+ **Package:** `@dodopayments/nextjs`
104
+ **Route placement:** `app/api/checkout/route.ts`, `app/api/customer-portal/route.ts`, `app/api/webhook/route.ts`
105
+
106
+ ### Checkout (choose one mode per route)
107
+
108
+ ```typescript
109
+ // app/api/checkout/route.ts
110
+ import { Checkout } from "@dodopayments/nextjs";
111
+
112
+ export const GET = Checkout({
113
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
114
+ returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
115
+ environment: dodoEnvironment,
116
+ type: "static",
117
+ });
118
+
119
+ export const POST = Checkout({
120
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
121
+ returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
122
+ environment: dodoEnvironment,
123
+ type: "session",
124
+ });
125
+ ```
126
+
127
+ ### Customer Portal
128
+
129
+ ```typescript
130
+ // app/api/customer-portal/route.ts
131
+ import { CustomerPortal } from "@dodopayments/nextjs";
132
+
133
+ export const GET = CustomerPortal({
134
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
135
+ environment: dodoEnvironment,
136
+ });
137
+ ```
138
+
139
+ ### Webhooks
140
+
141
+ ```typescript
142
+ // app/api/webhook/route.ts
143
+ import { Webhooks } from "@dodopayments/nextjs";
144
+
145
+ export const POST = Webhooks({
146
+ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
147
+ onPayload: async (payload) => {
148
+ console.log("Webhook received:", payload.type);
149
+ },
150
+ });
151
+ ```
152
+
153
+ ## Express
154
+
155
+ **Package:** `@dodopayments/express`
156
+
157
+ ### Checkout
158
+
159
+ The Express adapter names its checkout export `checkoutHandler` in lowercase, unlike every other adapter. `import { Checkout } from "@dodopayments/express"` does not resolve.
160
+
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
+ ```
208
+
209
+ ## Fastify
210
+
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
+ ```
243
+
244
+ ### Webhooks
245
+
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
+ ```
262
+
263
+ ## Hono
264
+
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
302
+
303
+ ```typescript
304
+ import { Webhooks } from "@dodopayments/hono";
305
+
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
+ ```
313
+
314
+ ## Astro
315
+
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";
355
+
356
+ export const prerender = false;
357
+
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
+ ```
365
+
366
+ ## Remix
367
+
368
+ **Package:** `@dodopayments/remix`
369
+
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";
376
+
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
+ ```
418
+
419
+ ## SvelteKit
420
+
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
443
+
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";
448
+
449
+ export const POST = Webhooks({
450
+ webhookKey: DODO_PAYMENTS_WEBHOOK_KEY,
451
+ onPayload: async (payload) => {
452
+ console.log("Webhook:", payload.type);
453
+ },
454
+ });
455
+ ```
456
+
457
+ ## Nuxt
458
+
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
479
+
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.
481
+
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
+ ```
509
+
510
+ ## TanStack Start
511
+
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
+ });
529
+
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
+ ```
537
+
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.
539
+
540
+ ## Bun
541
+
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
+ });
555
+
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);
565
+
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
+ ```
577
+
578
+ ## Convex
579
+
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.
614
+
615
+ ## Common mistakes
616
+
617
+ 1. **Duplicate POST handlers:** Next.js and Astro docs show two `export const POST` declarations. Choose one checkout mode per route or add routing logic to dispatch between them.
618
+
619
+ 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
+
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.
622
+
623
+ 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
+
625
+ 5. **Hardcoding secrets:** Always read API keys and webhook secrets from environment variables, never from code or config files.
626
+
627
+ 6. **Skipping environment setup:** The adapters won't work without `DODO_PAYMENTS_API_KEY` and `DODO_PAYMENTS_ENVIRONMENT`. Set these before testing.
628
+
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.
630
+
631
+ ## Resources
632
+
633
+ - [Framework Adapters Overview](https://docs.dodopayments.com/developer-resources/framework-adaptors)
634
+ - [Next.js Adapter](https://docs.dodopayments.com/developer-resources/nextjs-adaptor)
635
+ - [Express Adapter](https://docs.dodopayments.com/developer-resources/express-adaptor)
636
+ - [Fastify Adapter](https://docs.dodopayments.com/developer-resources/fastify-adaptor)
637
+ - [Hono Adapter](https://docs.dodopayments.com/developer-resources/hono-adaptor)
638
+ - [Astro Adapter](https://docs.dodopayments.com/developer-resources/astro-adaptor)
639
+ - [Remix Adapter](https://docs.dodopayments.com/developer-resources/remix-adaptor)
640
+ - [SvelteKit Adapter](https://docs.dodopayments.com/developer-resources/sveltekit-adaptor)
641
+ - [Nuxt Adapter](https://docs.dodopayments.com/developer-resources/nuxt-adaptor)
642
+ - [TanStack Adapter](https://docs.dodopayments.com/developer-resources/tanstack-adaptor)
643
+ - [Bun Adapter](https://docs.dodopayments.com/developer-resources/bun-adaptor)
644
+ - [Convex Component](https://docs.dodopayments.com/developer-resources/convex-component)