@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.
- package/README.md +48 -12
- package/opencode-plugin/README.md +3 -3
- package/package.json +1 -1
- package/skills/best-practices/SKILL.md +335 -179
- package/skills/better-auth-integration/SKILL.md +214 -0
- package/skills/billing-sdk/SKILL.md +357 -370
- package/skills/checkout-integration/SKILL.md +580 -329
- package/skills/credit-based-billing/SKILL.md +448 -775
- package/skills/customer-management/SKILL.md +281 -0
- package/skills/discounts-and-promotions/SKILL.md +464 -0
- package/skills/framework-adapters/SKILL.md +644 -0
- package/skills/license-keys/SKILL.md +324 -199
- package/skills/localized-pricing/SKILL.md +230 -0
- package/skills/mobile-checkout/SKILL.md +521 -0
- package/skills/product-catalog-management/SKILL.md +311 -0
- package/skills/refunds-and-disputes/SKILL.md +333 -0
- package/skills/subscription-integration/SKILL.md +323 -404
- package/skills/testing-and-go-live/SKILL.md +335 -0
- package/skills/usage-based-billing/SKILL.md +339 -479
- package/skills/webhook-integration/SKILL.md +600 -402
|
@@ -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)
|