@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.
- package/README.md +1 -0
- package/opencode-plugin/README.md +1 -1
- package/package.json +1 -1
- package/skills/better-auth-integration/SKILL.md +12 -3
- package/skills/billing-sdk/SKILL.md +15 -167
- package/skills/billing-sdk/references/pricing-page.md +197 -0
- package/skills/checkout-integration/SKILL.md +31 -460
- package/skills/checkout-integration/references/overlay-checkout.md +148 -0
- package/skills/checkout-integration/references/server-examples.md +317 -0
- package/skills/credit-based-billing/SKILL.md +11 -158
- package/skills/credit-based-billing/references/ai-token-example.md +108 -0
- package/skills/credit-based-billing/references/credit-webhooks.md +51 -0
- package/skills/customer-management/SKILL.md +25 -17
- package/skills/discounts-and-promotions/SKILL.md +20 -17
- package/skills/dodo-best-practices/SKILL.md +18 -5
- package/skills/dodo-mcp-usage/SKILL.md +152 -0
- package/skills/framework-adapters/SKILL.md +64 -421
- package/skills/framework-adapters/references/astro.md +52 -0
- package/skills/framework-adapters/references/bun.md +45 -0
- package/skills/framework-adapters/references/convex.md +91 -0
- package/skills/framework-adapters/references/express.md +72 -0
- package/skills/framework-adapters/references/fastify.md +54 -0
- package/skills/framework-adapters/references/hono.md +63 -0
- package/skills/framework-adapters/references/nuxt.md +53 -0
- package/skills/framework-adapters/references/remix.md +65 -0
- package/skills/framework-adapters/references/sveltekit.md +49 -0
- package/skills/framework-adapters/references/tanstack-start.md +31 -0
- package/skills/license-keys/SKILL.md +117 -367
- package/skills/license-keys/references/cli-tool.md +143 -0
- package/skills/license-keys/references/desktop-app.md +170 -0
- package/skills/localized-pricing/SKILL.md +51 -29
- package/skills/mobile-checkout/SKILL.md +72 -310
- package/skills/mobile-checkout/references/android.md +69 -0
- package/skills/mobile-checkout/references/flutter.md +97 -0
- package/skills/mobile-checkout/references/ios.md +66 -0
- package/skills/mobile-checkout/references/react-native.md +100 -0
- package/skills/product-catalog-management/SKILL.md +39 -16
- package/skills/refunds-and-disputes/SKILL.md +91 -71
- package/skills/subscription-integration/SKILL.md +43 -64
- package/skills/subscription-integration/references/webhook-events.md +82 -0
- package/skills/testing-and-go-live/SKILL.md +22 -15
- package/skills/usage-based-billing/SKILL.md +16 -122
- package/skills/usage-based-billing/references/instrumentation.md +119 -0
- package/skills/webhook-integration/SKILL.md +31 -263
- package/skills/webhook-integration/references/common-mistakes.md +146 -0
- package/skills/webhook-integration/references/event-catalog.md +103 -0
- package/skills/webhook-integration/references/local-testing.md +42 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framework-adapters
|
|
3
|
-
description:
|
|
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
|
|
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` | `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
### Checkout
|
|
172
|
+
Full guide: [references/express.md](references/express.md).
|
|
158
173
|
|
|
159
|
-
|
|
174
|
+
Covers:
|
|
160
175
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
+
Covers:
|
|
245
185
|
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
304
|
-
import { Webhooks } from "@dodopayments/hono";
|
|
193
|
+
Covers:
|
|
305
194
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
console.log("Webhook:", payload.type);
|
|
310
|
-
},
|
|
311
|
-
}));
|
|
312
|
-
```
|
|
195
|
+
- Checkout
|
|
196
|
+
- Customer Portal
|
|
197
|
+
- Webhooks
|
|
313
198
|
|
|
314
199
|
## Astro
|
|
315
200
|
|
|
316
|
-
|
|
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
|
-
|
|
203
|
+
Covers:
|
|
357
204
|
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
210
|
+
Full guide: [references/remix.md](references/remix.md).
|
|
369
211
|
|
|
370
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
450
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
+
Covers:
|
|
481
232
|
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
+
- Checkout
|
|
539
243
|
|
|
540
244
|
## Bun
|
|
541
245
|
|
|
542
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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. **
|
|
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
|
+
```
|