# Astro Adaptor Source: https://docs.dodopayments.com/developer-resources/astro-adaptor Learn how to integrate Dodo Payments with your Astro App Router project using our Astro Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup. Integrate Dodo Payments checkout into your Astro app. Allow customers to manage subscriptions and details. Receive and process Dodo Payments webhook events. ## Installation Run the following command in your project root: ```bash theme={null} npm install @dodopayments/astro ``` Create a .env file in your project root: ```env expandable theme={null} DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode" DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success ``` Never commit your .env file or secrets to version control. ## Route Handler Examples All examples assume you are using the Astro App Router. Use this handler to integrate Dodo Payments checkout into your Astro app. Supports static (GET), dynamic (POST), and session (POST) payment flows. ```typescript Astro Route Handler expandable theme={null} // src/pages/api/checkout.ts import { Checkout } from "@dodopayments/astro"; export const prerender = false; export const GET = Checkout({ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY, returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL, environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT, type: "static", // optional, defaults to 'static' }); export const POST = Checkout({ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY, returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL, environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT, type: "dynamic", // for dynamic checkout }); export const POST = Checkout({ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY, returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL, environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT, type: "session", // for checkout sessions }); ``` ```curl Static Checkout Curl example expandable theme={null} curl --request GET \ --url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \ --header 'User-Agent: insomnia/11.2.0' \ --cookie mode=test ``` ```curl Dynamic Checkout Curl example expandable theme={null} curl --request POST \ --url https://example.com/api/checkout \ --header 'Content-Type: application/json' \ --header 'User-Agent: insomnia/11.2.0' \ --cookie mode=test \ --data '{ "billing": { "city": "Texas", "country": "US", "state": "Texas", "street": "56, hhh", "zipcode": "560000" }, "customer": { "email": "test@example.com", "name": "test" }, "metadata": {}, "payment_link": true, "product_id": "pdt_QMDuvLkbVzCRWRQjLNcs", "quantity": 1, "billing_currency": "USD", "discount_code": "IKHZ23M9GQ", "return_url": "https://example.com", "trial_period_days": 10 }' ``` ```curl Checkout Session Curl example expandable theme={null} curl --request POST \ --url https://example.com/api/checkout \ --header 'Content-Type: application/json' \ --header 'User-Agent: insomnia/11.2.0' \ --cookie mode=test \ --data '{ "product_cart": [ { "product_id": "pdt_QMDuvLkbVzCRWRQjLNcs", "quantity": 1 } ], "customer": { "email": "test@example.com", "name": "test" }, "return_url": "https://example.com/success" }' ``` Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal. ```typescript Astro Route Handler expandable theme={null} // src/pages/api/customer-portal.ts import { CustomerPortal } from "@dodopayments/astro"; export const GET = CustomerPortal({ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY, environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT, }); ``` ```curl Customer Portal Curl example expandable theme={null} curl --request GET \ --url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \ --header 'User-Agent: insomnia/11.2.0' \ --cookie mode=test ``` Use this handler to receive and process Dodo Payments webhook events securely in your Astro app. ```typescript expandable theme={null} // src/pages/api/webhook.ts import { Webhooks } from "@dodopayments/astro"; export const POST = Webhooks({ webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // handle the payload }, // ... other event handlers for granular control }); ``` ## Checkout Route Handler Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows. * **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection. * **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs. * **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details. ### Supported Query Parameters Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR). Quantity of the product. Customer's full name. Customer's first name. Customer's last name. Customer's email address. Customer's country. Customer's address line. Customer's city. Customer's state/province. Customer's zip/postal code. Disable full name field. Disable first name field. Disable last name field. Disable email field. Disable country field. Disable address line field. Disable city field. Disable state field. Disable zip code field. Specify the payment currency (e.g., USD). Show currency selector. Specify the payment amount (e.g., 1000 for \$10.00). Show discount fields. Any query parameter starting with metadata\_ will be passed as metadata. If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response. ### Response Format Static checkout returns a JSON response with the checkout URL: ```json theme={null} { "checkout_url": "https://checkout.dodopayments.com/..." } ``` * Send parameters as a JSON body in a POST request. * Supports both one-time and recurring payments. * For a complete list of supported POST body fields, refer to: * [Request body for a One Time Payment Product](https://docs.dodopayments.com/api-reference/payments/post-payments) * [Request body for a Subscription Product](https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions) ### Response Format Dynamic checkout returns a JSON response with the checkout URL: ```json theme={null} { "checkout_url": "https://checkout.dodopayments.com/..." } ``` Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control. Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields. ### Response Format Checkout sessions return a JSON response with the checkout URL: ```json theme={null} { "checkout_url": "https://checkout.dodopayments.com/session/..." } ``` ## Customer Portal Route Handler The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Astro application. ### Query Parameters The customer ID for the portal session (e.g., ?customer\_id=cus\_123). If set to true, sends an email to the customer with the portal link. Returns 400 if customer\_id is missing. ## Webhook Route Handler * **Method:** Only POST requests are supported. Other methods return 405. * **Signature Verification:** Verifies the webhook signature using webhookKey. Returns 401 if verification fails. * **Payload Validation:** Validated with Zod. Returns 400 for invalid payloads. * **Error Handling:** * 401: Invalid signature * 400: Invalid payload * 500: Internal error during verification * **Event Routing:** Calls the appropriate event handler based on the payload type. ### Supported Webhook Event Handlers ```typescript Typescript expandable theme={null} onPayload?: (payload: WebhookPayload) => Promise; onPaymentSucceeded?: (payload: WebhookPayload) => Promise; onPaymentFailed?: (payload: WebhookPayload) => Promise; onPaymentProcessing?: (payload: WebhookPayload) => Promise; onPaymentCancelled?: (payload: WebhookPayload) => Promise; onRefundSucceeded?: (payload: WebhookPayload) => Promise; onRefundFailed?: (payload: WebhookPayload) => Promise; onDisputeOpened?: (payload: WebhookPayload) => Promise; onDisputeExpired?: (payload: WebhookPayload) => Promise; onDisputeAccepted?: (payload: WebhookPayload) => Promise; onDisputeCancelled?: (payload: WebhookPayload) => Promise; onDisputeChallenged?: (payload: WebhookPayload) => Promise; onDisputeWon?: (payload: WebhookPayload) => Promise; onDisputeLost?: (payload: WebhookPayload) => Promise; onSubscriptionActive?: (payload: WebhookPayload) => Promise; onSubscriptionOnHold?: (payload: WebhookPayload) => Promise; onSubscriptionRenewed?: (payload: WebhookPayload) => Promise; onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise; onSubscriptionCancelled?: (payload: WebhookPayload) => Promise; onSubscriptionFailed?: (payload: WebhookPayload) => Promise; onSubscriptionExpired?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; ``` *** ## Prompt for LLM ``` You are an expert Astro developer assistant. Your task is to guide a user through integrating the @dodopayments/astro adapter into their existing Astro project. The @dodopayments/astro adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for the Astro App Router. First, install the necessary packages. Use the package manager appropriate for your project (npm, yarn, or bun) based on the presence of lock files (e.g., package-lock.json for npm, yarn.lock for yarn, bun.lockb for bun): npm install @dodopayments/astro Here's how you should structure your response: Ask the user which functionalities they want to integrate. "Which parts of the @dodopayments/astro adapter would you like to integrate into your project? You can choose one or more of the following: Checkout Route Handler (for handling product checkouts) Customer Portal Route Handler (for managing customer subscriptions/details) Webhook Route Handler (for receiving Dodo Payments webhook events) All (integrate all three)" Based on the user's selection, provide detailed integration steps for each chosen functionality. If Checkout Route Handler is selected: Purpose: This handler redirects users to the Dodo Payments checkout page. File Creation: Create a new file at app/checkout/route.ts in your Astro project. Code Snippet: // src/pages/api/checkout.ts import { Checkout } from "@dodopayments/astro"; export const prerender = false; export const GET = Checkout({ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY, returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL, environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT, type: "static", // optional, defaults to 'static' }); export const POST = Checkout({ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY, returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL, environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT, type: "dynamic", // for dynamic checkout }); export const POST = Checkout({ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY, returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL, environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT, type: "session", // for checkout sessions }); Configuration & Usage: bearerToken: Your Dodo Payments API key. It's recommended to set this via the DODO_PAYMENTS_API_KEY environment variable. returnUrl: (Optional) The URL to redirect the user to after a successful checkout. environment: (Optional) Set to "test_mode" for testing, or omit/set to "live_mode" for production. type: (Optional) Set to "static" for GET/static checkout, "dynamic" for POST/dynamic checkout, or "session" for POST/checkout sessions. Static Checkout (GET) Query Parameters: productId (required): Product identifier (e.g., ?productId=pdt_nZuwz45WAs64n3l07zpQR) quantity (optional): Quantity of the product Customer Fields (optional): fullName, firstName, lastName, email, country, addressLine, city, state, zipCode Disable Flags (optional, set to true to disable): disableFullName, disableFirstName, disableLastName, disableEmail, disableCountry, disableAddressLine, disableCity, disableState, disableZipCode Advanced Controls (optional): paymentCurrency, showCurrencySelector, paymentAmount, showDiscounts Metadata (optional): Any query parameter starting with metadata_ (e.g., ?metadata_userId=abc123) Returns: {"checkout_url": "https://checkout.dodopayments.com/..."} Dynamic Checkout (POST) - Returns JSON with checkout_url: Parameters are sent as a JSON body. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}. For a complete list of supported POST body fields, refer to: Docs - One Time Payment Product: https://docs.dodopayments.com/api-reference/payments/post-payments Docs - Subscription Product: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions Checkout Sessions (POST) - (Recommended) A more customizable checkout experience. Returns JSON with checkout_url: Parameters are sent as a JSON body. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://checkout.dodopayments.com/session/..."}. For a complete list of supported fields, refer to: Checkout Sessions Integration Guide: https://docs.dodopayments.com/developer-resources/checkout-session Error Handling: If productId is missing or other query parameters are invalid, the handler will return a 400 response. If Customer Portal Route Handler is selected: Purpose: This handler redirects authenticated users to their Dodo Payments customer portal. File Creation: Create a new file at app/customer-portal/route.ts in your Astro project. Code Snippet: // src/pages/api/customer-portal.ts import { CustomerPortal } from "@dodopayments/astro"; export const GET = CustomerPortal({ bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY, environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT, }); Query Parameters: customer_id (required): The customer ID for the portal session (e.g., ?customer_id=cus_123) send_email (optional, boolean): If set to true, sends an email to the customer with the portal link. Returns 400 if customer_id is missing. If Webhook Route Handler is selected: Purpose: This handler processes incoming webhook events from Dodo Payments, allowing your application to react to events like successful payments, refunds, or subscription changes. File Creation: Create a new file at app/api/webhook/dodo-payments/route.ts in your Astro project. Code Snippet: // src/pages/api/webhook.ts import { Webhooks } from "@dodopayments/astro"; export const POST = Webhooks({ webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // handle the payload }, // ... other event handlers for granular control }); Handler Details: Method: Only POST requests are supported. Other methods return 405. Signature Verification: The handler verifies the webhook signature using the webhookKey and returns 401 if verification fails. Payload Validation: The payload is validated with Zod. Returns 400 for invalid payloads. Error Handling: 401: Invalid signature 400: Invalid payload 500: Internal error during verification Event Routing: Calls the appropriate event handler based on the payload type. Supported Webhook Event Handlers: onPayload?: (payload: WebhookPayload) => Promise onPaymentSucceeded?: (payload: WebhookPayload) => Promise onPaymentFailed?: (payload: WebhookPayload) => Promise onPaymentProcessing?: (payload: WebhookPayload) => Promise onPaymentCancelled?: (payload: WebhookPayload) => Promise onRefundSucceeded?: (payload: WebhookPayload) => Promise onRefundFailed?: (payload: WebhookPayload) => Promise onDisputeOpened?: (payload: WebhookPayload) => Promise onDisputeExpired?: (payload: WebhookPayload) => Promise onDisputeAccepted?: (payload: WebhookPayload) => Promise onDisputeCancelled?: (payload: WebhookPayload) => Promise onDisputeChallenged?: (payload: WebhookPayload) => Promise onDisputeWon?: (payload: WebhookPayload) => Promise onDisputeLost?: (payload: WebhookPayload) => Promise onSubscriptionActive?: (payload: WebhookPayload) => Promise onSubscriptionOnHold?: (payload: WebhookPayload) => Promise onSubscriptionRenewed?: (payload: WebhookPayload) => Promise onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise onSubscriptionCancelled?: (payload: WebhookPayload) => Promise onSubscriptionFailed?: (payload: WebhookPayload) => Promise onSubscriptionExpired?: (payload: WebhookPayload) => Promise onLicenseKeyCreated?: (payload: WebhookPayload) => Promise Environment Variable Setup: To ensure the adapter functions correctly, you will need to manually set up the following environment variables in your Astro project's deployment environment (e.g., Vercel, Netlify, AWS, etc.): DODO_PAYMENTS_API_KEY: Your Dodo Payments API Key (required for Checkout and Customer Portal). RETURN_URL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler). DODO_PAYMENTS_WEBHOOK_SECRET: Your Dodo Payments Webhook Secret (required for Webhook handler). Example .env file: DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode" DODO_PAYMENTS_RETURN_URL=your-return-url Usage in your code: bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY Important: Never commit sensitive environment variables directly into your version control. Use environment variables for all sensitive information. If the user needs assistance setting up environment variables for their specific deployment environment, ask them what platform they are using (e.g., Vercel, Netlify, AWS, etc.), and provide guidance. You can also add comments to their PR or chat depending on the context ``` # Astro Minimal Boilerplate Source: https://docs.dodopayments.com/developer-resources/astro-boilerplate Get started quickly with our minimal Astro boilerplate for integrating Dodo Payments into your Astro application ## Overview The Astro minimal boilerplate provides a ready-to-use starting point for integrating Dodo Payments with your Astro application. This template includes checkout sessions, webhook handling, customer portal, and modern UI components to help you start accepting payments quickly. This boilerplate uses Astro 5 with TypeScript, Tailwind CSS 4, and the `@dodopayments/astro` adapter. ### Features * **Quick Setup** - Get started in under 5 minutes * **Payment Integration** - Pre-configured checkout flow using `@dodopayments/astro` * **Modern UI** - Clean, dark-themed pricing page with Tailwind CSS * **Webhook Handler** - Ready-to-use webhook endpoint for payment events * **Customer Portal** - One-click subscription management * **TypeScript** - Fully typed with minimal, focused types * **Pre-filled Checkout** - Demonstrates passing customer data to improve UX ## Prerequisites Before you begin, make sure you have: * **Node.js LTS version** (required for Astro 5) * **Dodo Payments account** (to access API and Webhook Keys from dashboard) ## Quick Start ```bash theme={null} git clone https://github.com/dodopayments/dodo-astro-minimal-boilerplate.git cd dodo-astro-minimal-boilerplate ``` ```bash theme={null} npm install ``` Sign up at [Dodo Payments](https://dodopayments.com/) and get your credentials from the dashboard: * **API Key:** [Dashboard → Developer → API Keys](https://app.dodopayments.com/developer/api-keys) * **Webhook Key:** [Dashboard → Developer → Webhooks](https://app.dodopayments.com/developer/webhooks) Make sure you're in **Test Mode** while developing! Create a `.env` file in the root directory: ```bash theme={null} cp .env.example .env ``` Update the values with your Dodo Payments credentials: ```env theme={null} DODO_PAYMENTS_API_KEY=your_api_key_here DODO_PAYMENTS_WEBHOOK_KEY=your_webhook_signing_key_here DODO_PAYMENTS_RETURN_URL=http://localhost:4321/ DODO_PAYMENTS_ENVIRONMENT=test_mode ``` Never commit your `.env` file to version control. It's already included in `.gitignore`. Update `src/lib/products.ts` with your actual product IDs from Dodo Payments: ```typescript theme={null} export const products: Product[] = [ { product_id: "pdt_001", // Replace with your product ID name: "Basic Plan", description: "Get access to basic features and support", price: 9999, // in cents features: [ "Access to basic features", "Email support", "1 Team member", "Basic analytics", ], }, // ... add more products ]; ``` ```bash theme={null} npm run dev ``` Open [http://localhost:4321](http://localhost:4321) to see your pricing page! ## Project Structure ```text theme={null} src/ ├── components/ │ ├── Footer.astro # Reusable footer │ ├── Header.astro # Navigation header │ └── ProductCard.astro # Product pricing card ├── layouts/ │ └── Layout.astro # Root layout ├── lib/ │ └── products.ts # Product definitions ├── pages/ │ ├── index.astro # Pricing page (home) │ └── api/ │ ├── checkout.ts # Checkout session handler │ ├── customer-portal.ts # Customer portal redirect │ └── webhook.ts # Webhook event handler └── styles/ └── global.css # Global styles with Tailwind ``` ## Customization ### Update Product Information Edit `src/lib/products.ts` to modify: * Product IDs (from your Dodo dashboard) * Pricing * Features * Descriptions ### Pre-fill Customer Data In `src/components/ProductCard.astro`, replace the hardcoded values with your actual user data: ```typescript theme={null} customer: { name: "John Doe", email: "john@example.com", }, ``` ### Update Customer Portal In `src/components/Header.astro`, replace the hardcoded customer ID with the actual customer ID from your auth system or database: ```typescript theme={null} const CUSTOMER_ID = "cus_001"; // Replace with actual customer ID ``` You can complete a test purchase to get the customer ID for testing. ## Webhook Events The boilerplate demonstrates handling webhook events in `src/pages/api/webhook.ts`: * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCancelled` - Triggered when a subscription is cancelled Add your business logic inside these handlers: ```typescript theme={null} onSubscriptionActive: async (payload) => { // Grant access to your product // Update user database // Send welcome email }, ``` Add more webhook events as needed. For local development, you can use tools like [ngrok](https://ngrok.com/) to create a secure tunnel to your local server and use it as your webhook URL. ## Deployment This boilerplate uses static output with on-demand rendering for API routes. You'll need to install an adapter for your deployment platform: | Platform | Guide | | ---------- | ----------------------------------------------------------------------------- | | Vercel | [Deploy to Vercel](https://docs.astro.build/en/guides/deploy/vercel/) | | Netlify | [Deploy to Netlify](https://docs.astro.build/en/guides/deploy/netlify/) | | Cloudflare | [Deploy to Cloudflare](https://docs.astro.build/en/guides/deploy/cloudflare/) | See [Astro's deployment guides](https://docs.astro.build/en/guides/deploy/) for all platforms. ### Update Webhook URL After deploying, update your webhook URL in the [Dodo Payments Dashboard](https://app.dodopayments.com/developer/webhooks): ```text theme={null} https://your-domain.com/api/webhook ``` Also remember to update the `DODO_PAYMENTS_WEBHOOK_KEY` environment variable in your production environment to match the webhook signing key for your deployed domain. ## Troubleshooting Delete `node_modules` and reinstall dependencies: ```bash theme={null} rm -rf node_modules package-lock.json npm install ``` **Common causes:** * Invalid product ID - verify it exists in your Dodo dashboard * Wrong API key or environment setting in `.env` * Check browser console and terminal for errors For local testing, use [ngrok](https://ngrok.com) to expose your server: ```bash theme={null} ngrok http 4321 ``` Update the webhook URL in your [Dodo dashboard](https://app.dodopayments.com/developer/webhooks) to the ngrok URL. Remember to update your .env file with the correct webhook verification key. Replace the hardcoded `CUSTOMER_ID` in `src/components/Header.astro` with an actual customer ID from your Dodo dashboard. Or integrate your authentication system and database to fetch the customer ID dynamically. This boilerplate uses static output with on-demand API routes. You need to install an adapter for deployment: See [Astro's deployment guides](https://docs.astro.build/en/guides/deploy/) for details. ## Learn More * [Dodo Payments Documentation](https://docs.dodopayments.com/) * [Checkout Sessions Documentation](https://docs.dodopayments.com/developer-resources/checkout-sessions) * [Webhooks Documentation](https://docs.dodopayments.com/developer-resources/webhooks) ## Support Need help with the boilerplate? * Join our [Discord community](https://discord.gg/bYqAp4ayYh) for questions and discussions * Check the [GitHub repository](https://github.com/dodopayments/dodo-astro-minimal-boilerplate) for issues and updates * Contact our [support team](mailto:support@dodopayments.com) for assistance # Better-Auth Adapter Source: https://docs.dodopayments.com/developer-resources/better-auth-adaptor This guide shows you how to integrate Dodo Payments into your authentication flow using the Better-Auth adaptor. # Overview The Better Auth Adapter for Dodo Payments provides: * Automatic customer creation on sign-up * Checkout Sessions (preferred) with product slug mapping * Self-service customer portal * Metered usage ingestion and reporting endpoints for usage-based billing * Real-time webhook event processing with signature verification * Full TypeScript support You need a Dodo Payments account and API keys to use this integration. # Prerequisites * Node.js 20+ * Access to your Dodo Payments Dashboard * Existing project using [better-auth](https://www.npmjs.com/package/better-auth) # Installation Run the following command in your project root: ```bash theme={null} npm install @dodopayments/better-auth dodopayments better-auth zod ``` All required packages are now installed. # Setup Add these to your .env file: ```env expandable theme={null} DODO_PAYMENTS_API_KEY=your_api_key_here DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here BETTER_AUTH_URL=http://localhost:3000 BETTER_AUTH_SECRET=your_better_auth_secret_here ``` Never commit API keys or secrets to version control. Create or update src/lib/auth.ts: ```typescript expandable theme={null} import { betterAuth } from "better-auth"; import { dodopayments, checkout, portal, webhooks, usage, } from "@dodopayments/better-auth"; import DodoPayments from "dodopayments"; export const dodoPayments = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY!, environment: "test_mode", // or "live_mode" for production }); export const { auth, endpoints, client } = BetterAuth({ plugins: [ dodopayments({ client: dodoPayments, createCustomerOnSignUp: true, use: [ checkout({ products: [ { productId: "pdt_xxxxxxxxxxxxxxxxxxxxx", slug: "premium-plan", }, ], successUrl: "/dashboard/success", authenticatedUsersOnly: true, }), portal(), webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!, onPayload: async (payload) => { console.log("Received webhook:", payload.event_type); }, }), usage(), ], }), ], }); ``` Set environment to live\_mode for production. Create or update src/lib/auth-client.ts: ```typescript expandable theme={null} import { createAuthClient } from "better-auth/react"; import { dodopaymentsClient } from "@dodopayments/better-auth"; export const authClient = createAuthClient({ baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000", plugins: [dodopaymentsClient()], }); ``` # Usage Examples Prefer authClient.dodopayments.checkoutSession for new integrations. The legacy checkout method is deprecated and kept only for backward compatibility. ## Creating a Checkout Session (Preferred) ```typescript Using a slug theme={null} const { data: session, error } = await authClient.dodopayments.checkoutSession({ // Option A: use a configured slug slug: "premium-plan", // Your internal id for reference referenceId: "order_123", }); if (session) { window.location.href = session.url; } ``` ```typescript Using product cart theme={null} const { data: session, error } = await authClient.dodopayments.checkoutSession({ // Option B: pass a product cart/product id directly product_cart: [ { product_id: "pdt_xxxxxxxxxxxxxxxxxxxxx", quantity: 1, } ], // Your internal id for reference referenceId: "order_123", }); if (session) { window.location.href = session.url; } ``` Unlike the legacy checkout method, checkoutSession does not require billing information upfront as it will be taken from the user at the checkout page. You can still override it by passing the billing key in the argument. Similar to the legacy checkout method, checkoutSession takes customer's email and name from their better-auth session, however, you can override it by passing the customer object with email and name. You can pass the same options in the argument as the request body for [Create Checkout Session](https://docs.dodopayments.com/api-reference/checkout-sessions/create) endpoint. The return URL is taken from the successUrl configured in the server plugin. You do not need to include return\_url in the client payload. ## Legacy Checkout (Deprecated) The authClient.dodopayments.checkout method is deprecated. Use checkoutSession instead for new implementations. ```typescript expandable theme={null} const { data: checkout, error } = await authClient.dodopayments.checkout({ slug: "premium-plan", customer: { email: "customer@example.com", name: "John Doe", }, billing: { city: "San Francisco", country: "US", state: "CA", street: "123 Market St", zipcode: "94103", }, referenceId: "order_123", }); if (checkout) { window.location.href = checkout.url; } ``` ## Accessing the Customer Portal ```typescript expandable theme={null} const { data: customerPortal, error } = await authClient.dodopayments.customer.portal(); if (customerPortal && customerPortal.redirect) { window.location.href = customerPortal.url; } ``` ## Listing Customer Data ```typescript expandable theme={null} // Get subscriptions const { data: subscriptions, error } = await authClient.dodopayments.customer.subscriptions.list({ query: { limit: 10, page: 1, status: "active", }, }); // Get payment history const { data: payments, error } = await authClient.dodopayments.customer.payments.list({ query: { limit: 10, page: 1, status: "succeeded", }, }); ``` ## Tracking Metered Usage Enable the usage() plugin on the server to capture metered events and let customers inspect their usage-based billing. * authClient.dodopayments.usage.ingest ingests events for the signed-in, email-verified user. * authClient.dodopayments.usage.meters.list lists recent usage for the meters tied to that customer’s subscriptions. ```typescript expandable theme={null} // Record a metered event (e.g., an API request) const { error: ingestError } = await authClient.dodopayments.usage.ingest({ event_id: crypto.randomUUID(), event_name: "api_request", metadata: { route: "/reports", method: "GET", }, // Optional Date; defaults to now if omitted timestamp: new Date(), }); if (ingestError) { console.error("Failed to record usage", ingestError); } // List recent usage for the current customer const { data: usage, error: usageError } = await authClient.dodopayments.usage.meters.list({ query: { page_size: 20, meter_id: "mtr_yourMeterId", // optional }, }); if (usage?.items) { usage.items.forEach((event) => { console.log(event.event_name, event.timestamp, event.metadata); }); } ``` Timestamps older than one hour or more than five minutes into the future are rejected when ingesting usage. If you omit meter\_id when listing usage meters, all meters tied to the customer’s subscriptions are returned. # Webhooks The webhooks plugin processes real-time payment events from Dodo Payments with secure signature verification. The default endpoint is /api/auth/dodopayments/webhooks. Generate a webhook secret for your endpoint URL (e.g., https\://\/api/auth/dodopayments/webhooks) in the Dodo Payments Dashboard and set it in your .env file: ```env theme={null} DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here ``` Example handler: ```typescript expandable theme={null} webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!, onPayload: async (payload) => { console.log("Received webhook:", payload.event_type); }, }); ``` ### Supported Webhook Event Handlers ```typescript Typescript expandable theme={null} onPayload?: (payload: WebhookPayload) => Promise; onPaymentSucceeded?: (payload: WebhookPayload) => Promise; onPaymentFailed?: (payload: WebhookPayload) => Promise; onPaymentProcessing?: (payload: WebhookPayload) => Promise; onPaymentCancelled?: (payload: WebhookPayload) => Promise; onRefundSucceeded?: (payload: WebhookPayload) => Promise; onRefundFailed?: (payload: WebhookPayload) => Promise; onDisputeOpened?: (payload: WebhookPayload) => Promise; onDisputeExpired?: (payload: WebhookPayload) => Promise; onDisputeAccepted?: (payload: WebhookPayload) => Promise; onDisputeCancelled?: (payload: WebhookPayload) => Promise; onDisputeChallenged?: (payload: WebhookPayload) => Promise; onDisputeWon?: (payload: WebhookPayload) => Promise; onDisputeLost?: (payload: WebhookPayload) => Promise; onSubscriptionActive?: (payload: WebhookPayload) => Promise; onSubscriptionOnHold?: (payload: WebhookPayload) => Promise; onSubscriptionRenewed?: (payload: WebhookPayload) => Promise; onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise; onSubscriptionCancelled?: (payload: WebhookPayload) => Promise; onSubscriptionFailed?: (payload: WebhookPayload) => Promise; onSubscriptionExpired?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; ``` # Configuration Reference * client (required): DodoPayments client instance - createCustomerOnSignUp (optional): Auto-create customers on user signup - use (required): Array of plugins to enable (checkout, portal, usage, webhooks) * products: Array of products or async function returning products - successUrl: URL to redirect after successful payment - authenticatedUsersOnly: Require user authentication (default: false) # Troubleshooting & Tips * Invalid API key: Double-check your DODO\_PAYMENTS\_API\_KEY in .env. - Webhook signature mismatch: Ensure the webhook secret matches the one set in the Dodo Payments Dashboard. - Customer not created: Confirm createCustomerOnSignUp is set to true. * Use environment variables for all secrets and keys. - Test in test\_mode before switching to live\_mode. - Log webhook events for debugging and auditing. # Prompt for LLMs ```text expandable theme={null} You are a skilled developer helping to integrate the @dodopayments/better-auth adapter into a typescript web application with better-auth. This adapter enables seamless payment processing through Dodo Payments with automatic customer management, checkout flows, and webhook handling. STAGE 1: BASIC SETUP This stage covers the foundational setup needed before implementing any plugins. Complete this stage first. STEP 1: Installation Install the required dependencies: npm install @dodopayments/better-auth dodopayments better-auth zod STEP 2: Environment Variables Setup You will need to complete these external setup tasks. I will provide you with a TODO list for the actions you need to take outside of the code: TODO LIST FOR USER: 1. Generate Dodo Payments API Key: - Go to your Dodo Payments Dashboard > Developer > API Keys - Create a new API key (or use existing) - Copy the API key value - Set environment variable: DODO_PAYMENTS_API_KEY=your_api_key_here 2. Generate Better Auth Secret: - Generate a random secret key (32+ characters) - Set environment variable: BETTER_AUTH_SECRET=your_better_auth_secret_here 3. Set Application URL: - For development: BETTER_AUTH_URL=http://localhost:3000 - For production: BETTER_AUTH_URL=https://your-domain.com 4. Webhook Secret (only if implementing webhooks plugin): - This will be provided after you specify your domain name in Stage 2 - Set environment variable: DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here Add these environment variables to your .env file: DODO_PAYMENTS_API_KEY=your_api_key_here DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here BETTER_AUTH_URL=http://localhost:3000 BETTER_AUTH_SECRET=your_better_auth_secret_here STEP 3: Server Configuration Create or update your better-auth setup file (src/lib/auth.ts): import { BetterAuth } from "better-auth"; import { dodopayments } from "@dodopayments/better-auth"; import DodoPayments from "dodopayments"; // Create DodoPayments client export const dodoPayments = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY!, environment: "test_mode", // Change to "live_mode" for production }); // Configure better-auth with dodopayments adapter export const { auth, endpoints, client } = BetterAuth({ plugins: [ dodopayments({ client: dodoPayments, createCustomerOnSignUp: true, // Auto-create customers on signup use: [], // We'll add plugins here in Stage 2 }), ], }); STEP 4: Client Configuration Create or update your auth client file (src/lib/auth-client.ts): import { createAuthClient } from "better-auth/react"; import { dodopaymentsClient } from "@dodopayments/better-auth"; export const authClient = createAuthClient({ baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000", plugins: [dodopaymentsClient()], }); STAGE 2: PLUGIN IMPLEMENTATION After completing Stage 1, you can selectively implement any of these plugins based on your needs. Each plugin is independent and can be added or removed as needed. PLUGIN SELECTION: Before implementing any plugins, ask the user which plugins they want to implement: "Which plugins would you like to implement? You can choose any combination of: 1. CHECKOUT - Enables secure payment processing and checkout flows 2. PORTAL - Provides customer self-service portal for subscriptions and payments 3. USAGE - Tracks metered events and exposes usage history for billing beyond included allowances 4. WEBHOOKS - Handles real-time payment events from Dodo Payments Please specify which plugins you want (e.g., 'checkout and usage', 'all four', 'just portal', etc.)" If the user doesn't respond or you cannot prompt the user, implement all four plugins by default. Based on the user's selection, implement only the requested plugins from the sections below: CHECKOUT PLUGIN Purpose: Enables secure payment processing using Checkout Sessions (preferred) with product slug mapping. A legacy checkout endpoint is also exposed for backward compatibility but is deprecated. SETUP TODO LIST FOR USER: 1. Create products in Dodo Payments Dashboard: - Go to Dodo Payments Dashboard > Products - Create your products (e.g., subscription plans, one-time purchases) - Copy each product ID (starts with "pdt_") - Note down the product names for creating friendly slugs 2. Plan your checkout URLs: - Decide on your success URL (e.g., "/dashboard/success", "/thank-you") - Ensure this URL exists in your application Configuration: Add checkout to your imports in src/lib/auth.ts: import { dodopayments, checkout } from "@dodopayments/better-auth"; Add checkout plugin to the use array in your dodopayments configuration: use: [ checkout({ products: [ { productId: "pdt_xxxxxxxxxxxxxxxxxxxxx", // Your actual product ID from Dodo Payments slug: "premium-plan", // Friendly slug for checkout }, // Add more products as needed ], successUrl: "/dashboard/success", // Your success page URL authenticatedUsersOnly: true, // Require login for checkout }), ], Usage Example (Preferred - Checkout Sessions): const { data: session, error } = await authClient.dodopayments.checkoutSession({ // Use the slug from your configuration OR provide product_cart directly slug: "premium-plan", // product_cart: [{ product_id: "pdt_xxxxxxxxxxxxxxxxxxxxx", quantity: 1 }], referenceId: "order_123", // Optional reference }); if (session) { window.location.href = session.url; } Deprecated (Legacy Checkout): const { data: checkout, error } = await authClient.dodopayments.checkout({ slug: "premium-plan", customer: { email: "customer@example.com", name: "John Doe", }, billing: { city: "San Francisco", country: "US", state: "CA", street: "123 Market St", zipcode: "94103", }, }); if (checkout) { window.location.href = checkout.url; } Options: - products: Array of products or async function returning products - successUrl: URL to redirect after successful payment - authenticatedUsersOnly: Require user authentication (default: false) PORTAL PLUGIN Purpose: Provides customer self-service capabilities for managing subscriptions and viewing payment history. Configuration: Add portal to your imports in src/lib/auth.ts: import { dodopayments, portal } from "@dodopayments/better-auth"; Add portal plugin to the use array in your dodopayments configuration: use: [ portal(), ], Usage Examples: // Access customer portal const { data: customerPortal, error } = await authClient.dodopayments.customer.portal(); if (customerPortal && customerPortal.redirect) { window.location.href = customerPortal.url; } // List customer subscriptions const { data: subscriptions, error } = await authClient.dodopayments.customer.subscriptions.list({ query: { limit: 10, page: 1, active: true, }, }); // List customer payments const { data: payments, error } = await authClient.dodopayments.customer.payments.list({ query: { limit: 10, page: 1, status: "succeeded", }, }); Note: All portal methods require user authentication. USAGE PLUGIN (METERED BILLING) Purpose: Records metered events (like API requests) for usage-based plans and surfaces recent usage history per customer. SETUP TODO LIST FOR USER: 1. Create or identify usage meters in the Dodo Payments Dashboard: - Dashboard > Usage > Meters - Copy the meter IDs (prefixed with mtr_) used by your usage-based plans 2. Decide which event names and metadata you will capture (e.g., api_request, route, method) 3. Ensure BetterAuth users verify their email addresses (the plugin enforces this before ingesting usage) Configuration: Add usage to your imports in src/lib/auth.ts: import { dodopayments, usage } from "@dodopayments/better-auth"; Add the plugin to the use array: use: [ usage(), ], Usage Examples: // Record a metered event for the signed-in customer const { error: ingestError } = await authClient.dodopayments.usage.ingest({ event_id: crypto.randomUUID(), event_name: "api_request", metadata: { route: "/reports", method: "GET", }, // Optional Date or ISO string; defaults to now timestamp: new Date(), }); if (ingestError) { console.error("Failed to record usage", ingestError); } // List recent usage for the current customer const { data: usage, error: usageError } = await authClient.dodopayments.usage.meters.list({ query: { page_size: 20, meter_id: "mtr_yourMeterId", // optional filter }, }); if (usage?.items) { usage.items.forEach((event) => { console.log(event.event_name, event.timestamp, event.metadata); }); } The plugin exposes authClient.dodopayments.usage.ingest and authClient.dodopayments.usage.meters.list. Timestamps older than one hour or more than five minutes in the future are rejected. If you do not pass meter_id when listing usage meters, all meters tied to the customer’s active subscriptions are returned. WEBHOOKS PLUGIN Purpose: Handles real-time payment events from Dodo Payments with secure signature verification. BEFORE CONFIGURATION - Setup Webhook URL: First, I need your domain name to generate the webhook URL and provide you with setup instructions. STEP 1: Domain Name Input What is your domain name? Please provide: - For production: your domain name (e.g., "myapp.com", "api.mycompany.com") - For staging: your staging domain (e.g., "staging.myapp.com") - For development: use "localhost:3000" (or your local port) STEP 2: After receiving your domain name, I will: - Generate your webhook URL: https://[YOUR-DOMAIN]/api/auth/dodopayments/webhooks - Provide you with a TODO list for webhook setup in Dodo Payments dashboard - Give you the exact environment variable setup instructions WEBHOOK SETUP TODO LIST (will be provided after domain input): 1. Configure webhook in Dodo Payments Dashboard: - Go to Dodo Payments Dashboard > Developer > Webhooks - Click "Add Webhook" or "Create Webhook" - Enter webhook URL: https://[YOUR-DOMAIN]/api/auth/dodopayments/webhooks - Select events you want to receive (or select all) - Copy the generated webhook secret 2. Set webhook secret in your environment: - For production: Set DODO_PAYMENTS_WEBHOOK_SECRET in your hosting provider environment - For staging: Set DODO_PAYMENTS_WEBHOOK_SECRET in your staging environment - For development: Add DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here to your .env file 3. Deploy your application with the webhook secret configured STEP 3: Add webhooks to your imports in src/lib/auth.ts: import { dodopayments, webhooks } from "@dodopayments/better-auth"; STEP 4: Add webhooks plugin to the use array in your dodopayments configuration: use: [ webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!, // Generic handler for all webhook events onPayload: async (payload) => { console.log("Received webhook:", payload.event_type); }, // Payment event handlers onPaymentSucceeded: async (payload) => { console.log("Payment succeeded:", payload); }, onPaymentFailed: async (payload) => { console.log("Payment failed:", payload); }, onPaymentProcessing: async (payload) => { console.log("Payment processing:", payload); }, onPaymentCancelled: async (payload) => { console.log("Payment cancelled:", payload); }, // Refund event handlers onRefundSucceeded: async (payload) => { console.log("Refund succeeded:", payload); }, onRefundFailed: async (payload) => { console.log("Refund failed:", payload); }, // Dispute event handlers onDisputeOpened: async (payload) => { console.log("Dispute opened:", payload); }, onDisputeExpired: async (payload) => { console.log("Dispute expired:", payload); }, onDisputeAccepted: async (payload) => { console.log("Dispute accepted:", payload); }, onDisputeCancelled: async (payload) => { console.log("Dispute cancelled:", payload); }, onDisputeChallenged: async (payload) => { console.log("Dispute challenged:", payload); }, onDisputeWon: async (payload) => { console.log("Dispute won:", payload); }, onDisputeLost: async (payload) => { console.log("Dispute lost:", payload); }, // Subscription event handlers onSubscriptionActive: async (payload) => { console.log("Subscription active:", payload); }, onSubscriptionOnHold: async (payload) => { console.log("Subscription on hold:", payload); }, onSubscriptionRenewed: async (payload) => { console.log("Subscription renewed:", payload); }, onSubscriptionPlanChanged: async (payload) => { console.log("Subscription plan changed:", payload); }, onSubscriptionCancelled: async (payload) => { console.log("Subscription cancelled:", payload); }, onSubscriptionFailed: async (payload) => { console.log("Subscription failed:", payload); }, onSubscriptionExpired: async (payload) => { console.log("Subscription expired:", payload); }, // License key event handlers onLicenseKeyCreated: async (payload) => { console.log("License key created:", payload); }, }), ], Supported Webhook Event Handlers: - onPayload: Generic handler for all webhook events - onPaymentSucceeded: Payment completed successfully - onPaymentFailed: Payment failed - onPaymentProcessing: Payment is being processed - onPaymentCancelled: Payment was cancelled - onRefundSucceeded: Refund completed successfully - onRefundFailed: Refund failed - onDisputeOpened: Dispute was opened - onDisputeExpired: Dispute expired - onDisputeAccepted: Dispute was accepted - onDisputeCancelled: Dispute was cancelled - onDisputeChallenged: Dispute was challenged - onDisputeWon: Dispute was won - onDisputeLost: Dispute was lost - onSubscriptionActive: Subscription became active - onSubscriptionOnHold: Subscription was put on hold - onSubscriptionRenewed: Subscription was renewed - onSubscriptionPlanChanged: Subscription plan was changed - onSubscriptionCancelled: Subscription was cancelled - onSubscriptionFailed: Subscription failed - onSubscriptionExpired: Subscription expired - onLicenseKeyCreated: License key was created COMBINING SELECTED PLUGINS: After implementing the user's selected plugins, update your src/lib/auth.ts file to include all chosen plugins in the imports and use array: Example for all four plugins: import { dodopayments, checkout, portal, usage, webhooks, } from "@dodopayments/better-auth"; use: [ checkout({ // checkout configuration }), portal(), usage(), webhooks({ // webhook configuration }), ]; Example for checkout + portal + usage: import { dodopayments, checkout, portal, usage } from "@dodopayments/better-auth"; use: [ checkout({ // checkout configuration }), portal(), usage(), ]; Example for just webhooks: import { dodopayments, webhooks } from "@dodopayments/better-auth"; use: [ webhooks({ // webhook configuration }), ]; IMPORTANT NOTES: 1. Complete Stage 1 before implementing any plugins 2. Ask the user which plugins they want to implement, or implement all four if no response 3. Only implement the plugins the user specifically requested 4. ALWAYS provide TODO lists for external actions the user needs to complete: - API key generation and environment variable setup - Product creation in Dodo Payments dashboard (for checkout plugin) - Usage meter configuration and API event definition (for usage plugin) - Webhook setup in Dodo Payments dashboard (for webhooks plugin) - Domain name collection for webhook URL generation 5. For webhook plugin: Ask for the user's domain name and generate the exact webhook URL: https://[domain]/api/auth/dodopayments/webhooks 6. The usage plugin requires the BetterAuth session user to exist and have a verified email before ingesting or listing usage 7. All client methods return { data, error } objects for proper error handling 8. Use test_mode for development and live_mode for production 9. The webhook endpoint is automatically created and secured with signature verification (if webhooks plugin is selected) 10. Customer portal and subscription listing require user authentication (if portal plugin is selected) 11. Handle errors appropriately and test webhook functionality in development before going live 12. Present all external setup tasks as clear TODO lists with specific environment variable names ``` # Bun Adaptor Source: https://docs.dodopayments.com/developer-resources/bun-adaptor Learn how to integrate Dodo Payments with your Bun server project using our Bun Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup. Integrate Dodo Payments checkout into your Bun server. Allow customers to manage subscriptions and details. Receive and process Dodo Payments webhook events. ## Installation Run the following command in your project root: ```bash theme={null} bun add @dodopayments/bun ``` Create a .env file in your project root: ```env expandable theme={null} DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode" DODO_PAYMENTS_RETURN_URL=your-return-url ``` Never commit your .env file or secrets to version control. ## Route Handler Examples All examples assume you are using Bun's native server with Bun.serve(). Use this handler to integrate Dodo Payments checkout into your Bun server. Supports static (GET), dynamic (POST), and session (POST) payment flows. ```typescript Bun Server Handler expandable theme={null} import { Checkout } from '@dodopayments/bun'; const staticCheckoutHandler = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "static" }); const sessionCheckoutHandler = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "session" }); const dynamicCheckoutHandler = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "dynamic" }); Bun.serve({ port: 3000, fetch(request) { const url = new URL(request.url); if (url.pathname === "/api/checkout") { if (request.method === "GET") { return staticCheckoutHandler(request); } if (request.method === "POST") { return sessionCheckoutHandler(request); // or return dynamicCheckoutHandler(request); } } return new Response("Not Found", { status: 404 }); }, }); ``` ```curl Static Checkout Curl example expandable theme={null} curl --request GET \ --url 'https://example.com/api/checkout?productId=pdt_xxx' \ --header 'User-Agent: insomnia/11.2.0' \ --cookie mode=test ``` ```curl Dynamic Checkout Curl example expandable theme={null} curl --request POST \ --url https://example.com/api/checkout \ --header 'Content-Type: application/json' \ --header 'User-Agent: insomnia/11.2.0' \ --cookie mode=test \ --data '{ "billing": { "city": "Texas", "country": "US", "state": "Texas", "street": "56, hhh", "zipcode": "560000" }, "customer": { "email": "test@example.com", "name": "test" }, "metadata": {}, "payment_link": true, "product_id": "pdt_xxx", "quantity": 1, "billing_currency": "USD", "discount_code": "IKHZ23M9GQ", "return_url": "https://example.com", "trial_period_days": 10 }' ``` ```curl Checkout Session Curl example expandable theme={null} curl --request POST \ --url https://example.com/api/checkout \ --header 'Content-Type: application/json' \ --header 'User-Agent: insomnia/11.2.0' \ --cookie mode=test \ --data '{ "product_cart": [ { "product_id": "pdt_xxx", "quantity": 1 } ], "customer": { "email": "test@example.com", "name": "test" }, "return_url": "https://example.com/success" }' ``` Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal. ```typescript Bun Server Handler expandable theme={null} import { CustomerPortal } from "@dodopayments/bun"; const customerPortalHandler = CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, }); Bun.serve({ port: 3000, fetch(request) { const url = new URL(request.url); if (url.pathname === "/api/customer-portal" && request.method === "GET") { return customerPortalHandler(request); } return new Response("Not Found", { status: 404 }); }, }); ``` ```curl Customer Portal Curl example expandable theme={null} curl --request GET \ --url 'https://example.com/api/customer-portal?customer_id=cus_123&send_email=true' \ --header 'User-Agent: insomnia/11.2.0' \ --cookie mode=test ``` Use this handler to receive and process Dodo Payments webhook events securely in your Bun server. ```typescript Bun Server Handler expandable theme={null} import { Webhooks } from "@dodopayments/bun"; const webhookHandler = Webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // handle the payload }, // ... other event handlers for granular control }); Bun.serve({ port: 3000, fetch(request) { const url = new URL(request.url); if (url.pathname === "/api/webhook/dodo-payments" && request.method === "POST") { return webhookHandler(request); } return new Response("Not Found", { status: 404 }); }, }); ``` ## Checkout Route Handler Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows. * **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection. * **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs. * **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details. ### Supported Query Parameters Product identifier (e.g., ?productId=pdt\_xxx). Quantity of the product. Customer's full name. Customer's first name. Customer's last name. Customer's email address. Customer's country. Customer's address line. Customer's city. Customer's state/province. Customer's zip/postal code. Disable full name field. Disable first name field. Disable last name field. Disable email field. Disable country field. Disable address line field. Disable city field. Disable state field. Disable zip code field. Specify the payment currency (e.g., USD). Show currency selector. Specify the payment amount (e.g., 1000 for \$10.00). Show discount fields. Any query parameter starting with metadata\_ will be passed as metadata. If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response. ### Response Format Static checkout returns a JSON response with the checkout URL: ```json theme={null} { "checkout_url": "https://checkout.dodopayments.com/..." } ``` * Send parameters as a JSON body in a POST request. * Supports both one-time and recurring payments. * For a complete list of supported POST body fields, refer to: * [Request body for a One Time Payment Product](https://docs.dodopayments.com/api-reference/payments/post-payments) * [Request body for a Subscription Product](https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions) ### Response Format Dynamic checkout returns a JSON response with the checkout URL: ```json theme={null} { "checkout_url": "https://checkout.dodopayments.com/..." } ``` Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control. Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields. ### Response Format Checkout sessions return a JSON response with the checkout URL: ```json theme={null} { "checkout_url": "https://checkout.dodopayments.com/session/..." } ``` ## Customer Portal Route Handler The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Bun server application. ### Query Parameters The customer ID for the portal session (e.g., ?customer\_id=cus\_123). If set to true, sends an email to the customer with the portal link. Returns 400 if customer\_id is missing. ## Webhook Route Handler * **Method:** Only POST requests are supported. Other methods return 405. * **Signature Verification:** Verifies the webhook signature using webhookKey. Returns 401 if verification fails. * **Payload Validation:** Validated with Zod. Returns 400 for invalid payloads. * **Error Handling:** * 401: Invalid signature * 400: Invalid payload * 500: Internal error during verification * **Event Routing:** Calls the appropriate event handler based on the payload type. ### Supported Webhook Event Handlers ```typescript Typescript expandable theme={null} onPayload?: (payload: WebhookPayload) => Promise; onPaymentSucceeded?: (payload: WebhookPayload) => Promise; onPaymentFailed?: (payload: WebhookPayload) => Promise; onPaymentProcessing?: (payload: WebhookPayload) => Promise; onPaymentCancelled?: (payload: WebhookPayload) => Promise; onRefundSucceeded?: (payload: WebhookPayload) => Promise; onRefundFailed?: (payload: WebhookPayload) => Promise; onDisputeOpened?: (payload: WebhookPayload) => Promise; onDisputeExpired?: (payload: WebhookPayload) => Promise; onDisputeAccepted?: (payload: WebhookPayload) => Promise; onDisputeCancelled?: (payload: WebhookPayload) => Promise; onDisputeChallenged?: (payload: WebhookPayload) => Promise; onDisputeWon?: (payload: WebhookPayload) => Promise; onDisputeLost?: (payload: WebhookPayload) => Promise; onSubscriptionActive?: (payload: WebhookPayload) => Promise; onSubscriptionOnHold?: (payload: WebhookPayload) => Promise; onSubscriptionRenewed?: (payload: WebhookPayload) => Promise; onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise; onSubscriptionCancelled?: (payload: WebhookPayload) => Promise; onSubscriptionFailed?: (payload: WebhookPayload) => Promise; onSubscriptionExpired?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; ``` *** ## Prompt for LLM ```` You are an expert Bun developer assistant. Your task is to guide a user through integrating the @dodopayments/bun adapter into their existing Bun project. The @dodopayments/bun adapter provides handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for Bun's native server with Bun.serve(). First, install the necessary package: bun add @dodopayments/bun Here's how you should structure your response: Ask the user which functionalities they want to integrate. "Which parts of the @dodopayments/bun adapter would you like to integrate into your project? You can choose one or more of the following: 1. Checkout (static, dynamic, or session-based) 2. Customer Portal 3. Webhooks" Based on the user's selection, provide step-by-step integration instructions. For each selected functionality, show: The environment variables required The exact code to add to their server file Where to place the code in their Bun.serve() configuration Provide complete, working examples that the user can copy and paste directly into their project. Environment Variables Setup Always include instructions for setting up the .env file: DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode" DODO_PAYMENTS_RETURN_URL=your-return-url Remind the user to never commit their .env file to version control. For Checkout Integration If the user selects Checkout, ask which type they need: Static checkout (GET requests with query parameters) Dynamic checkout (POST requests with JSON body) Checkout sessions (POST requests with product cart) Provide the appropriate handler code for their selection. For Customer Portal Integration Provide a complete example showing how to integrate the customer portal handler into their Bun server. For Webhook Integration Provide a complete example showing how to integrate the webhook handler with available event handlers for granular control. Full Example If the user wants to integrate all three functionalities, provide a complete Bun server example that combines all handlers. ```typescript import { Checkout, CustomerPortal, Webhooks } from "@dodopayments/bun"; const staticCheckoutHandler = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "static", }); const sessionCheckoutHandler = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "session", }); const customerPortalHandler = CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, }); const webhookHandler = Webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY, onPaymentSucceeded: async (payload) => { console.log("Payment succeeded:", payload); // Your business logic here }, onSubscriptionActive: async (payload) => { console.log("Subscription activated:", payload); // Your business logic here }, }); Bun.serve({ port: 3000, fetch(request) { const url = new URL(request.url); // Checkout routes if (url.pathname === "/api/checkout") { if (request.method === "GET") { return staticCheckoutHandler(request); } if (request.method === "POST") { return sessionCheckoutHandler(request); } } // Customer portal route if (url.pathname === "/api/customer-portal" && request.method === "GET") { return customerPortalHandler(request); } // Webhook route if (url.pathname === "/api/webhook/dodo-payments" && request.method === "POST") { return webhookHandler(request); } return new Response("Not Found", { status: 404 }); }, }); console.log("Server running on http://localhost:3000"); Additional Guidance: • Explain that the handlers are Bun-native and work seamlessly with Bun.serve() • Highlight the use of Web Standard Request and Response objects • Mention that error handling is built-in and returns appropriate HTTP status codes • Provide links to the Dodo Payments documentation for more details ```` # Convex Component Source: https://docs.dodopayments.com/developer-resources/convex-component Learn how to integrate Dodo Payments with your Convex backend using our Convex Component. Covers checkout functions, customer portal, webhooks, and secure environment setup. Integrate Dodo Payments checkout with session-based flow. Allow customers to manage subscriptions and details. Receive and process Dodo Payments webhook events. ## Installation Run the following command in your project root: ```bash theme={null} npm install @dodopayments/convex ``` Add the Dodo Payments component to your Convex configuration: ```typescript theme={null} // convex/convex.config.ts import { defineApp } from "convex/server"; import dodopayments from "@dodopayments/convex/convex.config"; const app = defineApp(); app.use(dodopayments); export default app; ``` After editing `convex.config.ts`, run `npx convex dev` once to generate the necessary types. Set up environment variables in your Convex dashboard (**Settings** → **Environment Variables**). You can access the dashboard by running: ```bash theme={null} npx convex dashboard ``` Add the following environment variables: * `DODO_PAYMENTS_API_KEY` - Your Dodo Payments API key * `DODO_PAYMENTS_ENVIRONMENT` - Set to `test_mode` or `live_mode` * `DODO_PAYMENTS_WEBHOOK_SECRET` - Your webhook secret (required for webhook handling) Always use Convex environment variables for sensitive information. Never commit secrets to version control. ## Component Setup Examples First, create an internal query to fetch customers from your database. This will be used in the payment functions to identify customers. Before using this query, make sure to define the appropriate schema in your `convex/schema.ts` file or change the query to match your existing schema. ```typescript theme={null} // convex/customers.ts import { internalQuery } from "./_generated/server"; import { v } from "convex/values"; // Internal query to fetch customer by auth ID export const getByAuthId = internalQuery({ args: { authId: v.string() }, handler: async (ctx, { authId }) => { return await ctx.db .query("customers") .withIndex("by_auth_id", (q) => q.eq("authId", authId)) .first(); }, }); ``` ```typescript Convex Component Setup expandable theme={null} // convex/dodo.ts import { DodoPayments, DodoPaymentsClientConfig } from "@dodopayments/convex"; import { components } from "./_generated/api"; import { internal } from "./_generated/api"; export const dodo = new DodoPayments(components.dodopayments, { // This function maps your Convex user to a Dodo Payments customer // Customize it based on your authentication provider and database identify: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) { return null; // User is not logged in } // Use ctx.runQuery() to lookup customer from your database const customer = await ctx.runQuery(internal.customers.getByAuthId, { authId: identity.subject, }); if (!customer) { return null; // Customer not found in database } return { dodoCustomerId: customer.dodoCustomerId, // Replace customer.dodoCustomerId with your field storing Dodo Payments customer ID }; }, apiKey: process.env.DODO_PAYMENTS_API_KEY!, environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode", } as DodoPaymentsClientConfig); // Export the API methods for use in your app export const { checkout, customerPortal } = dodo.api(); ``` Use this function to integrate Dodo Payments checkout into your Convex app. Uses session-based checkout with full feature support. ```typescript Checkout Action expandable theme={null} // convex/payments.ts import { action } from "./_generated/server"; import { v } from "convex/values"; import { checkout } from "./dodo"; export const createCheckout = action({ args: { product_cart: v.array(v.object({ product_id: v.string(), quantity: v.number(), })), returnUrl: v.optional(v.string()), }, handler: async (ctx, args) => { try { const session = await checkout(ctx, { payload: { product_cart: args.product_cart, return_url: args.returnUrl, billing_currency: "USD", feature_flags: { allow_discount_code: true, }, }, }); if (!session?.checkout_url) { throw new Error("Checkout session did not return a checkout_url"); } return session; } catch (error) { console.error("Failed to create checkout session", error); throw new Error("Unable to create checkout session. Please try again."); } }, }); ``` Use this function to allow customers to manage their subscriptions and details via the Dodo Payments customer portal. The customer is automatically identified via the `identify` function. ```typescript Customer Portal Action expandable theme={null} // convex/payments.ts (add to existing file) import { action } from "./_generated/server"; import { v } from "convex/values"; import { customerPortal } from "./dodo"; export const getCustomerPortal = action({ args: { send_email: v.optional(v.boolean()), }, handler: async (ctx, args) => { try { const portal = await customerPortal(ctx, args); if (!portal?.portal_url) { throw new Error("Customer portal did not return a portal_url"); } return portal; } catch (error) { console.error("Failed to generate customer portal link", error); throw new Error("Unable to generate customer portal link. Please try again."); } }, }); ``` Use this handler to receive and process Dodo Payments webhook events securely in your Convex app. All webhook handlers receive the Convex `ActionCtx` as the first parameter, allowing you to use `ctx.runQuery()` and `ctx.runMutation()` to interact with your database. ```typescript Convex HTTP Action expandable theme={null} // convex/http.ts import { createDodoWebhookHandler } from "@dodopayments/convex"; import { httpRouter } from "convex/server"; import { internal } from "./_generated/api"; const http = httpRouter(); http.route({ path: "/dodopayments-webhook", method: "POST", handler: createDodoWebhookHandler({ // Handle successful payments onPaymentSucceeded: async (ctx, payload) => { console.log("🎉 Payment Succeeded!"); // Use Convex context to persist payment data await ctx.runMutation(internal.webhooks.createPayment, { paymentId: payload.data.payment_id, businessId: payload.business_id, customerEmail: payload.data.customer.email, amount: payload.data.total_amount, currency: payload.data.currency, status: payload.data.status, webhookPayload: JSON.stringify(payload), }); }, // Handle subscription activation onSubscriptionActive: async (ctx, payload) => { console.log("🎉 Subscription Activated!"); // Use Convex context to persist subscription data await ctx.runMutation(internal.webhooks.createSubscription, { subscriptionId: payload.data.subscription_id, businessId: payload.business_id, customerEmail: payload.data.customer.email, status: payload.data.status, webhookPayload: JSON.stringify(payload), }); }, // Add other event handlers as needed }), }); export default http; ``` Make sure to define the corresponding database mutations in your Convex backend for each webhook event you want to handle. For example, create a createPayment mutation to record successful payments or a createSubscription mutation to manage subscription state. ## Checkout Function The Dodo Payments Convex component uses session-based checkout, providing a secure, customizable checkout experience with pre-configured product carts and customer details. This is the recommended approach for all payment flows. ### Usage ```typescript theme={null} const result = await checkout(ctx, { payload: { product_cart: [{ product_id: "prod_123", quantity: 1 }], customer: { email: "user@example.com" }, return_url: "https://example.com/success" } }); ``` Refer [Checkout Sessions](/developer-resources/checkout-session) for more details and a complete list of supported fields. ### Response Format The checkout function returns a JSON response with the checkout URL: ```json theme={null} { "checkout_url": "https://checkout.dodopayments.com/session/..." } ``` ## Customer Portal Function The Customer Portal Function enables you to seamlessly integrate the Dodo Payments customer portal into your Convex application. ### Usage ```typescript theme={null} const result = await customerPortal(ctx, { send_email: false }); ``` ### Parameters If set to true, sends an email to the customer with the portal link. The customer is automatically identified using the `identify` function configured in your DodoPayments setup. This function should return the customer's `dodoCustomerId`. ## Webhook Handler * **Method:** Only POST requests are supported. Other methods return 405. * **Signature Verification:** Verifies the webhook signature using DODO\_PAYMENTS\_WEBHOOK\_SECRET. Returns 401 if verification fails. * **Payload Validation:** Validated with Zod. Returns 400 for invalid payloads. * **Error Handling:** * 401: Invalid signature * 400: Invalid payload * 500: Internal error during verification * **Event Routing:** Calls the appropriate event handler based on the payload type. ### Supported Webhook Event Handlers ```typescript Typescript expandable theme={null} onPayload?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onPaymentSucceeded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onPaymentFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onPaymentProcessing?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onPaymentCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onRefundSucceeded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onRefundFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDisputeOpened?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDisputeExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDisputeAccepted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDisputeCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDisputeChallenged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDisputeWon?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDisputeLost?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onSubscriptionActive?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onSubscriptionOnHold?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onSubscriptionRenewed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onSubscriptionPlanChanged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onSubscriptionCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onSubscriptionFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onSubscriptionExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; ``` ## Frontend Usage Use the checkout function from your React components with Convex hooks. ```tsx React Checkout Component expandable theme={null} import { useAction } from "convex/react"; import { api } from "../convex/_generated/api"; export function CheckoutButton() { const createCheckout = useAction(api.payments.createCheckout); const handleCheckout = async () => { try { const { checkout_url } = await createCheckout({ product_cart: [{ product_id: "prod_123", quantity: 1 }], returnUrl: "https://example.com/success" }); if (!checkout_url) { throw new Error("Missing checkout_url in response"); } window.location.href = checkout_url; } catch (error) { console.error("Failed to create checkout", error); throw new Error("Unable to create checkout. Please try again."); } }; return ; } ``` ```tsx Customer Portal Component expandable theme={null} import { useAction } from "convex/react"; import { api } from "../convex/_generated/api"; export function CustomerPortalButton() { const getPortal = useAction(api.payments.getCustomerPortal); const handlePortal = async () => { try { const { portal_url } = await getPortal({ send_email: false }); if (!portal_url) { throw new Error("Missing portal_url in response"); } window.location.href = portal_url; } catch (error) { console.error("Unable to open customer portal", error); alert("We couldn't open the customer portal. Please try again."); } }; return ; } ``` *** ## Prompt for LLM ``` You are an expert Convex developer assistant. Your task is to guide a user through integrating the @dodopayments/convex component into their existing Convex application. The @dodopayments/convex adapter provides a Convex component for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, built using the official Convex component architecture pattern. First, install the necessary package: npm install @dodopayments/convex Here's how you should structure your response: 1. Ask the user which functionalities they want to integrate. "Which parts of the @dodopayments/convex component would you like to integrate into your project? You can choose one or more of the following: - Checkout Function (for handling product checkouts) - Customer Portal Function (for managing customer subscriptions/details) - Webhook Handler (for receiving Dodo Payments webhook events) - All (integrate all three)" 2. Based on the user's selection, provide detailed integration steps for each chosen functionality. If Checkout Function is selected: Purpose: This function handles session-based checkout flows and returns checkout URLs for programmatic handling. Integration Steps: Step 1: Add the component to your Convex configuration. // convex/convex.config.ts import { defineApp } from "convex/server"; import dodopayments from "@dodopayments/convex/convex.config"; const app = defineApp(); app.use(dodopayments); export default app; Step 2: Guide the user to set up environment variables in the Convex dashboard. Instruct them to open the dashboard by running: npx convex dashboard Then add the required environment variables (e.g., DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_ENVIRONMENT, DODO_PAYMENTS_WEBHOOK_SECRET) in **Settings → Environment Variables**. Do not use .env files for backend functions. Step 3: Create an internal query to fetch customers from your database. Note: Ensure the user has appropriate schema defined in their convex/schema.ts file or modify the query to match their existing schema. // convex/customers.ts import { internalQuery } from "./_generated/server"; import { v } from "convex/values"; // Internal query to fetch customer by auth ID export const getByAuthId = internalQuery({ args: { authId: v.string() }, handler: async (ctx, { authId }) => { return await ctx.db .query("customers") .withIndex("by_auth_id", (q) => q.eq("authId", authId)) .first(); }, }); Step 4: Create your payment functions file. // convex/dodo.ts import { DodoPayments, DodoPaymentsClientConfig } from "@dodopayments/convex"; import { components } from "./_generated/api"; import { internal } from "./_generated/api"; export const dodo = new DodoPayments(components.dodopayments, { // This function maps your Convex user to a Dodo Payments customer // Customize it based on your authentication provider and user database identify: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) { return null; // User is not logged in } // Use ctx.runQuery() to lookup customer from your database const customer = await ctx.runQuery(internal.customers.getByAuthId, { authId: identity.subject, }); if (!customer) { return null; // Customer not found in database } return { dodoCustomerId: customer.dodoCustomerId, // Replace customer.dodoCustomerId with your field storing Dodo Payments customer ID }; }, apiKey: process.env.DODO_PAYMENTS_API_KEY!, environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode", } as DodoPaymentsClientConfig); // Export the API methods for use in your app export const { checkout, customerPortal } = dodo.api(); Step 5: Create actions that use the checkout function. // convex/payments.ts import { action } from "./_generated/server"; import { v } from "convex/values"; import { checkout } from "./dodo"; // Checkout session with full feature support export const createCheckout = action({ args: { product_cart: v.array(v.object({ product_id: v.string(), quantity: v.number(), })), returnUrl: v.optional(v.string()), }, handler: async (ctx, args) => { return await checkout(ctx, { payload: { product_cart: args.product_cart, return_url: args.returnUrl, billing_currency: "USD", feature_flags: { allow_discount_code: true, }, }, }); }, }); Step 6: Use in your frontend. // Your frontend component import { useAction } from "convex/react"; import { api } from "../convex/_generated/api"; export function CheckoutButton() { const createCheckout = useAction(api.payments.createCheckout); const handleCheckout = async () => { const { checkout_url } = await createCheckout({ product_cart: [{ product_id: "prod_123", quantity: 1 }], }); window.location.href = checkout_url; }; return ; } Configuration Details: - `checkout()`: Checkout session with full feature support using session checkout. - Returns: `{"checkout_url": "https://checkout.dodopayments.com/..."}` For complete API documentation, refer to: - Checkout Sessions: https://docs.dodopayments.com/developer-resources/checkout-session - One-time Payments: https://docs.dodopayments.com/api-reference/payments/post-payments - Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions If Customer Portal Function is selected: Purpose: This function allows customers to manage their subscriptions and payment methods. The customer is automatically identified via the `identify` function. Integration Steps: Follow Steps 1-4 from the Checkout Function section, then: Step 5: Create a customer portal action. // convex/payments.ts (add to existing file) import { action } from "./_generated/server"; import { v } from "convex/values"; import { customerPortal } from "./dodo"; export const getCustomerPortal = action({ args: { send_email: v.optional(v.boolean()), }, handler: async (ctx, args) => { try { const portal = await customerPortal(ctx, args); if (!portal?.portal_url) { throw new Error("Customer portal did not return a portal_url"); } return portal; } catch (error) { console.error("Failed to generate customer portal link", error); throw new Error("Unable to generate customer portal link. Please retry."); } }, }); Step 6: Use in your frontend. // Your frontend component import { useAction } from "convex/react"; import { api } from "../convex/_generated/api"; export function CustomerPortalButton() { const getPortal = useAction(api.payments.getCustomerPortal); const handlePortal = async () => { const { portal_url } = await getPortal({ send_email: false }); window.location.href = portal_url; }; return ; } Configuration Details: - Requires authenticated user (via `identify` function). - Customer identification is handled automatically by the `identify` function. - `send_email`: Optional boolean to send portal link via email. If Webhook Handler is selected: Purpose: This handler processes incoming webhook events from Dodo Payments, allowing your application to react to events like successful payments or subscription changes. Integration Steps: Step 1: Add the webhook secret to your environment variables in the Convex dashboard. You can open the dashboard by running: Guide the user to open the Convex dashboard by running: npx convex dashboard In the dashboard, go to **Settings → Environment Variables** and add: - `DODO_PAYMENTS_WEBHOOK_SECRET=whsec_...` Do not use .env files for backend functions; always set secrets in the Convex dashboard. Step 2: Create a file `convex/http.ts`: // convex/http.ts import { createDodoWebhookHandler } from "@dodopayments/convex"; import { httpRouter } from "convex/server"; import { internal } from "./_generated/api"; const http = httpRouter(); http.route({ path: "/dodopayments-webhook", method: "POST", handler: createDodoWebhookHandler({ // Handle successful payments onPaymentSucceeded: async (ctx, payload) => { console.log("🎉 Payment Succeeded!"); // Use Convex context to persist payment data await ctx.runMutation(internal.webhooks.createPayment, { paymentId: payload.data.payment_id, businessId: payload.business_id, customerEmail: payload.data.customer.email, amount: payload.data.total_amount, currency: payload.data.currency, status: payload.data.status, webhookPayload: JSON.stringify(payload), }); }, // Handle subscription activation onSubscriptionActive: async (ctx, payload) => { console.log("🎉 Subscription Activated!"); // Use Convex context to persist subscription data await ctx.runMutation(internal.webhooks.createSubscription, { subscriptionId: payload.data.subscription_id, businessId: payload.business_id, customerEmail: payload.data.customer.email, status: payload.data.status, webhookPayload: JSON.stringify(payload), }); }, // Add other event handlers as needed }), }); export default http; Note: Make sure to define the corresponding database mutations in your Convex backend for each webhook event you want to handle. For example, create a `createPayment` mutation to record successful payments or a `createSubscription` mutation to manage subscription state. Now, you can set the webhook endpoint URL in your Dodo Payments dashboard to `https:///dodopayments-webhook`. Environment Variable Setup: Set up the following environment variables in your Convex dashboard if you haven't already (Settings → Environment Variables): - `DODO_PAYMENTS_API_KEY` - Your Dodo Payments API key - `DODO_PAYMENTS_ENVIRONMENT` - Set to `test_mode` or `live_mode` - `DODO_PAYMENTS_WEBHOOK_SECRET` - Your webhook secret (required for webhook handling) Usage in your component configuration: apiKey: process.env.DODO_PAYMENTS_API_KEY environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode" Important: Never commit sensitive environment variables directly into your code. Always use Convex environment variables for all sensitive information. If the user needs assistance setting up environment variables or deployment, ask them about their specific setup and provide guidance accordingly. Run `npx convex dev` after setting up the component to generate the necessary types. ``` # Official SDKs Overview Source: https://docs.dodopayments.com/developer-resources/dodo-payments-sdks Official SDKs for TypeScript, Python, PHP, Go, Ruby, Java, Kotlin, and C# to integrate Dodo Payments into your applications Dodo Payments provides official SDKs for multiple programming languages, each designed with language-specific best practices and modern features for seamless payment integration. Always use the latest SDK version to access the newest features and improvements. Check your package manager for updates regularly to ensure you have access to all Dodo Payments capabilities. ## Available SDKs Choose the SDK that matches your tech stack: Type-safe integration for TypeScript and Node.js with promise-based API and auto-pagination Pythonic interface with async/await support for Python 3.7+ applications PSR-4 compliant SDK for modern PHP 8.1+ applications Idiomatic Go interface with context support and strong typing Elegant Ruby interface following Ruby conventions and best practices Robust and thread-safe SDK for Java 8+ with Maven and Gradle support Modern Kotlin SDK with coroutines, null safety, and extension functions Type-safe SDK for .NET 8+ with async Task-based API (Beta) Native mobile SDK for building secure payment experiences in iOS and Android apps Command-line interface for interacting with the API from your terminal ## Quick Start Get started with any SDK in minutes: Use your language's package manager to install the SDK ```bash theme={null} npm install dodopayments ``` ```bash theme={null} pip install dodopayments ``` ```bash theme={null} composer require dodopayments/client ``` ```bash theme={null} go get github.com/dodopayments/dodopayments-go ``` Configure the client with your API key ```typescript theme={null} import DodoPayments from 'dodopayments'; const client = new DodoPayments({ bearerToken: 'your_api_key' }); ``` ```python theme={null} from dodopayments import DodoPayments client = DodoPayments(bearer_token="your_api_key") ``` ```php theme={null} use Dodopayments\Client; $client = new Client(bearerToken: 'your_api_key'); ``` ```go theme={null} import "github.com/dodopayments/dodopayments-go" client := dodopayments.NewClient(option.WithBearerToken("your_api_key")) ``` Always store your API keys securely using environment variables. Never commit them to version control. Create a checkout session or payment You're now ready to process payments! Visit the individual SDK pages for detailed guides and examples. ## Key Features All SDKs share these core capabilities: * **Type Safety**: Strong typing for compile-time safety and better IDE support * **Error Handling**: Comprehensive exception handling with detailed error messages * **Authentication**: Simple API key authentication with environment variable support * **Async Support**: Modern async/await patterns where applicable * **Auto-Pagination**: Automatic pagination for list responses * **Usage-Based Billing**: Built-in support for tracking and ingesting usage events * **Testing**: Full sandbox environment support for development and testing ## React Native SDK For mobile applications, we provide a dedicated React Native SDK: Build secure payment experiences for iOS and Android apps with native UI components and simplified payment data collection **Features:** * Native UI components for Android and iOS * Simplified security for collecting sensitive payment data * Support for multiple payment methods * Seamless integration with React Native apps Apple Pay, Google Pay, Cash App, and UPI are not currently supported in the React Native SDK. Support for these payment methods is planned for future releases. ## Command-Line Interface For terminal-based workflows and automation: Auto-generated command-line interface with support for all 78 API endpoints **Features:** * Resource-based command structure for intuitive usage * Multiple output formats (JSON, YAML, pretty, interactive) * Shell completion for bash, zsh, and fish * Perfect for scripting and CI/CD automation ```bash theme={null} # Quick example dodopayments payments list --format json | jq '.data[] | {id, amount}' ``` ## Migration from Node.js SDK We migrated from the Node.js SDK to the new TypeScript SDK. If you're using the legacy Node.js SDK, see the [migration guide](https://github.com/dodopayments/dodopayments-typescript/blob/main/MIGRATION.md) to update your integration. ## Framework Adapters Integrate in under 10 lines of code with our framework adapters. Choose from our recommended frameworks or explore all supported options. ### Recommended Frameworks React-based full-stack framework with App Router support Authentication framework with built-in integrations Open source Firebase alternative with Postgres and Auth Backend-as-a-Service with real-time capabilities ## Getting Help Need assistance with any SDK? * **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time help * **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com) * **GitHub**: Open an issue on the respective SDK repository * **Documentation**: Visit our [API reference](/api-reference/introduction) ## Contributing We welcome contributions to all our SDKs! Each repository has a `CONTRIBUTING.md` file with guidelines for: * Reporting bugs * Requesting features * Submitting pull requests * Running tests locally * Code style and conventions Visit the individual SDK pages to access their GitHub repositories and contribution guidelines. # Expo Boilerplate Source: https://docs.dodopayments.com/developer-resources/expo-boilerplate Build React Native mobile apps with Dodo Payments using our Expo boilerplate. Includes checkout integration, payment handling, and TypeScript support. Complete Expo + React Native + Dodo Payments boilerplate ## Overview A production-ready boilerplate for integrating Dodo Payments into Expo/React Native applications. This template includes checkout session creation, payment handling, and proper TypeScript configuration to help you start accepting payments in your mobile app quickly. This boilerplate uses Expo SDK with TypeScript and includes example implementations for checkout sessions and payment processing.