Guide for creating checkout sessions and payment flows with Dodo Payments - one-time, subscriptions, and overlay checkout.
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 completedsession_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
You Might Also Like
Related Skills

gog
Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
openclaw
orpc-contract-first
Guide for implementing oRPC contract-first API patterns in Dify frontend. Triggers when creating new API contracts, adding service endpoints, integrating TanStack Query with typed contracts, or migrating legacy service calls to oRPC. Use for all API layer work in web/contract and web/service directories.
langgenius

