Dodo Payments Checkout Integration
Reference: docs.dodopayments.com/developer-resources/integration-guide
Create seamless payment experiences with hosted checkout pages or overlay checkout modals.
Checkout Methods
Method Best For Integration
Hosted Checkout Simple integration, full-page redirect Server-side SDK
Overlay Checkout Seamless UX, stays on your site JavaScript SDK
Payment Links No-code, shareable links Dashboard
Hosted Checkout
Basic Implementation
import DodoPayments from 'dodopayments';
const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, });
// Create checkout session const session = await client.checkoutSessions.create({ product_cart: [ { product_id: 'prod_xxxxx', quantity: 1 } ], customer: { email: 'customer@example.com', name: 'John Doe', }, return_url: 'https://yoursite.com/checkout/success', });
// Redirect customer to checkout // session.checkout_url
With Multiple Products
const session = await client.checkoutSessions.create({ product_cart: [ { product_id: 'prod_item_1', quantity: 2 }, { product_id: 'prod_item_2', quantity: 1 }, ], customer: { email: 'customer@example.com', }, return_url: 'https://yoursite.com/success', });
With Customer ID (Existing Customer)
const session = await client.checkoutSessions.create({ product_cart: [ { product_id: 'prod_xxxxx', quantity: 1 } ], customer_id: 'cust_existing_customer', return_url: 'https://yoursite.com/success', });
With Metadata
const session = await client.checkoutSessions.create({ product_cart: [ { product_id: 'prod_xxxxx', quantity: 1 } ], customer: { email: 'customer@example.com', }, metadata: { order_id: 'order_12345', referral_code: 'FRIEND20', user_id: 'internal_user_id', }, return_url: 'https://yoursite.com/success', });
Next.js Implementation
API Route
// app/api/checkout/route.ts import { NextRequest, NextResponse } from 'next/server'; import DodoPayments from 'dodopayments';
const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY!, });
export async function POST(req: NextRequest) { try { const { productId, quantity = 1, email, name, metadata } = await req.json();
if (!productId || !email) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
const session = await client.checkoutSessions.create({
product_cart: [{ product_id: productId, quantity }],
customer: { email, name },
metadata,
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success`,
});
return NextResponse.json({
checkoutUrl: session.checkout_url,
sessionId: session.checkout_session_id,
});
} catch (error: any) { console.error('Checkout error:', error); return NextResponse.json( { error: error.message || 'Failed to create checkout' }, { status: 500 } ); } }
Client Component
// components/CheckoutButton.tsx 'use client';
import { useState } from 'react';
interface CheckoutButtonProps { productId: string; email: string; name?: string; children: React.ReactNode; }
export function CheckoutButton({ productId, email, name, children }: CheckoutButtonProps) { const [loading, setLoading] = useState(false);
const handleCheckout = async () => { setLoading(true);
try {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ productId, email, name }),
});
const data = await response.json();
if (data.checkoutUrl) {
window.location.href = data.checkoutUrl;
} else {
throw new Error(data.error || 'Failed to create checkout');
}
} catch (error) {
console.error('Checkout error:', error);
alert('Failed to start checkout. Please try again.');
} finally {
setLoading(false);
}
};
return ( <button onClick={handleCheckout} disabled={loading}> {loading ? 'Loading...' : children} </button> ); }
Success Page
// app/checkout/success/page.tsx import { Suspense } from 'react';
function SuccessContent() { return ( <div className="text-center py-20"> <h1 className="text-3xl font-bold">Payment Successful!</h1> <p className="mt-4 text-gray-600"> Thank you for your purchase. You will receive a confirmation email shortly. </p> <a href="/" className="mt-8 inline-block text-blue-600 hover:underline"> Return to Home </a> </div> ); }
export default function SuccessPage() { return ( <Suspense fallback={<div>Loading...</div>}> <SuccessContent /> </Suspense> ); }
Overlay Checkout
Embed checkout directly on your page without redirects.
Installation
npm install @dodopayments/checkout
Basic Usage
import { DodoCheckout } from '@dodopayments/checkout';
// Initialize const checkout = new DodoCheckout({ apiKey: 'your_publishable_key', environment: 'live', // or 'test' });
// Open overlay checkout.open({ productId: 'prod_xxxxx', customer: { email: 'customer@example.com', }, onSuccess: (result) => { console.log('Payment successful:', result); // Handle success }, onClose: () => { console.log('Checkout closed'); }, });
React Component
// components/OverlayCheckout.tsx 'use client';
import { useEffect, useRef } from 'react'; import { DodoCheckout } from '@dodopayments/checkout';
interface OverlayCheckoutProps { productId: string; email: string; onSuccess?: (result: any) => void; children: React.ReactNode; }
export function OverlayCheckout({ productId, email, onSuccess, children }: OverlayCheckoutProps) { const checkoutRef = useRef<DodoCheckout | null>(null);
useEffect(() => { checkoutRef.current = new DodoCheckout({ apiKey: process.env.NEXT_PUBLIC_DODO_PUBLISHABLE_KEY!, environment: process.env.NODE_ENV === 'production' ? 'live' : 'test', });
return () => {
checkoutRef.current?.close();
};
}, []);
const handleClick = () => { checkoutRef.current?.open({ productId, customer: { email }, onSuccess: (result) => { onSuccess?.(result); // Optionally redirect window.location.href = '/checkout/success'; }, onClose: () => { console.log('Checkout closed'); }, }); };
return ( <button onClick={handleClick}> {children} </button> ); }
Customization
checkout.open({ productId: 'prod_xxxxx', customer: { email: 'customer@example.com' }, theme: { primaryColor: '#0066FF', backgroundColor: '#FFFFFF', fontFamily: 'Inter, sans-serif', }, locale: 'en', });
Express.js Implementation
import express from 'express'; import DodoPayments from 'dodopayments';
const app = express(); app.use(express.json());
const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY!, });
app.post('/api/create-checkout', async (req, res) => { try { const { productId, email, name, quantity = 1 } = req.body;
const session = await client.checkoutSessions.create({
product_cart: [{ product_id: productId, quantity }],
customer: { email, name },
return_url: `${process.env.APP_URL}/success`,
});
res.json({ checkoutUrl: session.checkout_url });
} catch (error: any) { res.status(500).json({ error: error.message }); } });
// Success page route app.get('/success', (req, res) => { res.send('Payment successful!'); });
Python Implementation
FastAPI
from fastapi import FastAPI, HTTPException from pydantic import BaseModel from dodopayments import DodoPayments import os
app = FastAPI() client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])
class CheckoutRequest(BaseModel): product_id: str email: str name: str = None quantity: int = 1
@app.post("/api/checkout") async def create_checkout(request: CheckoutRequest): try: session = client.checkout_sessions.create( product_cart=[{ "product_id": request.product_id, "quantity": request.quantity }], customer={ "email": request.email, "name": request.name }, return_url=f"{os.environ['APP_URL']}/success" )
return {"checkout_url": session.checkout_url}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Flask
from flask import Flask, request, jsonify from dodopayments import DodoPayments import os
app = Flask(name) client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])
@app.route('/api/checkout', methods=['POST']) def create_checkout(): data = request.json
session = client.checkout_sessions.create(
product_cart=[{
"product_id": data['product_id'],
"quantity": data.get('quantity', 1)
}],
customer={
"email": data['email'],
"name": data.get('name')
},
return_url=f"{os.environ['APP_URL']}/success"
)
return jsonify({"checkout_url": session.checkout_url})
Go Implementation
package main
import ( "encoding/json" "net/http" "os"
"github.com/dodopayments/dodopayments-go"
)
var client = dodopayments.NewClient( option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")), )
type CheckoutRequest struct {
ProductID string json:"product_id"
Email string json:"email"
Name string json:"name"
Quantity int json:"quantity"
}
func createCheckout(w http.ResponseWriter, r *http.Request) { var req CheckoutRequest json.NewDecoder(r.Body).Decode(&req)
if req.Quantity == 0 {
req.Quantity = 1
}
session, err := client.CheckoutSessions.Create(r.Context(), &dodopayments.CheckoutSessionCreateParams{
ProductCart: []dodopayments.CartItem{
{ProductID: req.ProductID, Quantity: req.Quantity},
},
Customer: &dodopayments.Customer{
Email: req.Email,
Name: req.Name,
},
ReturnURL: os.Getenv("APP_URL") + "/success",
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{
"checkout_url": session.CheckoutURL,
})
}
Handling Success
Query Parameters
The return URL receives these query parameters:
-
status=success
-
Payment completed
-
session_id
-
Checkout session ID
Verify Payment Server-Side
Don't rely solely on the redirect. Always verify via webhook:
// Webhook handler confirms payment app.post('/webhook', async (req, res) => { const event = req.body;
if (event.type === 'payment.succeeded') { // This is the source of truth await fulfillOrder(event.data); }
res.json({ received: true }); });
Advanced Options
Prefill Customer Info
const session = await client.checkoutSessions.create({ product_cart: [{ product_id: 'prod_xxxxx', quantity: 1 }], customer: { email: 'customer@example.com', name: 'John Doe', phone: '+1234567890', address: { line1: '123 Main St', city: 'San Francisco', state: 'CA', postal_code: '94105', country: 'US', }, }, return_url: 'https://yoursite.com/success', });
Custom Success/Cancel URLs
const session = await client.checkoutSessions.create({ product_cart: [{ product_id: 'prod_xxxxx', quantity: 1 }], customer: { email: 'customer@example.com' }, return_url: 'https://yoursite.com/checkout/success?session_id={CHECKOUT_SESSION_ID}', });
Subscription with Trial
const session = await client.checkoutSessions.create({ product_cart: [{ product_id: 'prod_subscription', quantity: 1 }], subscription_data: { trial_period_days: 14, }, customer: { email: 'customer@example.com' }, return_url: 'https://yoursite.com/success', });
Error Handling
try { const session = await client.checkoutSessions.create({...}); } catch (error: any) { if (error.status === 400) { // Invalid parameters console.error('Invalid request:', error.message); } else if (error.status === 401) { // Invalid API key console.error('Authentication failed'); } else if (error.status === 404) { // Product not found console.error('Product not found'); } else { console.error('Checkout error:', error); } }
Resources
-
Integration Guide
-
Overlay Checkout
-
API Reference