@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.
- package/package.json +1 -1
- package/skills/billing-sdk/SKILL.md +8 -193
- package/skills/billing-sdk/references/pricing-page.md +197 -0
- package/skills/checkout-integration/SKILL.md +14 -449
- 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 +5 -151
- 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/framework-adapters/SKILL.md +38 -527
- 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 +8 -303
- package/skills/license-keys/references/cli-tool.md +143 -0
- package/skills/license-keys/references/desktop-app.md +170 -0
- package/skills/mobile-checkout/SKILL.md +25 -313
- 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/refunds-and-disputes/SKILL.md +1 -1
- package/skills/subscription-integration/SKILL.md +3 -77
- package/skills/subscription-integration/references/webhook-events.md +82 -0
- package/skills/usage-based-billing/SKILL.md +5 -114
- package/skills/usage-based-billing/references/instrumentation.md +119 -0
- package/skills/webhook-integration/SKILL.md +28 -277
- 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
|
@@ -169,578 +169,89 @@ export const POST = Webhooks({
|
|
|
169
169
|
|
|
170
170
|
## Express
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
Full guide: [references/express.md](references/express.md).
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
Covers:
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
283
|
-
"application/json",
|
|
284
|
-
{ parseAs: "string" },
|
|
285
|
-
(req, body, done) => done(null, body)
|
|
286
|
-
);
|
|
184
|
+
Covers:
|
|
287
185
|
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
191
|
+
Full guide: [references/hono.md](references/hono.md).
|
|
299
192
|
|
|
300
|
-
|
|
193
|
+
Covers:
|
|
301
194
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
369
|
-
// src/pages/api/checkout.ts
|
|
370
|
-
import { Checkout } from "@dodopayments/astro";
|
|
371
|
-
|
|
372
|
-
export const prerender = false;
|
|
203
|
+
Covers:
|
|
373
204
|
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
210
|
+
Full guide: [references/remix.md](references/remix.md).
|
|
414
211
|
|
|
415
|
-
|
|
212
|
+
Covers:
|
|
416
213
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
222
|
+
Covers:
|
|
489
223
|
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
571
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
+
- Checkout
|
|
608
243
|
|
|
609
244
|
## Bun
|
|
610
245
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
### Checkout and Portal
|
|
246
|
+
Full guide: [references/bun.md](references/bun.md).
|
|
614
247
|
|
|
615
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
```
|