@dodopayments/opencode-plugin 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,599 @@
1
+ ---
2
+ name: checkout-integration
3
+ description: Guide for creating checkout sessions and payment flows with Dodo Payments - one-time, subscriptions, and overlay checkout.
4
+ ---
5
+
6
+ # Dodo Payments Checkout Integration
7
+
8
+ **Reference: [docs.dodopayments.com/developer-resources/integration-guide](https://docs.dodopayments.com/developer-resources/integration-guide)**
9
+
10
+ Create seamless payment experiences with hosted checkout pages or overlay checkout modals.
11
+
12
+ ---
13
+
14
+ ## Checkout Methods
15
+
16
+ | Method | Best For | Integration |
17
+ |--------|----------|-------------|
18
+ | **Hosted Checkout** | Simple integration, full-page redirect | Server-side SDK |
19
+ | **Overlay Checkout** | Seamless UX, stays on your site | JavaScript SDK |
20
+ | **Payment Links** | No-code, shareable links | Dashboard |
21
+
22
+ ---
23
+
24
+ ## Hosted Checkout
25
+
26
+ ### Basic Implementation
27
+
28
+ ```typescript
29
+ import DodoPayments from 'dodopayments';
30
+
31
+ const client = new DodoPayments({
32
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
33
+ });
34
+
35
+ // Create checkout session
36
+ const session = await client.checkoutSessions.create({
37
+ product_cart: [
38
+ { product_id: 'prod_xxxxx', quantity: 1 }
39
+ ],
40
+ customer: {
41
+ email: 'customer@example.com',
42
+ name: 'John Doe',
43
+ },
44
+ return_url: 'https://yoursite.com/checkout/success',
45
+ });
46
+
47
+ // Redirect customer to checkout
48
+ // session.checkout_url
49
+ ```
50
+
51
+ ### With Multiple Products
52
+
53
+ ```typescript
54
+ const session = await client.checkoutSessions.create({
55
+ product_cart: [
56
+ { product_id: 'prod_item_1', quantity: 2 },
57
+ { product_id: 'prod_item_2', quantity: 1 },
58
+ ],
59
+ customer: {
60
+ email: 'customer@example.com',
61
+ },
62
+ return_url: 'https://yoursite.com/success',
63
+ });
64
+ ```
65
+
66
+ ### With Customer ID (Existing Customer)
67
+
68
+ ```typescript
69
+ const session = await client.checkoutSessions.create({
70
+ product_cart: [
71
+ { product_id: 'prod_xxxxx', quantity: 1 }
72
+ ],
73
+ customer_id: 'cust_existing_customer',
74
+ return_url: 'https://yoursite.com/success',
75
+ });
76
+ ```
77
+
78
+ ### With Metadata
79
+
80
+ ```typescript
81
+ const session = await client.checkoutSessions.create({
82
+ product_cart: [
83
+ { product_id: 'prod_xxxxx', quantity: 1 }
84
+ ],
85
+ customer: {
86
+ email: 'customer@example.com',
87
+ },
88
+ metadata: {
89
+ order_id: 'order_12345',
90
+ referral_code: 'FRIEND20',
91
+ user_id: 'internal_user_id',
92
+ },
93
+ return_url: 'https://yoursite.com/success',
94
+ });
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Next.js Implementation
100
+
101
+ ### API Route
102
+
103
+ ```typescript
104
+ // app/api/checkout/route.ts
105
+ import { NextRequest, NextResponse } from 'next/server';
106
+ import DodoPayments from 'dodopayments';
107
+
108
+ const client = new DodoPayments({
109
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
110
+ });
111
+
112
+ export async function POST(req: NextRequest) {
113
+ try {
114
+ const { productId, quantity = 1, email, name, metadata } = await req.json();
115
+
116
+ if (!productId || !email) {
117
+ return NextResponse.json(
118
+ { error: 'Missing required fields' },
119
+ { status: 400 }
120
+ );
121
+ }
122
+
123
+ const session = await client.checkoutSessions.create({
124
+ product_cart: [{ product_id: productId, quantity }],
125
+ customer: { email, name },
126
+ metadata,
127
+ return_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success`,
128
+ });
129
+
130
+ return NextResponse.json({
131
+ checkoutUrl: session.checkout_url,
132
+ sessionId: session.checkout_session_id,
133
+ });
134
+ } catch (error: any) {
135
+ console.error('Checkout error:', error);
136
+ return NextResponse.json(
137
+ { error: error.message || 'Failed to create checkout' },
138
+ { status: 500 }
139
+ );
140
+ }
141
+ }
142
+ ```
143
+
144
+ ### Client Component
145
+
146
+ ```typescript
147
+ // components/CheckoutButton.tsx
148
+ 'use client';
149
+
150
+ import { useState } from 'react';
151
+
152
+ interface CheckoutButtonProps {
153
+ productId: string;
154
+ email: string;
155
+ name?: string;
156
+ children: React.ReactNode;
157
+ }
158
+
159
+ export function CheckoutButton({ productId, email, name, children }: CheckoutButtonProps) {
160
+ const [loading, setLoading] = useState(false);
161
+
162
+ const handleCheckout = async () => {
163
+ setLoading(true);
164
+
165
+ try {
166
+ const response = await fetch('/api/checkout', {
167
+ method: 'POST',
168
+ headers: { 'Content-Type': 'application/json' },
169
+ body: JSON.stringify({ productId, email, name }),
170
+ });
171
+
172
+ const data = await response.json();
173
+
174
+ if (data.checkoutUrl) {
175
+ window.location.href = data.checkoutUrl;
176
+ } else {
177
+ throw new Error(data.error || 'Failed to create checkout');
178
+ }
179
+ } catch (error) {
180
+ console.error('Checkout error:', error);
181
+ alert('Failed to start checkout. Please try again.');
182
+ } finally {
183
+ setLoading(false);
184
+ }
185
+ };
186
+
187
+ return (
188
+ <button onClick={handleCheckout} disabled={loading}>
189
+ {loading ? 'Loading...' : children}
190
+ </button>
191
+ );
192
+ }
193
+ ```
194
+
195
+ ### Success Page
196
+
197
+ ```typescript
198
+ // app/checkout/success/page.tsx
199
+ import { Suspense } from 'react';
200
+
201
+ function SuccessContent() {
202
+ return (
203
+ <div className="text-center py-20">
204
+ <h1 className="text-3xl font-bold">Payment Successful!</h1>
205
+ <p className="mt-4 text-gray-600">
206
+ Thank you for your purchase. You will receive a confirmation email shortly.
207
+ </p>
208
+ <a href="/" className="mt-8 inline-block text-blue-600 hover:underline">
209
+ Return to Home
210
+ </a>
211
+ </div>
212
+ );
213
+ }
214
+
215
+ export default function SuccessPage() {
216
+ return (
217
+ <Suspense fallback={<div>Loading...</div>}>
218
+ <SuccessContent />
219
+ </Suspense>
220
+ );
221
+ }
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Overlay Checkout
227
+
228
+ Embed checkout directly on your page without redirects.
229
+
230
+ ### Installation
231
+
232
+ ```bash
233
+ npm install @dodopayments/checkout
234
+ ```
235
+
236
+ ### Basic Usage
237
+
238
+ ```typescript
239
+ import { DodoCheckout } from '@dodopayments/checkout';
240
+
241
+ // Initialize
242
+ const checkout = new DodoCheckout({
243
+ apiKey: 'your_publishable_key',
244
+ environment: 'live', // or 'test'
245
+ });
246
+
247
+ // Open overlay
248
+ checkout.open({
249
+ productId: 'prod_xxxxx',
250
+ customer: {
251
+ email: 'customer@example.com',
252
+ },
253
+ onSuccess: (result) => {
254
+ console.log('Payment successful:', result);
255
+ // Handle success
256
+ },
257
+ onClose: () => {
258
+ console.log('Checkout closed');
259
+ },
260
+ });
261
+ ```
262
+
263
+ ### React Component
264
+
265
+ ```typescript
266
+ // components/OverlayCheckout.tsx
267
+ 'use client';
268
+
269
+ import { useEffect, useRef } from 'react';
270
+ import { DodoCheckout } from '@dodopayments/checkout';
271
+
272
+ interface OverlayCheckoutProps {
273
+ productId: string;
274
+ email: string;
275
+ onSuccess?: (result: any) => void;
276
+ children: React.ReactNode;
277
+ }
278
+
279
+ export function OverlayCheckout({
280
+ productId,
281
+ email,
282
+ onSuccess,
283
+ children
284
+ }: OverlayCheckoutProps) {
285
+ const checkoutRef = useRef<DodoCheckout | null>(null);
286
+
287
+ useEffect(() => {
288
+ checkoutRef.current = new DodoCheckout({
289
+ apiKey: process.env.NEXT_PUBLIC_DODO_PUBLISHABLE_KEY!,
290
+ environment: process.env.NODE_ENV === 'production' ? 'live' : 'test',
291
+ });
292
+
293
+ return () => {
294
+ checkoutRef.current?.close();
295
+ };
296
+ }, []);
297
+
298
+ const handleClick = () => {
299
+ checkoutRef.current?.open({
300
+ productId,
301
+ customer: { email },
302
+ onSuccess: (result) => {
303
+ onSuccess?.(result);
304
+ // Optionally redirect
305
+ window.location.href = '/checkout/success';
306
+ },
307
+ onClose: () => {
308
+ console.log('Checkout closed');
309
+ },
310
+ });
311
+ };
312
+
313
+ return (
314
+ <button onClick={handleClick}>
315
+ {children}
316
+ </button>
317
+ );
318
+ }
319
+ ```
320
+
321
+ ### Customization
322
+
323
+ ```typescript
324
+ checkout.open({
325
+ productId: 'prod_xxxxx',
326
+ customer: { email: 'customer@example.com' },
327
+ theme: {
328
+ primaryColor: '#0066FF',
329
+ backgroundColor: '#FFFFFF',
330
+ fontFamily: 'Inter, sans-serif',
331
+ },
332
+ locale: 'en',
333
+ });
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Express.js Implementation
339
+
340
+ ```typescript
341
+ import express from 'express';
342
+ import DodoPayments from 'dodopayments';
343
+
344
+ const app = express();
345
+ app.use(express.json());
346
+
347
+ const client = new DodoPayments({
348
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
349
+ });
350
+
351
+ app.post('/api/create-checkout', async (req, res) => {
352
+ try {
353
+ const { productId, email, name, quantity = 1 } = req.body;
354
+
355
+ const session = await client.checkoutSessions.create({
356
+ product_cart: [{ product_id: productId, quantity }],
357
+ customer: { email, name },
358
+ return_url: `${process.env.APP_URL}/success`,
359
+ });
360
+
361
+ res.json({ checkoutUrl: session.checkout_url });
362
+ } catch (error: any) {
363
+ res.status(500).json({ error: error.message });
364
+ }
365
+ });
366
+
367
+ // Success page route
368
+ app.get('/success', (req, res) => {
369
+ res.send('Payment successful!');
370
+ });
371
+ ```
372
+
373
+ ---
374
+
375
+ ## Python Implementation
376
+
377
+ ### FastAPI
378
+
379
+ ```python
380
+ from fastapi import FastAPI, HTTPException
381
+ from pydantic import BaseModel
382
+ from dodopayments import DodoPayments
383
+ import os
384
+
385
+ app = FastAPI()
386
+ client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])
387
+
388
+ class CheckoutRequest(BaseModel):
389
+ product_id: str
390
+ email: str
391
+ name: str = None
392
+ quantity: int = 1
393
+
394
+ @app.post("/api/checkout")
395
+ async def create_checkout(request: CheckoutRequest):
396
+ try:
397
+ session = client.checkout_sessions.create(
398
+ product_cart=[{
399
+ "product_id": request.product_id,
400
+ "quantity": request.quantity
401
+ }],
402
+ customer={
403
+ "email": request.email,
404
+ "name": request.name
405
+ },
406
+ return_url=f"{os.environ['APP_URL']}/success"
407
+ )
408
+
409
+ return {"checkout_url": session.checkout_url}
410
+ except Exception as e:
411
+ raise HTTPException(status_code=500, detail=str(e))
412
+ ```
413
+
414
+ ### Flask
415
+
416
+ ```python
417
+ from flask import Flask, request, jsonify
418
+ from dodopayments import DodoPayments
419
+ import os
420
+
421
+ app = Flask(__name__)
422
+ client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])
423
+
424
+ @app.route('/api/checkout', methods=['POST'])
425
+ def create_checkout():
426
+ data = request.json
427
+
428
+ session = client.checkout_sessions.create(
429
+ product_cart=[{
430
+ "product_id": data['product_id'],
431
+ "quantity": data.get('quantity', 1)
432
+ }],
433
+ customer={
434
+ "email": data['email'],
435
+ "name": data.get('name')
436
+ },
437
+ return_url=f"{os.environ['APP_URL']}/success"
438
+ )
439
+
440
+ return jsonify({"checkout_url": session.checkout_url})
441
+ ```
442
+
443
+ ---
444
+
445
+ ## Go Implementation
446
+
447
+ ```go
448
+ package main
449
+
450
+ import (
451
+ "encoding/json"
452
+ "net/http"
453
+ "os"
454
+
455
+ "github.com/dodopayments/dodopayments-go"
456
+ )
457
+
458
+ var client = dodopayments.NewClient(
459
+ option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
460
+ )
461
+
462
+ type CheckoutRequest struct {
463
+ ProductID string `json:"product_id"`
464
+ Email string `json:"email"`
465
+ Name string `json:"name"`
466
+ Quantity int `json:"quantity"`
467
+ }
468
+
469
+ func createCheckout(w http.ResponseWriter, r *http.Request) {
470
+ var req CheckoutRequest
471
+ json.NewDecoder(r.Body).Decode(&req)
472
+
473
+ if req.Quantity == 0 {
474
+ req.Quantity = 1
475
+ }
476
+
477
+ session, err := client.CheckoutSessions.Create(r.Context(), &dodopayments.CheckoutSessionCreateParams{
478
+ ProductCart: []dodopayments.CartItem{
479
+ {ProductID: req.ProductID, Quantity: req.Quantity},
480
+ },
481
+ Customer: &dodopayments.Customer{
482
+ Email: req.Email,
483
+ Name: req.Name,
484
+ },
485
+ ReturnURL: os.Getenv("APP_URL") + "/success",
486
+ })
487
+
488
+ if err != nil {
489
+ http.Error(w, err.Error(), http.StatusInternalServerError)
490
+ return
491
+ }
492
+
493
+ json.NewEncoder(w).Encode(map[string]string{
494
+ "checkout_url": session.CheckoutURL,
495
+ })
496
+ }
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Handling Success
502
+
503
+ ### Query Parameters
504
+ The return URL receives these query parameters:
505
+ - `status=success` - Payment completed
506
+ - `session_id` - Checkout session ID
507
+
508
+ ### Verify Payment Server-Side
509
+ Don't rely solely on the redirect. Always verify via webhook:
510
+
511
+ ```typescript
512
+ // Webhook handler confirms payment
513
+ app.post('/webhook', async (req, res) => {
514
+ const event = req.body;
515
+
516
+ if (event.type === 'payment.succeeded') {
517
+ // This is the source of truth
518
+ await fulfillOrder(event.data);
519
+ }
520
+
521
+ res.json({ received: true });
522
+ });
523
+ ```
524
+
525
+ ---
526
+
527
+ ## Advanced Options
528
+
529
+ ### Prefill Customer Info
530
+ ```typescript
531
+ const session = await client.checkoutSessions.create({
532
+ product_cart: [{ product_id: 'prod_xxxxx', quantity: 1 }],
533
+ customer: {
534
+ email: 'customer@example.com',
535
+ name: 'John Doe',
536
+ phone: '+1234567890',
537
+ address: {
538
+ line1: '123 Main St',
539
+ city: 'San Francisco',
540
+ state: 'CA',
541
+ postal_code: '94105',
542
+ country: 'US',
543
+ },
544
+ },
545
+ return_url: 'https://yoursite.com/success',
546
+ });
547
+ ```
548
+
549
+ ### Custom Success/Cancel URLs
550
+ ```typescript
551
+ const session = await client.checkoutSessions.create({
552
+ product_cart: [{ product_id: 'prod_xxxxx', quantity: 1 }],
553
+ customer: { email: 'customer@example.com' },
554
+ return_url: 'https://yoursite.com/checkout/success?session_id={CHECKOUT_SESSION_ID}',
555
+ });
556
+ ```
557
+
558
+ ### Subscription with Trial
559
+ ```typescript
560
+ const session = await client.checkoutSessions.create({
561
+ product_cart: [{ product_id: 'prod_subscription', quantity: 1 }],
562
+ subscription_data: {
563
+ trial_period_days: 14,
564
+ },
565
+ customer: { email: 'customer@example.com' },
566
+ return_url: 'https://yoursite.com/success',
567
+ });
568
+ ```
569
+
570
+ ---
571
+
572
+ ## Error Handling
573
+
574
+ ```typescript
575
+ try {
576
+ const session = await client.checkoutSessions.create({...});
577
+ } catch (error: any) {
578
+ if (error.status === 400) {
579
+ // Invalid parameters
580
+ console.error('Invalid request:', error.message);
581
+ } else if (error.status === 401) {
582
+ // Invalid API key
583
+ console.error('Authentication failed');
584
+ } else if (error.status === 404) {
585
+ // Product not found
586
+ console.error('Product not found');
587
+ } else {
588
+ console.error('Checkout error:', error);
589
+ }
590
+ }
591
+ ```
592
+
593
+ ---
594
+
595
+ ## Resources
596
+
597
+ - [Integration Guide](https://docs.dodopayments.com/developer-resources/integration-guide)
598
+ - [Overlay Checkout](https://docs.dodopayments.com/developer-resources/overlay-checkout)
599
+ - [API Reference](https://docs.dodopayments.com/api-reference/checkout-sessions)