@dodopayments/opencode-plugin 0.6.1 → 0.6.3

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 (37) hide show
  1. package/package.json +1 -1
  2. package/skills/billing-sdk/SKILL.md +8 -193
  3. package/skills/billing-sdk/references/pricing-page.md +197 -0
  4. package/skills/checkout-integration/SKILL.md +14 -449
  5. package/skills/checkout-integration/references/overlay-checkout.md +148 -0
  6. package/skills/checkout-integration/references/server-examples.md +317 -0
  7. package/skills/credit-based-billing/SKILL.md +5 -151
  8. package/skills/credit-based-billing/references/ai-token-example.md +108 -0
  9. package/skills/credit-based-billing/references/credit-webhooks.md +51 -0
  10. package/skills/framework-adapters/SKILL.md +38 -527
  11. package/skills/framework-adapters/references/astro.md +52 -0
  12. package/skills/framework-adapters/references/bun.md +45 -0
  13. package/skills/framework-adapters/references/convex.md +91 -0
  14. package/skills/framework-adapters/references/express.md +72 -0
  15. package/skills/framework-adapters/references/fastify.md +54 -0
  16. package/skills/framework-adapters/references/hono.md +63 -0
  17. package/skills/framework-adapters/references/nuxt.md +53 -0
  18. package/skills/framework-adapters/references/remix.md +65 -0
  19. package/skills/framework-adapters/references/sveltekit.md +49 -0
  20. package/skills/framework-adapters/references/tanstack-start.md +31 -0
  21. package/skills/license-keys/SKILL.md +8 -303
  22. package/skills/license-keys/references/cli-tool.md +143 -0
  23. package/skills/license-keys/references/desktop-app.md +170 -0
  24. package/skills/mobile-checkout/SKILL.md +25 -313
  25. package/skills/mobile-checkout/references/android.md +69 -0
  26. package/skills/mobile-checkout/references/flutter.md +97 -0
  27. package/skills/mobile-checkout/references/ios.md +66 -0
  28. package/skills/mobile-checkout/references/react-native.md +100 -0
  29. package/skills/refunds-and-disputes/SKILL.md +1 -1
  30. package/skills/subscription-integration/SKILL.md +3 -77
  31. package/skills/subscription-integration/references/webhook-events.md +82 -0
  32. package/skills/usage-based-billing/SKILL.md +5 -114
  33. package/skills/usage-based-billing/references/instrumentation.md +119 -0
  34. package/skills/webhook-integration/SKILL.md +28 -277
  35. package/skills/webhook-integration/references/common-mistakes.md +146 -0
  36. package/skills/webhook-integration/references/event-catalog.md +103 -0
  37. package/skills/webhook-integration/references/local-testing.md +42 -0
@@ -169,578 +169,89 @@ export const POST = Webhooks({
169
169
 
170
170
  ## Express
171
171
 
172
- **Package:** `@dodopayments/express`
172
+ Full guide: [references/express.md](references/express.md).
173
173
 
174
- ### Checkout
174
+ Covers:
175
175
 
176
- The Express adapter names its checkout export `checkoutHandler` in lowercase, unlike every other adapter. `import { Checkout } from "@dodopayments/express"` does not resolve.
177
-
178
- ```typescript
179
- import express from "express";
180
- import { checkoutHandler } from "@dodopayments/express";
181
- import { dodoEnvironment } from "./lib/dodo-env";
182
-
183
- const app = express();
184
- // Required before the POST route: the session/dynamic handlers read req.body.
185
- app.use(express.json());
186
-
187
- app.get("/api/checkout", checkoutHandler({
188
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
189
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
190
- environment: dodoEnvironment,
191
- type: "static",
192
- }));
193
-
194
- app.post("/api/checkout", checkoutHandler({
195
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
196
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
197
- environment: dodoEnvironment,
198
- type: "session",
199
- }));
200
- ```
201
-
202
- ### Customer Portal
203
-
204
- The adapter's `CustomerPortal` reads `customer_id` from `req.query`, which you cannot safely rewrite in Express 5. Behind your auth middleware, call the SDK directly with the session's customer ID instead:
205
-
206
- ```typescript
207
- import DodoPayments from "dodopayments";
208
- import { dodoEnvironment } from "./lib/dodo-env";
209
-
210
- const client = new DodoPayments({
211
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
212
- environment: dodoEnvironment,
213
- });
214
-
215
- // requireAuth = your session middleware; it must set req.user.
216
- app.get("/api/customer-portal", requireAuth, async (req, res) => {
217
- const customerId = req.user?.dodoCustomerId; // from the session, never from req.query
218
- if (!customerId) return res.status(401).send("Unauthorized");
219
- const session = await client.customers.customerPortal.create(customerId);
220
- res.redirect(session.link);
221
- });
222
- ```
223
-
224
- ### Webhooks
225
-
226
- ```typescript
227
- import { Webhooks } from "@dodopayments/express";
228
-
229
- // The Express handler verifies the signature against the PARSED req.body, so it
230
- // needs express.json() (skip this line if already registered). Do NOT use express.raw() here - the
231
- // handler would see a Buffer and reject every request.
232
- app.use(express.json());
233
-
234
- app.post("/api/webhook", Webhooks({
235
- webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
236
- onPayload: async (payload) => {
237
- console.log("Webhook:", payload.type);
238
- },
239
- }));
240
- ```
176
+ - Checkout
177
+ - Customer Portal
178
+ - Webhooks
241
179
 
242
180
  ## Fastify
243
181
 
244
- **Package:** `@dodopayments/fastify`
245
-
246
- Fastify requires a string body parser to preserve the raw body for webhook verification.
247
-
248
- ### Checkout
249
-
250
- `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.
251
-
252
- ```typescript
253
- import Fastify from "fastify";
254
- import { Checkout } from "@dodopayments/fastify";
255
- import { dodoEnvironment } from "./lib/dodo-env";
256
-
257
- const fastify = Fastify();
258
-
259
- const staticCheckout = Checkout({
260
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
261
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
262
- environment: dodoEnvironment,
263
- type: "static",
264
- });
265
-
266
- const sessionCheckout = Checkout({
267
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
268
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
269
- environment: dodoEnvironment,
270
- type: "session",
271
- });
272
-
273
- fastify.get("/api/checkout", staticCheckout.getHandler);
274
- fastify.post("/api/checkout", sessionCheckout.postHandler);
275
- ```
276
-
277
- ### Webhooks
278
-
279
- ```typescript
280
- import { Webhooks } from "@dodopayments/fastify";
182
+ Full guide: [references/fastify.md](references/fastify.md).
281
183
 
282
- fastify.addContentTypeParser(
283
- "application/json",
284
- { parseAs: "string" },
285
- (req, body, done) => done(null, body)
286
- );
184
+ Covers:
287
185
 
288
- fastify.post("/api/webhook", Webhooks({
289
- webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
290
- onPayload: async (payload) => {
291
- console.log("Webhook:", payload.type);
292
- },
293
- }));
294
- ```
186
+ - Checkout
187
+ - Webhooks
295
188
 
296
189
  ## Hono
297
190
 
298
- **Package:** `@dodopayments/hono`
191
+ Full guide: [references/hono.md](references/hono.md).
299
192
 
300
- ### Checkout
193
+ Covers:
301
194
 
302
- ```typescript
303
- import { Hono } from "hono";
304
- import { Checkout } from "@dodopayments/hono";
305
- import { dodoEnvironment } from "./lib/dodo-env";
306
-
307
- const app = new Hono();
308
-
309
- app.get("/api/checkout", Checkout({
310
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
311
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
312
- environment: dodoEnvironment,
313
- type: "static",
314
- }));
315
-
316
- app.post("/api/checkout", Checkout({
317
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
318
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
319
- environment: dodoEnvironment,
320
- type: "session",
321
- }));
322
- ```
323
-
324
- ### Customer Portal
325
-
326
- The adapter's `CustomerPortal` reads `c.req.query("customer_id")`. Behind your auth middleware, call the SDK directly with the session's customer ID instead:
327
-
328
- ```typescript
329
- import DodoPayments from "dodopayments";
330
- import { dodoEnvironment } from "./lib/dodo-env";
331
-
332
- const client = new DodoPayments({
333
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
334
- environment: dodoEnvironment,
335
- });
336
-
337
- // Assumes your auth middleware ran first and stored the user on the context.
338
- app.get("/api/customer-portal", async (c) => {
339
- const customerId = c.get("user")?.dodoCustomerId; // from the session, never from the query
340
- if (!customerId) return c.text("Unauthorized", 401);
341
- const session = await client.customers.customerPortal.create(customerId);
342
- return c.redirect(session.link);
343
- });
344
- ```
345
-
346
- ### Webhooks
347
-
348
- ```typescript
349
- import { Webhooks } from "@dodopayments/hono";
350
-
351
- app.post("/api/webhook", Webhooks({
352
- webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
353
- onPayload: async (payload) => {
354
- console.log("Webhook:", payload.type);
355
- },
356
- }));
357
- ```
195
+ - Checkout
196
+ - Customer Portal
197
+ - Webhooks
358
198
 
359
199
  ## Astro
360
200
 
361
- **Package:** `@dodopayments/astro`
362
- **Route placement:** `src/pages/api/checkout.ts`, `src/pages/api/customer-portal.ts`, `src/pages/api/webhook.ts`
363
-
364
- Disable prerendering for checkout routes.
365
-
366
- ### Checkout
201
+ Full guide: [references/astro.md](references/astro.md).
367
202
 
368
- ```typescript
369
- // src/pages/api/checkout.ts
370
- import { Checkout } from "@dodopayments/astro";
371
-
372
- export const prerender = false;
203
+ Covers:
373
204
 
374
- // Astro reads env from import.meta.env, which is typed as string - so it needs
375
- // the same narrowing as process.env. Define this alongside the other helper in
376
- // lib/dodo-env.ts if you use both.
377
- const dodoEnvironment =
378
- import.meta.env.DODO_PAYMENTS_ENVIRONMENT === "live_mode" ? "live_mode" : "test_mode";
379
-
380
- export const GET = Checkout({
381
- bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
382
- returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
383
- environment: dodoEnvironment,
384
- type: "static",
385
- });
386
-
387
- export const POST = Checkout({
388
- bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
389
- returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
390
- environment: dodoEnvironment,
391
- type: "session",
392
- });
393
- ```
394
-
395
- ### Webhooks
396
-
397
- ```typescript
398
- // src/pages/api/webhook.ts
399
- import { Webhooks } from "@dodopayments/astro";
400
-
401
- export const prerender = false;
402
-
403
- export const POST = Webhooks({
404
- webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY,
405
- onPayload: async (payload) => {
406
- console.log("Webhook:", payload.type);
407
- },
408
- });
409
- ```
205
+ - Checkout
206
+ - Webhooks
410
207
 
411
208
  ## Remix
412
209
 
413
- **Package:** `@dodopayments/remix`
210
+ Full guide: [references/remix.md](references/remix.md).
414
211
 
415
- ### Checkout
212
+ Covers:
416
213
 
417
- ```typescript
418
- // app/routes/api.checkout.tsx
419
- import { Checkout } from "@dodopayments/remix";
420
- import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
421
- import { dodoEnvironment } from "~/lib/dodo-env";
422
-
423
- const checkoutHandler = Checkout({
424
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
425
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
426
- environment: dodoEnvironment,
427
- type: "session",
428
- });
429
-
430
- export const loader = ({ request }: LoaderFunctionArgs) => checkoutHandler(request);
431
- export const action = ({ request }: ActionFunctionArgs) => checkoutHandler(request);
432
- ```
433
-
434
- ### Customer Portal
435
-
436
- ```typescript
437
- // app/routes/api.customer-portal.tsx
438
- import { CustomerPortal } from "@dodopayments/remix";
439
- import type { LoaderFunctionArgs } from "@remix-run/node";
440
- import { dodoEnvironment } from "~/lib/dodo-env";
441
- // your auth helper
442
- import { getSessionUser } from "~/lib/auth.server";
443
-
444
- const portalHandler = CustomerPortal({
445
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
446
- environment: dodoEnvironment,
447
- });
448
-
449
- export const loader = async ({ request }: LoaderFunctionArgs) => {
450
- const user = await getSessionUser(request);
451
- if (!user?.dodoCustomerId) return new Response("Unauthorized", { status: 401 });
452
-
453
- // Overwrite any client-supplied ?customer_id= with the signed-in user's ID.
454
- const url = new URL(request.url);
455
- url.searchParams.set("customer_id", user.dodoCustomerId);
456
- return portalHandler(new Request(url, request));
457
- };
458
- ```
459
-
460
- ### Webhooks
461
-
462
- ```typescript
463
- // app/routes/api.webhook.tsx
464
- import { Webhooks } from "@dodopayments/remix";
465
- import type { ActionFunctionArgs } from "@remix-run/node";
466
-
467
- export const action = ({ request }: ActionFunctionArgs) =>
468
- Webhooks({
469
- webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
470
- onPayload: async (payload) => {
471
- console.log("Webhook:", payload.type);
472
- },
473
- })(request);
474
- ```
214
+ - Checkout
215
+ - Customer Portal
216
+ - Webhooks
475
217
 
476
218
  ## SvelteKit
477
219
 
478
- **Package:** `@dodopayments/sveltekit`
479
- **Route placement:** `src/routes/api/checkout/+server.ts`, `src/routes/api/customer-portal/+server.ts`, `src/routes/api/webhook/+server.ts`
480
-
481
- ### Checkout
482
-
483
- ```typescript
484
- // src/routes/api/checkout/+server.ts
485
- import { Checkout } from "@dodopayments/sveltekit";
486
- import { DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_RETURN_URL, DODO_PAYMENTS_ENVIRONMENT } from "$env/static/private";
220
+ Full guide: [references/sveltekit.md](references/sveltekit.md).
487
221
 
488
- const dodoEnvironment = DODO_PAYMENTS_ENVIRONMENT === "live_mode" ? "live_mode" : "test_mode";
222
+ Covers:
489
223
 
490
- // Checkout() returns { GET, POST }, not a handler. Export GET from a "static"
491
- // handler - the GET of a "session"/"dynamic" handler returns 400.
492
- const staticCheckout = Checkout({
493
- bearerToken: DODO_PAYMENTS_API_KEY,
494
- returnUrl: DODO_PAYMENTS_RETURN_URL,
495
- environment: dodoEnvironment,
496
- type: "static",
497
- });
498
-
499
- const sessionCheckout = Checkout({
500
- bearerToken: DODO_PAYMENTS_API_KEY,
501
- returnUrl: DODO_PAYMENTS_RETURN_URL,
502
- environment: dodoEnvironment,
503
- type: "session",
504
- });
505
-
506
- export const GET = staticCheckout.GET;
507
- export const POST = sessionCheckout.POST;
508
- ```
509
-
510
- ### Webhooks
511
-
512
- ```typescript
513
- // src/routes/api/webhook/+server.ts
514
- import { Webhooks } from "@dodopayments/sveltekit";
515
- import { DODO_PAYMENTS_WEBHOOK_KEY } from "$env/static/private";
516
-
517
- export const POST = Webhooks({
518
- webhookKey: DODO_PAYMENTS_WEBHOOK_KEY,
519
- onPayload: async (payload) => {
520
- console.log("Webhook:", payload.type);
521
- },
522
- });
523
- ```
224
+ - Checkout
225
+ - Webhooks
524
226
 
525
227
  ## Nuxt
526
228
 
527
- **Package:** `@dodopayments/nuxt`
528
-
529
- Add the module to `nuxt.config.ts` and configure runtime variables:
530
-
531
- ```typescript
532
- // nuxt.config.ts
533
- export default defineNuxtConfig({
534
- modules: ["@dodopayments/nuxt"],
535
- runtimeConfig: {
536
- private: {
537
- bearerToken: process.env.NUXT_PRIVATE_BEARER_TOKEN,
538
- webhookKey: process.env.NUXT_PRIVATE_WEBHOOK_KEY,
539
- environment: process.env.NUXT_PRIVATE_ENVIRONMENT,
540
- returnUrl: process.env.NUXT_PRIVATE_RETURN_URL,
541
- },
542
- },
543
- });
544
- ```
545
-
546
- ### Checkout
547
-
548
- 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.
549
-
550
- ```typescript
551
- // server/routes/api/checkout.ts
552
- // checkoutHandler and useRuntimeConfig are auto-imported by the module.
553
- const config = useRuntimeConfig();
554
-
555
- export default checkoutHandler({
556
- bearerToken: config.private.bearerToken,
557
- returnUrl: config.private.returnUrl,
558
- environment: config.private.environment,
559
- type: "session",
560
- });
561
- ```
562
-
563
- ### Webhooks
229
+ Full guide: [references/nuxt.md](references/nuxt.md).
564
230
 
565
- ```typescript
566
- // server/routes/api/webhook.ts
567
- // Webhooks and useRuntimeConfig are auto-imported by the module.
568
- const config = useRuntimeConfig();
231
+ Covers:
569
232
 
570
- export default Webhooks({
571
- webhookKey: config.private.webhookKey,
572
- onPayload: async (payload) => {
573
- console.log("Webhook:", payload.type);
574
- },
575
- });
576
- ```
233
+ - Checkout
234
+ - Webhooks
577
235
 
578
236
  ## TanStack Start
579
237
 
580
- **Package:** `@dodopayments/tanstack`
581
-
582
- ### Checkout
583
-
584
- `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)`.
585
-
586
- ```typescript
587
- // src/routes/api/checkout.ts
588
- import { Checkout } from "@dodopayments/tanstack";
589
- // src/lib/dodo-env.ts
590
- import { dodoEnvironment } from "../../lib/dodo-env";
591
-
592
- export const GET = Checkout({
593
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
594
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
595
- environment: dodoEnvironment,
596
- type: "static",
597
- });
238
+ Full guide: [references/tanstack-start.md](references/tanstack-start.md).
598
239
 
599
- export const POST = Checkout({
600
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
601
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
602
- environment: dodoEnvironment,
603
- type: "session",
604
- });
605
- ```
240
+ Covers:
606
241
 
607
- 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
608
243
 
609
244
  ## Bun
610
245
 
611
- **Package:** `@dodopayments/bun`
612
-
613
- ### Checkout and Portal
246
+ Full guide: [references/bun.md](references/bun.md).
614
247
 
615
- ```typescript
616
- import { Checkout, CustomerPortal } from "@dodopayments/bun";
617
- import { dodoEnvironment } from "./lib/dodo-env";
618
- // your auth helper
619
- import { getSessionUser } from "./lib/auth";
248
+ Covers:
620
249
 
621
- const checkoutHandler = Checkout({
622
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
623
- returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
624
- environment: dodoEnvironment,
625
- type: "session",
626
- });
627
-
628
- const portalHandler = CustomerPortal({
629
- bearerToken: process.env.DODO_PAYMENTS_API_KEY,
630
- environment: dodoEnvironment,
631
- });
632
-
633
- Bun.serve({
634
- port: 3000,
635
- async fetch(request) {
636
- const url = new URL(request.url);
637
-
638
- if (url.pathname === "/api/checkout") {
639
- return checkoutHandler(request);
640
- }
641
- if (url.pathname === "/api/customer-portal" && request.method === "GET") {
642
- const user = await getSessionUser(request);
643
- if (!user?.dodoCustomerId) return new Response("Unauthorized", { status: 401 });
644
- // Overwrite any client-supplied ?customer_id= with the signed-in user's ID.
645
- url.searchParams.set("customer_id", user.dodoCustomerId);
646
- return portalHandler(new Request(url, request));
647
- }
648
-
649
- return new Response("Not Found", { status: 404 });
650
- },
651
- });
652
- ```
250
+ - Checkout and Portal
653
251
 
654
252
  ## Convex
655
253
 
656
- **Package:** `@dodopayments/convex`
657
-
658
- Convex uses a component-based architecture. Register the component in `convex.config.ts`:
659
-
660
- ```typescript
661
- // convex/convex.config.ts
662
- import { defineApp } from "convex/server";
663
- import dodopayments from "@dodopayments/convex/convex.config";
664
-
665
- const app = defineApp();
666
- app.use(dodopayments);
667
- export default app;
668
- ```
669
-
670
- Run `npx convex dev` once to generate `components.dodopayments`. `identify` needs an app-owned lookup from the signed-in user to their Dodo customer ID. This assumes a `customers` table with `authId` and `dodoCustomerId` fields and a `by_auth_id` index; adapt it to your schema:
671
-
672
- ```typescript
673
- // convex/customers.ts
674
- import { internalQuery } from "./_generated/server";
675
- import { v } from "convex/values";
676
-
677
- export const getByAuthId = internalQuery({
678
- args: { authId: v.string() },
679
- handler: async (ctx, { authId }) =>
680
- ctx.db
681
- .query("customers")
682
- .withIndex("by_auth_id", (q) => q.eq("authId", authId))
683
- .unique(),
684
- });
685
- ```
686
-
687
- Then create a `DodoPayments` client in a local module and export its API methods:
688
-
689
- ```typescript
690
- // convex/dodo.ts
691
- import { DodoPayments } from "@dodopayments/convex";
692
- import { components, internal } from "./_generated/api";
693
-
694
- const apiKey = process.env.DODO_PAYMENTS_API_KEY;
695
- if (!apiKey) throw new Error("DODO_PAYMENTS_API_KEY is not set");
696
-
697
- export const dodo = new DodoPayments(components.dodopayments, {
698
- // Maps the signed-in Convex user to a Dodo customer; used by customerPortal.
699
- identify: async (ctx) => {
700
- const identity = await ctx.auth.getUserIdentity();
701
- if (!identity) return null;
702
- const customer = await ctx.runQuery(internal.customers.getByAuthId, {
703
- authId: identity.subject,
704
- });
705
- return customer ? { dodoCustomerId: customer.dodoCustomerId } : null;
706
- },
707
- apiKey,
708
- environment: process.env.DODO_PAYMENTS_ENVIRONMENT === "live_mode" ? "live_mode" : "test_mode",
709
- });
710
-
711
- export const { checkout, customerPortal } = dodo.api();
712
- ```
713
-
714
- Call `checkout` from an **action** (mutations cannot make network calls), passing the checkout session fields in `payload`. The client sends a plan slug; the server maps it to a product ID and owns the return URL, so a caller cannot pick an arbitrary product or redirect target:
715
-
716
- ```typescript
717
- // convex/payments.ts
718
- import { action } from "./_generated/server";
719
- import { v } from "convex/values";
720
- import { checkout } from "./dodo";
721
-
722
- // Public plan slug -> Dodo product ID. Never accept a product_id from the client.
723
- const PRODUCT_IDS: Readonly<Record<string, string>> = {
724
- starter: "pdt_starter_monthly",
725
- pro: "pdt_pro_monthly",
726
- };
727
-
728
- export const createCheckout = action({
729
- args: { plan: v.string() },
730
- handler: async (ctx, { plan }) => {
731
- if (!Object.hasOwn(PRODUCT_IDS, plan)) throw new Error(`Unknown plan: ${plan}`);
732
- // Returns { checkout_url }
733
- return await checkout(ctx, {
734
- payload: {
735
- product_cart: [{ product_id: PRODUCT_IDS[plan], quantity: 1 }],
736
- return_url: process.env.DODO_PAYMENTS_RETURN_URL,
737
- },
738
- });
739
- },
740
- });
741
- ```
742
-
743
- Convex only supports session checkout, not static or dynamic modes.
254
+ Full guide: [references/convex.md](references/convex.md).
744
255
 
745
256
  ## Common mistakes
746
257
 
@@ -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
+ ```