# 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; onSubscriptionUpdated?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise; onDunningStarted?: (payload: WebhookPayload) => Promise; onDunningRecovered?: (payload: WebhookPayload) => Promise; onCreditAdded?: (payload: WebhookPayload) => Promise; onCreditDeducted?: (payload: WebhookPayload) => Promise; onCreditExpired?: (payload: WebhookPayload) => Promise; onCreditRolledOver?: (payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise; onCreditOverageCharged?: (payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (payload: WebhookPayload) => Promise; onCreditBalanceLow?: (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 onSubscriptionUpdated?: (payload: WebhookPayload) => Promise onLicenseKeyCreated?: (payload: WebhookPayload) => Promise onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise onDunningStarted?: (payload: WebhookPayload) => Promise onDunningRecovered?: (payload: WebhookPayload) => Promise onCreditAdded?: (payload: WebhookPayload) => Promise onCreditDeducted?: (payload: WebhookPayload) => Promise onCreditExpired?: (payload: WebhookPayload) => Promise onCreditRolledOver?: (payload: WebhookPayload) => Promise onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise onCreditOverageCharged?: (payload: WebhookPayload) => Promise onCreditManualAdjustment?: (payload: WebhookPayload) => Promise onCreditBalanceLow?: (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 ``` # 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; onSubscriptionUpdated?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise; onDunningStarted?: (payload: WebhookPayload) => Promise; onDunningRecovered?: (payload: WebhookPayload) => Promise; onCreditAdded?: (payload: WebhookPayload) => Promise; onCreditDeducted?: (payload: WebhookPayload) => Promise; onCreditExpired?: (payload: WebhookPayload) => Promise; onCreditRolledOver?: (payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise; onCreditOverageCharged?: (payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (payload: WebhookPayload) => Promise; onCreditBalanceLow?: (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) * getCustomerParams (optional): Function that receives the BetterAuth `User` and returns extra fields to attach to the DodoPayments customer on creation and update (e.g. `metadata`, `phone_number`) ```typescript theme={null} dodopayments({ client: dodoPayments, createCustomerOnSignUp: true, use: [portal()], getCustomerParams: (user) => ({ metadata: { userId: user.id }, phone_number: user.phoneNumber ?? null, }), }) ``` * 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 // Optional: attach metadata or phone_number to DodoPayments customer records // getCustomerParams: (user) => ({ // metadata: { userId: user.id }, // phone_number: user.phoneNumber ?? null, // }), }), ], }); 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); }, onSubscriptionUpdated: async (payload) => { console.log("Subscription updated:", payload); }, // License key event handlers onLicenseKeyCreated: async (payload) => { console.log("License key created:", payload); }, // Abandoned checkout event handlers onAbandonedCheckoutDetected: async (payload) => { console.log("Abandoned checkout detected:", payload); }, onAbandonedCheckoutRecovered: async (payload) => { console.log("Abandoned checkout recovered:", payload); }, // Dunning event handlers onDunningStarted: async (payload) => { console.log("Dunning started:", payload); }, onDunningRecovered: async (payload) => { console.log("Dunning recovered:", payload); }, // Credit event handlers onCreditAdded: async (payload) => { console.log("Credit added:", payload); }, onCreditDeducted: async (payload) => { console.log("Credit deducted:", payload); }, onCreditExpired: async (payload) => { console.log("Credit expired:", payload); }, onCreditRolledOver: async (payload) => { console.log("Credit rolled over:", payload); }, onCreditRolloverForfeited: async (payload) => { console.log("Credit rollover forfeited:", payload); }, onCreditOverageCharged: async (payload) => { console.log("Credit overage charged:", payload); }, onCreditManualAdjustment: async (payload) => { console.log("Credit manual adjustment:", payload); }, onCreditBalanceLow: async (payload) => { console.log("Credit balance low:", 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 - onSubscriptionUpdated: Subscription was updated - onLicenseKeyCreated: License key was created - onAbandonedCheckoutDetected: Abandoned checkout was detected - onAbandonedCheckoutRecovered: Abandoned checkout was recovered - onDunningStarted: Dunning process started - onDunningRecovered: Dunning process recovered - onCreditAdded: Credits were added - onCreditDeducted: Credits were deducted - onCreditExpired: Credits expired - onCreditRolledOver: Credits were rolled over - onCreditRolloverForfeited: Credit rollover was forfeited - onCreditOverageCharged: Credit overage was charged - onCreditManualAdjustment: Manual credit adjustment was made - onCreditBalanceLow: Credit balance is low 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 13. Use getCustomerParams to attach metadata or phone_number to DodoPayments customer records — the function receives the BetterAuth User object and runs on every customer creation and update ``` # Build with AI Coding Agents Source: https://docs.dodopayments.com/developer-resources/build-with-ai-coding-agents Use the Dodo Agent Plugin to build integrations with Claude Code, Codex CLI, Cursor, and OpenCode — MCP servers and skills in one install. The Dodo Agent Plugin wires two MCP servers and eight integration skills into your AI coding agent in a single install. It works with **Claude Code**, **Codex CLI**, **Cursor**, and **OpenCode** — and the MCP servers and skills CLI work with any MCP-compatible client. **Three primitives, one plugin.** The Agent Plugin bundles everything you need: * **API MCP server** — live access to payments, subscriptions, customers, products, refunds, licenses, and usage. Authenticates via browser OAuth (no local keys required). * **Knowledge MCP server** — semantic search across all Dodo Payments documentation. No credentials needed. * **Eight agent skills** — cheat sheets your agent loads on demand for checkout, subscriptions, webhooks, usage-based billing, credit-based billing, license keys, BillingSDK, and best practices. ## Install the plugin Choose your coding agent below. The install adds both MCP servers and all eight skills automatically. Install from the marketplace: ```bash theme={null} claude plugins marketplace add dodopayments/dodo-agent-plugin claude plugins install dodopayments@dodopayments ``` The API MCP server uses browser OAuth by default — no keys required at install time. The first time your agent calls a Dodo tool, you'll be prompted to sign in. Source code, configuration options, and local development instructions Codex installs plugins in two steps: register the marketplace from your shell, then install the plugin from inside the Codex TUI. ```bash theme={null} codex plugin marketplace add dodopayments/dodo-agent-plugin ``` Open Codex and run the `/plugins` slash command: ```bash theme={null} codex ``` Then type `/plugins`, switch to the **Dodo Payments** marketplace, select the **dodopayments** plugin, and choose **Install plugin**. Both MCP servers and all eight skills are registered automatically once the plugin is installed. Codex CLI does not have a `codex plugin install` subcommand — plugin installation always happens through the in-TUI `/plugins` flow. See the [official Codex plugins docs](https://developers.openai.com/codex/plugins). If you previously added the marketplace and the plugin doesn't appear under `/plugins`, refresh it: ```bash theme={null} codex plugin marketplace upgrade dodopayments ``` Manual install — clone the repo into Cursor's local plugins directory: ```bash theme={null} git clone https://github.com/dodopayments/dodo-agent-plugin.git ~/.cursor/plugins/local/dodo-agent-plugin ``` Restart Cursor. The plugin loads skills from `.claude/skills/` (via Cursor's Claude Code compatibility layer) and MCP servers from `.mcp.json`. Cursor marketplace support is coming. For now, use the manual install above. OpenCode distributes via npm. Add the plugin to your `opencode.json`: ```json theme={null} { "$schema": "https://opencode.ai/config.json", "plugin": ["@dodopayments/opencode-plugin"] } ``` Restart OpenCode. Both MCP servers (`dodopayments-api`, `dodo-knowledge`) are registered automatically via the plugin's config hook, and the eight skills are auto-discovered from the installed package. No manual `mcp` block required. Using a different agent? The [MCP Server](/developer-resources/mcp-server) and [Agent Skills](/developer-resources/agent-skills) guides cover Cursor, Claude Desktop, VS Code, Windsurf, Cline, Zed, and any MCP-compatible client. ## What you get Once the plugin is installed, your agent has access to two MCP servers and eight skills. ### MCP servers | Server | Purpose | Auth | | ------------------ | ---------------------------------------------------------------------------------------- | --------------- | | `dodopayments-api` | Live API access — payments, subscriptions, customers, products, refunds, licenses, usage | OAuth (browser) | | `dodo-knowledge` | Semantic search across all Dodo Payments documentation | None | Both servers are wired through `mcp-remote` so they run in any MCP-compatible client. ### Agent skills | Skill | Description | | -------------------------- | -------------------------------------------------------------------- | | `best-practices` | Comprehensive guide to integrating Dodo Payments with best practices | | `checkout-integration` | Creating checkout sessions and payment flows | | `subscription-integration` | Implementing subscription billing flows | | `webhook-integration` | Setting up and handling webhooks for payment events | | `usage-based-billing` | Implementing metered billing with events and meters | | `credit-based-billing` | Credit entitlements, balances, and metered credit deduction | | `license-keys` | Managing license keys for digital products | | `billing-sdk` | Using BillingSDK React components | Skills load automatically — your agent picks the right one when it detects a relevant task. See the [Agent Skills documentation](/developer-resources/agent-skills) for the full list and individual installation. ### Try this prompt first Once the plugin is active, try: ``` Set up Dodo Payments webhook handlers in my Next.js app for payment.succeeded and subscription.active events. ``` Your agent will load the `webhook-integration` skill, use the `dodo-knowledge` MCP to pull the latest payload shapes, and write a handler with signature verification following the Standard Webhooks spec. ## Other supported agents The Agent Plugin covers Claude Code, Codex CLI, Cursor, and OpenCode. If you use a different agent, connect to Dodo Payments through the MCP servers and skills CLI: | Agent | Fastest path | Also supports | | ------------------------ | --------------------------------------------------- | ----------------------------------------------- | | **Claude Code** | Agent Plugin (one command) | MCP server, individual skills | | **Codex CLI** | Agent Plugin (one command) | MCP server | | **Cursor** | Agent Plugin (git clone) | MCP server config, skills CLI | | **OpenCode** | Agent Plugin (npm) | MCP server config, skills CLI | | GitHub Copilot (VS Code) | [MCP Server guide](/developer-resources/mcp-server) | [Skills CLI](/developer-resources/agent-skills) | | Claude Desktop | [MCP Server guide](/developer-resources/mcp-server) | — | | Windsurf | [MCP Server guide](/developer-resources/mcp-server) | [Skills CLI](/developer-resources/agent-skills) | | Cline / Zed / others | [MCP Server guide](/developer-resources/mcp-server) | [Skills CLI](/developer-resources/agent-skills) | ## Docs built for agents Every Dodo Payments documentation page is available in a format optimized for AI consumption: * **Full docs index**: [`docs.dodopayments.com/llms.txt`](https://docs.dodopayments.com/llms.txt) — serves the complete documentation index for context ingestion. * **Plain markdown**: Append `.md` to any documentation URL to get the raw markdown version (e.g., `/api-reference/introduction.md`). * **Source repository**: [`github.com/dodopayments/dodo-docs`](https://github.com/dodopayments/dodo-docs) — clone for offline indexing. ## What your agent can do With the plugin installed, your coding agent can: * **Create checkout sessions and payment links** — [One-time payments](/features/one-time-payment-products) and [subscriptions](/features/subscription) * **Stand up subscription and usage-based billing end-to-end** — [Subscriptions](/features/subscription), [Usage-based billing](/features/usage-based-billing), [Credit-based billing](/features/credit-based-billing) * **Generate Standard Webhooks–compliant handlers** with signature verification — [Webhooks](/developer-resources/webhooks) * **Wire BillingSDK React components** for pricing tables and subscription management — [BillingSDK](/developer-resources/billingsdk) * **Author license-key flows** for digital products — [License keys](/features/license-keys) * **Implement credit-based billing** with entitlements, balances, rollover, and overage — [Credits](/features/credit-based-billing) ## Security and best practices **Never commit production API keys.** Use [test mode](/miscellaneous/test-mode-vs-live-mode) during development. The Agent Plugin uses browser OAuth by default — only switch to local API keys if your workflow requires it. * **Use test mode first.** Sandbox your integration with `dodo_test_...` keys before going live. See [Test Mode vs Live Mode](/miscellaneous/test-mode-vs-live-mode). * **OAuth is the default.** The Agent Plugin authenticates via browser OAuth (no local secrets). Only use API-key mode if you need it — see the Configure section below. * **Review agent-generated code.** Always verify webhook handlers include signature verification following the [Standard Webhooks spec](https://standardwebhooks.com/). ## Configure with an API key By default, the Agent Plugin uses the remote MCP server with browser OAuth — no local credentials needed. If your workflow requires a local API key (e.g., CI environments, headless servers), you can switch to stdio mode. Open `/plugins` in Claude Code, select **Dodo Payments**, and choose **Configure options**. Fill in: * `dodo_api_key` — your `dodo_test_...` or `dodo_live_...` key * `dodo_webhook_key` — your webhook signing secret * `dodo_environment` — `test_mode` or `live_mode` Then edit `.mcp.json` to point `dodopayments-api` at the local stdio server: ```json theme={null} { "mcpServers": { "dodopayments-api": { "type": "stdio", "command": "npx", "args": ["-y", "dodopayments-mcp@latest"], "env": { "DODO_PAYMENTS_API_KEY": "${user_config.dodo_api_key}", "DODO_PAYMENTS_WEBHOOK_KEY": "${user_config.dodo_webhook_key}", "DODO_PAYMENTS_ENVIRONMENT": "${user_config.dodo_environment}" } } } } ``` Run `/reload-plugins` to apply changes to your current session. Declare `dodopayments-api` yourself in `opencode.json` — your entry wins over the plugin's default remote server: ```json theme={null} { "$schema": "https://opencode.ai/config.json", "plugin": ["@dodopayments/opencode-plugin"], "mcp": { "dodopayments-api": { "type": "local", "command": ["npx", "-y", "dodopayments-mcp@latest"], "environment": { "DODO_PAYMENTS_API_KEY": "dodo_test_...", "DODO_PAYMENTS_WEBHOOK_KEY": "whsec_...", "DODO_PAYMENTS_ENVIRONMENT": "test_mode" }, "enabled": true } } } ``` Restart OpenCode to apply. ## Next steps Full reference for both MCP servers — all supported clients, configuration, and available tools Individual skill installation, skill reference, and per-agent setup instructions AI-powered billing assistant for VS Code, Cursor, and Windsurf — ask, build, and plan in your editor Complete OpenAPI reference for all Dodo Payments endpoints # 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; onSubscriptionUpdated?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise; onDunningStarted?: (payload: WebhookPayload) => Promise; onDunningRecovered?: (payload: WebhookPayload) => Promise; onCreditAdded?: (payload: WebhookPayload) => Promise; onCreditDeducted?: (payload: WebhookPayload) => Promise; onCreditExpired?: (payload: WebhookPayload) => Promise; onCreditRolledOver?: (payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise; onCreditOverageCharged?: (payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (payload: WebhookPayload) => Promise; onCreditBalanceLow?: (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; onSubscriptionUpdated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDunningStarted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onDunningRecovered?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onCreditAdded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onCreditDeducted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onCreditExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onCreditRolledOver?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onCreditOverageCharged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise; onCreditBalanceLow?: (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. # Express Adaptor Source: https://docs.dodopayments.com/developer-resources/express-adaptor Learn how to integrate Dodo Payments with your Express App Router project using our Express Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup. Integrate Dodo Payments checkout into your Express 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/express ``` 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 Use this handler to integrate Dodo Payments checkout into your Express app. Supports static (GET), dynamic (POST), and session (POST) payment flows. ```typescript Express Route Handler expandable theme={null} import { checkoutHandler } from '@dodopayments/express'; app.get('/api/checkout', checkoutHandler({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "static" })) app.post('/api/checkout', checkoutHandler({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "dynamic" })) app.post('/api/checkout', checkoutHandler({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "session" })) ``` ```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 Express Route Handler expandable theme={null} import { CustomerPortal } from "@dodopayments/express"; app.get('/api/customer-portal', CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.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 Express app. ```typescript Express Route Handler expandable theme={null} import { Webhooks } from "@dodopayments/express"; app.post('/api/webhook',Webhooks({ webhookKey: process.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 Express 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; onSubscriptionUpdated?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise; onDunningStarted?: (payload: WebhookPayload) => Promise; onDunningRecovered?: (payload: WebhookPayload) => Promise; onCreditAdded?: (payload: WebhookPayload) => Promise; onCreditDeducted?: (payload: WebhookPayload) => Promise; onCreditExpired?: (payload: WebhookPayload) => Promise; onCreditRolledOver?: (payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise; onCreditOverageCharged?: (payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (payload: WebhookPayload) => Promise; onCreditBalanceLow?: (payload: WebhookPayload) => Promise; ``` *** ## Prompt for LLM ``` You are an expert Express.js developer assistant. Your task is to guide a user through integrating the @dodopayments/express adapter into their existing Express.js project. The @dodopayments/express adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed to plug directly into an Express app. First, install the necessary package. Use the package manager appropriate for the user's project (npm, yarn, or bun): npm install @dodopayments/express --- Here's how you should structure your response: 1. Ask the user which functionalities they want to integrate. "Which parts of the @dodopayments/express 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)" --- 2. Based on the user's selection, provide detailed integration steps for each chosen functionality. --- **If Checkout Route Handler is selected:** **Purpose**: This handler manages different types of checkout flows. All checkout types (static, dynamic, and sessions) return JSON responses with checkout URLs for programmatic handling. **Integration**: Create routes in your Express app for static (GET), dynamic (POST), and checkout sessions (POST). import { checkoutHandler } from '@dodopayments/express'; app.get('/api/checkout', checkoutHandler({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "static" })); app.post('/api/checkout', checkoutHandler({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "dynamic" })); // For checkout sessions app.post('/api/checkout', checkoutHandler({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "session" })); Config Options: bearerToken: Your Dodo Payments API key (recommended to be stored in DODO_PAYMENTS_API_KEY env variable). returnUrl (optional): URL to redirect the user after successful checkout. environment: "test_mode" or "live_mode" type: "static" (GET), "dynamic" (POST), or "session" (POST) GET (static checkout) expects query parameters: productId (required) quantity, customer fields (fullName, email, etc.), and metadata (metadata_*) are optional. Returns: {"checkout_url": "https://checkout.dodopayments.com/..."} POST (dynamic checkout) expects a JSON body with payment details (one-time or subscription). Reference the docs for the full POST schema: One-time payments: https://docs.dodopayments.com/api-reference/payments/post-payments Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions Returns: {"checkout_url": "https://checkout.dodopayments.com/..."} POST (checkout sessions) - (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 If Customer Portal Route Handler is selected: Purpose: This route allows customers to manage their subscriptions via the Dodo Payments portal. Integration: import { CustomerPortal } from "@dodopayments/express"; app.get('/api/customer-portal', CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, })); Query Parameters: customer_id (required): e.g., ?customer_id=cus_123 send_email (optional): if true, customer is emailed the portal link Returns 400 if customer_id is missing. If Webhook Route Handler is selected: Purpose: Processes incoming webhook events from Dodo Payments to trigger events in your app. Integration: import { Webhooks } from "@dodopayments/express"; app.post('/api/webhook', Webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // Handle generic payload }, // You can also provide fine-grained handlers for each event type below })); Features: Only POST method is allowed — others return 405 Signature verification is performed using webhookKey. Returns 401 if invalid. Zod-based payload validation. Returns 400 if invalid schema. All handlers are async functions. Supported Webhook Event Handlers: You may pass in any of the following handlers: onPayload onPaymentSucceeded onPaymentFailed onPaymentProcessing onPaymentCancelled onRefundSucceeded onRefundFailed onDisputeOpened, onDisputeExpired, onDisputeAccepted, onDisputeCancelled, onDisputeChallenged, onDisputeWon, onDisputeLost onSubscriptionActive, onSubscriptionOnHold, onSubscriptionRenewed, onSubscriptionPlanChanged, onSubscriptionCancelled, onSubscriptionFailed, onSubscriptionExpired, onSubscriptionUpdated onLicenseKeyCreated onAbandonedCheckoutDetected, onAbandonedCheckoutRecovered onDunningStarted, onDunningRecovered onCreditAdded, onCreditDeducted, onCreditExpired, onCreditRolledOver, onCreditRolloverForfeited, onCreditOverageCharged, onCreditManualAdjustment, onCreditBalanceLow Environment Variable Setup: Make sure to define these environment variables in your project: 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 Use these inside your code as: process.env.DODO_PAYMENTS_API_KEY process.env.DODO_PAYMENTS_WEBHOOK_SECRET Security Note: Do NOT commit secrets to version control. Use .env files locally and secrets managers in deployment environments (e.g., AWS, Vercel, Heroku, etc.). ``` # Fastify Adaptor Source: https://docs.dodopayments.com/developer-resources/fastify-adaptor Learn how to integrate Dodo Payments with your Fastify App Router project using our NextJS Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup. Integrate Dodo Payments checkout into your Fastify 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/fastify ``` Create a .env file in your project root: ```env expandable theme={null} DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"" ``` Never commit your .env file or secrets to version control. ## Route Handler Examples All examples assume you are using the Fastify App Router. Use this handler to integrate Dodo Payments checkout into your Fastify app. Supports static (GET), dynamic (POST), and session (POST) payment flows. ```typescript Fastify Route Handler expandable theme={null} // route.ts import { Checkout } from '@dodopayments/fastify'; import Fastify from 'fastify' const fastify = Fastify({}) const checkoutGet = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'static' }); const checkoutPost = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'dynamic' }); const checkoutSession = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'session' }); fastify.get('/api/checkout', checkoutGet.getHandler); fastify.post('/api/checkout', checkoutPost.postHandler); fastify.post('/api/checkout-session', checkoutSession.postHandler); ``` ```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-session \ --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 Fastify Route Handler expandable theme={null} // route.ts import { CustomerPortal } from "@dodopayments/fastify"; import Fastify from 'fastify' const fastify = Fastify({}) const customerPortalHandler = CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT }); fastify.get('/api/customer-portal', customerPortalHandler); ``` ```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 Fastify app. ```typescript Fastify Route Handler expandable theme={null} // route.ts import Fastify from 'fastify' import { Webhooks } from '@dodopayments/fastify' const fastify = Fastify({}) fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { done(null, body) }) fastify.post('/api/webhooks', Webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // Handle Payload Here console.log(payload) } })); ``` ## 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 experiences that handle 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 Fastify 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; onSubscriptionUpdated?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise; onDunningStarted?: (payload: WebhookPayload) => Promise; onDunningRecovered?: (payload: WebhookPayload) => Promise; onCreditAdded?: (payload: WebhookPayload) => Promise; onCreditDeducted?: (payload: WebhookPayload) => Promise; onCreditExpired?: (payload: WebhookPayload) => Promise; onCreditRolledOver?: (payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise; onCreditOverageCharged?: (payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (payload: WebhookPayload) => Promise; onCreditBalanceLow?: (payload: WebhookPayload) => Promise; ``` *** ## Prompt for LLM ``` You are an expert Fastify developer assistant. Your task is to guide a user through integrating the @dodopayments/fastify adapter into their existing Fastify project. The @dodopayments/fastify adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed to plug directly into an Fastify app. First, install the necessary package. Use the package manager appropriate for the user's project (npm, yarn, or bun): npm install @dodopayments/fastify --- Here's how you should structure your response: 1. Ask the user which functionalities they want to integrate. "Which parts of the @dodopayments/fastify 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)" --- 2. 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. **Integration**: Create two routes in your Fastify app — one for static (GET) and one for dynamic (POST) checkout. import { Checkout } from '@dodopayments/fastify'; import Fastify from 'fastify' const fastify = Fastify({}) const checkoutGet = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'static' }); const checkoutPost = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'dynamic' }); const checkoutSession = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'session' }); fastify.get('/api/checkout', checkoutGet.getHandler); fastify.post('/api/checkout', checkoutPost.postHandler); fastify.post('/api/checkout-session', checkoutSession.postHandler); Config Options: bearerToken: Your Dodo Payments API key (recommended to be stored in DODO_PAYMENTS_API_KEY env variable). returnUrl (optional): URL to redirect the user after successful checkout. environment: "test_mode" or "live_mode" type: "static" (GET), "dynamic" (POST), or "session" (POST) GET (static checkout) expects query parameters: productId (required) quantity, customer fields (fullName, email, etc.), and metadata (metadata_*) are optional. Returns: {"checkout_url": "https://checkout.dodopayments.com/..."} POST (dynamic checkout) expects a JSON body with payment details (one-time or subscription). Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}. Reference the docs for the full POST schema: One-time payments: https://docs.dodopayments.com/api-reference/payments/post-payments Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions POST (checkout sessions) - (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 If Customer Portal Route Handler is selected: Purpose: This route allows customers to manage their subscriptions via the Dodo Payments portal. Integration: import { CustomerPortal } from "@dodopayments/fastify"; import Fastify from 'fastify' const fastify = Fastify({}) const customerPortalHandler = CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT }); fastify.get('/api/customer-portal', customerPortalHandler); Query Parameters: customer_id (required): e.g., ?customer_id=cus_123 send_email (optional): if true, customer is emailed the portal link Returns 400 if customer_id is missing. If Webhook Route Handler is selected: Purpose: Processes incoming webhook events from Dodo Payments to trigger events in your app. Integration: import Fastify from 'fastify' import { Webhooks } from '@dodopayments/fastify' const fastify = Fastify({}) fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { done(null, body) }) fastify.post('/api/webhooks', Webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // Handle Payload Here console.log(payload) } })); Features: Only POST method is allowed — others return 405 Signature verification is performed using webhookKey. Returns 401 if invalid. Zod-based payload validation. Returns 400 if invalid schema. All handlers are async functions. Supported Webhook Event Handlers: You may pass in any of the following handlers: onPayload onPaymentSucceeded onPaymentFailed onPaymentProcessing onPaymentCancelled onRefundSucceeded onRefundFailed onDisputeOpened, onDisputeExpired, onDisputeAccepted, onDisputeCancelled, onDisputeChallenged, onDisputeWon, onDisputeLost onSubscriptionActive, onSubscriptionOnHold, onSubscriptionRenewed, onSubscriptionPlanChanged, onSubscriptionCancelled, onSubscriptionFailed, onSubscriptionExpired, onSubscriptionUpdated onLicenseKeyCreated onAbandonedCheckoutDetected, onAbandonedCheckoutRecovered onDunningStarted, onDunningRecovered onCreditAdded, onCreditDeducted, onCreditExpired, onCreditRolledOver, onCreditRolloverForfeited, onCreditOverageCharged, onCreditManualAdjustment, onCreditBalanceLow Environment Variable Setup: Make sure to define these environment variables in your project: DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"" Use these inside your code as: process.env.DODO_PAYMENTS_API_KEY process.env.DODO_PAYMENTS_WEBHOOK_KEY Security Note: Do NOT commit secrets to version control. Use .env files locally and secrets managers in deployment environments (e.g., AWS, Vercel, Heroku, etc.). ``` # Framework Adaptors Overview Source: https://docs.dodopayments.com/developer-resources/framework-adaptors Pre-built adaptors for popular web frameworks to integrate Dodo Payments in minutes with minimal code Dodo Payments provides official framework adaptors that simplify payment integration. Each adaptor is designed to work seamlessly with your framework's conventions, offering checkout, customer portal, and webhook handling out of the box. Framework adaptors let you integrate Dodo Payments in under 10 lines of code. They handle authentication, request parsing, and response formatting automatically. ## Available Framework Adaptors Choose the adaptor that matches your framework: App Router support with route handlers for checkout, portal, and webhooks Vue-based full-stack framework with server routes integration Middleware-based handlers for the popular Node.js framework High-performance Node.js framework with plugin architecture Ultrafast web framework for the edge, Cloudflare Workers, and more Content-focused framework with server endpoints support Full-stack Svelte framework with server hooks integration Full-stack React framework with loader and action handlers Type-safe full-stack React framework with server functions Authentication framework plugin for seamless auth + payments Backend-as-a-Service component for real-time payment sync Native Bun.serve() handlers for checkout, portal, and webhooks ## Core Features All framework adaptors provide these built-in capabilities: | Feature | Description | | ---------------------- | ------------------------------------------------------------- | | **Checkout Handler** | Support for static, dynamic, and session-based checkout flows | | **Customer Portal** | Pre-built handler for subscription and billing management | | **Webhook Handler** | Secure signature verification with typed event handlers | | **Environment Config** | Simple setup via environment variables | | **Type Safety** | Full TypeScript support with typed payloads | ## Quick Start Get started with any framework adaptor in three steps: Use your package manager to install the framework-specific adaptor: ```bash theme={null} npm install @dodopayments/nextjs ``` ```bash theme={null} npm install @dodopayments/nuxt ``` ```bash theme={null} npm install @dodopayments/express ``` ```bash theme={null} npm install @dodopayments/hono ``` ```bash theme={null} npm install @dodopayments/astro ``` ```bash theme={null} npm install @dodopayments/sveltekit ``` Add your Dodo Payments credentials to your environment: ```env theme={null} DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success DODO_PAYMENTS_ENVIRONMENT="test_mode" # or "live_mode" ``` Never commit your `.env` file or secrets to version control. Set up your checkout, customer portal, and webhook routes: ```typescript theme={null} // app/checkout/route.ts import { Checkout } from "@dodopayments/nextjs"; export const GET = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, }); ``` ```typescript theme={null} import { checkoutHandler } from '@dodopayments/express'; app.get('/api/checkout', checkoutHandler({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, })); ``` ```typescript theme={null} import { Checkout } from "@dodopayments/hono"; app.get('/checkout', Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, })); ``` You're now ready to process payments! Visit the individual adaptor pages for detailed guides and all available options. ## Checkout Flow Types All adaptors support three checkout flow types: Use static checkout for simple, shareable payment links. Pass the product ID as a query parameter: ``` /api/checkout?productId=pdt_xxx&quantity=1 ``` Supports optional customer prefill and customization via query parameters. Use dynamic checkout to programmatically create payments with custom details: ```json theme={null} { "product_id": "pdt_xxx", "customer": { "email": "customer@example.com", "name": "John Doe" }, "quantity": 1 } ``` Supports both one-time payments and subscriptions. Use checkout sessions for the most flexible checkout experience with cart support: ```json theme={null} { "product_cart": [ { "product_id": "pdt_xxx", "quantity": 1 }, { "product_id": "pdt_yyy", "quantity": 2 } ], "customer": { "email": "customer@example.com" } } ``` Learn more in the [Checkout Sessions Guide](/developer-resources/checkout-session). ## Webhook Event Handling All adaptors provide type-safe webhook handling with granular event callbacks: ```typescript theme={null} Webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // Handle any webhook event }, onPaymentSucceeded: async (payload) => { // Handle successful payments }, onSubscriptionActive: async (payload) => { // Handle new subscriptions }, // ... 20+ event types supported }); ``` All webhook handlers automatically verify signatures and validate payloads using Zod schemas. Invalid requests are rejected with appropriate error codes. ## Choosing the Right Adaptor | Framework | Best For | Runtime | | ------------------ | -------------------------------------- | -------------- | | **Next.js** | Full-stack React apps with App Router | Node.js, Edge | | **Nuxt** | Full-stack Vue.js applications | Node.js | | **Express** | REST APIs and traditional Node.js apps | Node.js | | **Fastify** | High-performance APIs | Node.js | | **Hono** | Edge deployments, Cloudflare Workers | Edge, Node.js | | **Astro** | Content sites with server endpoints | Node.js, Edge | | **SvelteKit** | Full-stack Svelte applications | Node.js | | **Remix** | Full-stack React with nested routing | Node.js | | **TanStack Start** | Type-safe full-stack React | Node.js | | **Better Auth** | Apps already using Better Auth | Various | | **Convex** | Apps using Convex for backend | Convex Runtime | | **Bun** | Native Bun server applications | Bun | ## Getting Help Need assistance with framework adaptors? * **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 adaptor repository * **Documentation**: Visit our [API reference](/api-reference/introduction) # Hono Adaptor Source: https://docs.dodopayments.com/developer-resources/hono-adaptor Learn how to integrate Dodo Payments with your Hono App Router project using our NextJS Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup. Integrate Dodo Payments checkout into your Hono 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/hono ``` Create a .env file in your project root: ```env expandable theme={null} DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"" ``` Never commit your .env file or secrets to version control. ## Route Handler Examples All examples assume you are using the Hono App Router. Use this handler to integrate Dodo Payments checkout into your Hono app. Supports static (GET), dynamic (POST), and session (POST) flows. ```typescript Hono Route Handler expandable theme={null} // route.ts import { Checkout } from '@dodopayments/hono'; import Hono from 'hono' const app = new Hono() app.get( "/api/checkout", Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'static' }) ); app.post( "/api/checkout", Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'dynamic' }) ); app.post( "/api/checkout", Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'session' }) ); ``` ```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 Hono Route Handler expandable theme={null} // route.ts import { Checkout } from '@dodopayments/hono'; import Hono from 'hono' const app = new Hono() app.get( "/api/customer-portal", CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.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 Hono app. ```typescript Hono Route Handler expandable theme={null} // route.ts import Hono from 'hono' import { Webhooks } from '@dodopayments/hono' const app = new Hono() app.post( "/api/webhooks", Webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // Handle Payload Here console.log(payload) } }) ); ``` ## 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 Hono 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; onSubscriptionUpdated?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise; onDunningStarted?: (payload: WebhookPayload) => Promise; onDunningRecovered?: (payload: WebhookPayload) => Promise; onCreditAdded?: (payload: WebhookPayload) => Promise; onCreditDeducted?: (payload: WebhookPayload) => Promise; onCreditExpired?: (payload: WebhookPayload) => Promise; onCreditRolledOver?: (payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise; onCreditOverageCharged?: (payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (payload: WebhookPayload) => Promise; onCreditBalanceLow?: (payload: WebhookPayload) => Promise; ``` *** ## Prompt for LLM ``` You are an expert Hono developer assistant. Your task is to guide a user through integrating the @dodopayments/hono adapter into their existing Hono project. The @dodopayments/hono adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed to plug directly into an Hono app. First, install the necessary package. Use the package manager appropriate for the user's project (npm, yarn, or bun): npm install @dodopayments/hono --- Here's how you should structure your response: 1. Ask the user which functionalities they want to integrate. "Which parts of the @dodopayments/hono 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)" --- 2. 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. **Integration**: Create two routes in your Hono app — one for static (GET) and one for dynamic (POST) checkout. import { Checkout } from '@dodopayments/hono'; import Hono from 'hono' const app = new Hono() app.get( "/api/checkout", Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'static' }) ); app.post( "/api/checkout", Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, type: 'session' // or 'dynamic' for dynamic link }) ); Config Options: bearerToken: Your Dodo Payments API key (recommended to be stored in DODO_PAYMENTS_API_KEY env variable). returnUrl (optional): URL to redirect the user after successful checkout. environment: "test_mode" or "live_mode" type: "static" (GET) or "dynamic" (POST) or "session" (POST) GET (static checkout) expects query parameters: productId (required) quantity, customer fields (fullName, email, etc.), and metadata (metadata_*) are optional. POST (dynamic checkout) expects a JSON body with payment details (one-time or subscription). Reference the docs for the full POST schema: One-time payments: https://docs.dodopayments.com/api-reference/payments/post-payments Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions POST (checkout sessions) - (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 If Customer Portal Route Handler is selected: Purpose: This route allows customers to manage their subscriptions via the Dodo Payments portal. Integration: import { Checkout } from '@dodopayments/hono'; import Hono from 'hono' const app = new Hono() app.get( "/api/customer-portal", CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.DODO_PAYMENTS_ENVIRONMENT }) ); Query Parameters: customer_id (required): e.g., ?customer_id=cus_123 send_email (optional): if true, customer is emailed the portal link Returns 400 if customer_id is missing. If Webhook Route Handler is selected: Purpose: Processes incoming webhook events from Dodo Payments to trigger events in your app. Integration: import Hono from 'hono' import { Webhooks } from '@dodopayments/hono' const app = new Hono() app.post( "/api/webhooks", Webhooks({ webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY, onPayload: async (payload) => { // Handle Payload Here console.log(payload) } }) ); Features: Only POST method is allowed — others return 405 Signature verification is performed using webhookKey. Returns 401 if invalid. Zod-based payload validation. Returns 400 if invalid schema. All handlers are async functions. Supported Webhook Event Handlers: You may pass in any of the following handlers: onPayload onPaymentSucceeded onPaymentFailed onPaymentProcessing onPaymentCancelled onRefundSucceeded onRefundFailed onDisputeOpened, onDisputeExpired, onDisputeAccepted, onDisputeCancelled, onDisputeChallenged, onDisputeWon, onDisputeLost onSubscriptionActive, onSubscriptionOnHold, onSubscriptionRenewed, onSubscriptionPlanChanged, onSubscriptionCancelled, onSubscriptionFailed, onSubscriptionExpired, onSubscriptionUpdated onLicenseKeyCreated onAbandonedCheckoutDetected, onAbandonedCheckoutRecovered onDunningStarted, onDunningRecovered onCreditAdded, onCreditDeducted, onCreditExpired, onCreditRolledOver, onCreditRolloverForfeited, onCreditOverageCharged, onCreditManualAdjustment, onCreditBalanceLow Environment Variable Setup: Make sure to define these environment variables in your project: DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"" Use these inside your code as: process.env.DODO_PAYMENTS_API_KEY process.env.DODO_PAYMENTS_WEBHOOK_KEY Security Note: Do NOT commit secrets to version control. Use .env files locally and secrets managers in deployment environments (e.g., AWS, Vercel, Heroku, etc.). ``` # API Gateway Blueprint Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/api-gateway Track API calls and gateway-level usage for billing. Perfect for API-as-a-service platforms with high-volume request tracking. ## Use Cases Explore common scenarios supported by the API Gateway Blueprint: Track usage per customer for API platforms and charge based on number of calls. Monitor API usage patterns and implement usage-based rate limiting. Track response times and error rates alongside billing data. Bill customers based on their API consumption across different endpoints. Ideal for tracking API endpoint usage, rate limiting, and implementing usage-based API billing. ## Quick Start Track API calls at the gateway level with automatic batching for high-volume scenarios: ```bash theme={null} npm install @dodopayments/ingestion-blueprints ``` * **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys) Create a meter in your [Dodo Payments Dashboard](https://app.dodopayments.com/): * **Event Name**: `api_call` (or your preferred name) * **Aggregation Type**: `count` for tracking number of calls * Configure additional properties if tracking metadata like response times, status codes, etc. ```javascript Single API Call theme={null} import { Ingestion, trackAPICall } from '@dodopayments/ingestion-blueprints'; const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'api_call' }); // Track a single API call await trackAPICall(ingestion, { customerId: 'customer_123', metadata: { endpoint: '/api/v1/users', method: 'GET', status_code: 200, response_time_ms: 45 } }); ``` ```javascript High-Volume with Batching theme={null} import { Ingestion, createBatch } from '@dodopayments/ingestion-blueprints'; const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'live_mode', eventName: 'api_call' }); // Create batch for high-volume tracking const batch = createBatch(ingestion, { maxSize: 100, // Flush after 100 events flushInterval: 5000 // Or flush every 5 seconds }); // Add API calls to batch batch.add({ customerId: 'customer_123', metadata: { endpoint: '/api/v1/products', method: 'GET', status_code: 200 } }); // Clean up when done await batch.cleanup(); ``` ```javascript Express.js Middleware theme={null} import express from 'express'; import { Ingestion, createBatch } from '@dodopayments/ingestion-blueprints'; const app = express(); const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'live_mode', eventName: 'api_call' }); const batch = createBatch(ingestion, { maxSize: 50, flushInterval: 10000 }); // Middleware to track all API calls app.use((req, res, next) => { const startTime = Date.now(); res.on('finish', () => { const responseTime = Date.now() - startTime; batch.add({ customerId: req.user?.id || 'anonymous', metadata: { endpoint: req.path, method: req.method, status_code: res.statusCode, response_time_ms: responseTime } }); }); next(); }); // Cleanup on shutdown process.on('SIGTERM', async () => { await batch.cleanup(); process.exit(0); }); ``` ## Configuration ### Ingestion Configuration Your Dodo Payments API key from the dashboard. Environment mode: `test_mode` or `live_mode`. Event name that matches your meter configuration. ### Track API Call Options The customer ID for billing attribution. Optional metadata about the API call like endpoint, method, status code, response time, etc. ### Batch Configuration Maximum number of events before auto-flush. Default: `100`. Auto-flush interval in milliseconds. Default: `5000` (5 seconds). ## Best Practices **Use Batching for High Volume**: For applications handling more than 10 requests per second, use `createBatch()` to reduce overhead and improve performance. **Always Clean Up Batches**: Call `batch.cleanup()` on application shutdown to flush pending events and prevent data loss. # LLM Blueprint Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/llm Effortlessly track LLM token usage for usage-based billing with automatic ingestion to Dodo Payments. Works with AI SDK, OpenAI, Anthropic, OpenRouter, Groq, and Google Gemini. Get started in 2 minutes with automatic token tracking. Complete API documentation for ingesting usage events. Learn how to create and configure meters for billing. Comprehensive guide to usage-based billing with meters. Perfect for SaaS apps, AI chatbots, content generation tools, and any LLM-powered application that needs usage-based billing. ## Quick Start Get started with automatic LLM token tracking in just 2 minutes: Install the Dodo Payments Ingestion Blueprints: ```bash theme={null} npm install @dodopayments/ingestion-blueprints ``` You'll need two API keys: * **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys) * **LLM Provider API Key**: From AI SDK, OpenAI, Anthropic, Groq, etc. Store your API keys securely in environment variables. Never commit them to version control. Before tracking usage, create a meter in your Dodo Payments dashboard: 1. **Login** to [Dodo Payments Dashboard](https://app.dodopayments.com/) 2. **Navigate to** Products → Meters 3. **Click** "Create Meter" 4. **Configure your meter**: * **Meter Name**: Choose a descriptive name (e.g., "LLM Token Usage") * **Event Name**: Set a unique event identifier (e.g., `llm.chat_completion`) * **Aggregation Type**: Select `sum` to add up token counts * **Over Property**: Choose what to track: * `inputTokens` - Track input/prompt tokens * `outputTokens` - Track output/completion tokens (includes reasoning tokens when applicable) * `totalTokens` - Track combined input + output tokens The **Event Name** you set here must match exactly what you pass to the SDK (case-sensitive). For detailed instructions, see the [Usage-Based Billing Guide](/developer-resources/usage-based-billing-guide). Wrap your LLM client and start tracking automatically: ```javascript AI SDK theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import { generateText } from 'ai'; import { google } from '@ai-sdk/google'; const llmTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'aisdk.usage', }); const client = llmTracker.wrap({ client: { generateText }, customerId: 'customer_123' }); const response = await client.generateText({ model: google('gemini-2.0-flash'), prompt: 'Hello!', maxOutputTokens: 500 }); console.log('Usage:', response.usage); ``` ```javascript OpenRouter theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import OpenAI from 'openai'; const openrouter = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY }); const llmTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'openrouter.usage' }); const client = llmTracker.wrap({ client: openrouter, customerId: 'customer_123' }); const response = await client.chat.completions.create({ model: 'qwen/qwen3-max', messages: [{ role: 'user', content: 'Hello!' }], max_tokens: 500 }); console.log('Response:', response.choices[0].message.content); console.log('Usage:', response.usage); ``` ```javascript OpenAI theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import OpenAI from 'openai'; // 1. Create your LLM client (normal way) const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // 2. Create tracker ONCE at startup const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', // Use 'live_mode' for production eventName: 'llm.chat_completion' // Match your meter's event name }); // 3. Wrap & use - automatic tracking! const client = tracker.wrap({ client: openai, customerId: 'customer_123' }); // Every API call is now automatically tracked const response = await client.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: 'Hello!' }] }); // ✨ Usage automatically sent to Dodo Payments! console.log('Tokens used:', response.usage); ``` ```javascript Anthropic theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import Anthropic from '@anthropic-ai/sdk'; const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'anthropic.usage' }); const client = tracker.wrap({ client: anthropic, customerId: 'customer_123' }); const response = await client.messages.create({ model: 'claude-sonnet-4-0', max_tokens: 1024, messages: [{ role: 'user', content: 'Hello Claude!' }] }); console.log('Tokens used:', response.usage); ``` ```javascript Groq theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import Groq from 'groq-sdk'; const groq = new Groq({ apiKey: process.env.GROQ_API_KEY }); const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'groq.usage' }); const client = tracker.wrap({ client: groq, customerId: 'customer_123' }); const response = await client.chat.completions.create({ model: 'llama-3.1-8b-instant', messages: [{ role: 'user', content: 'Hello!' }] }); console.log('Tokens:', response.usage); ``` ```javascript Google Gemini theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import { GoogleGenAI } from '@google/genai'; const googleGenai = new GoogleGenAI({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY }); const llmTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'gemini.usage' }); const client = llmTracker.wrap({ client: googleGenai, customerId: 'customer_123' }); const response = await client.models.generateContent({ model: 'gemini-2.5-flash', contents: 'Why is the sky blue?' }); console.log('Response:', response.text); console.log('Usage:', response.usageMetadata); ``` That's it! Every API call now automatically tracks token usage and sends events to Dodo Payments for billing. *** ## Configuration ### Tracker Configuration Create a tracker once at application startup with these required parameters: Your Dodo Payments API key. Get it from the [API Keys page](https://app.dodopayments.com/developer/api-keys). ```javascript theme={null} apiKey: process.env.DODO_PAYMENTS_API_KEY ``` The environment mode for the tracker. * `test_mode` - Use for development and testing * `live_mode` - Use for production ```javascript theme={null} environment: 'test_mode' // or 'live_mode' ``` Always use `test_mode` during development to avoid affecting production metrics. The event name that triggers your meter. Must match exactly what you configured in your Dodo Payments meter (case-sensitive). ```javascript theme={null} eventName: 'llm.chat_completion' ``` This event name links your tracked usage to the correct meter for billing calculations. ### Wrapper Configuration When wrapping your LLM client, provide these parameters: Your LLM client instance (OpenAI, Anthropic, Groq, etc.). ```javascript theme={null} client: openai ``` The unique customer identifier for billing. This should match your customer ID in Dodo Payments. ```javascript theme={null} customerId: 'customer_123' ``` Use your application's user ID or customer ID to ensure accurate billing per customer. Optional additional data to attach to the tracking event. Useful for filtering and analysis. ```javascript theme={null} metadata: { feature: 'chat', userTier: 'premium', sessionId: 'session_123', modelVersion: 'gpt-4' } ``` ### Complete Configuration Example ```javascript Full Configuration theme={null} import { createLLMTracker } from "@dodopayments/ingestion-blueprints"; import { generateText } from "ai"; import { google } from "@ai-sdk/google"; import "dotenv/config"; async function aiSdkExample() { console.log("🤖 AI SDK Simple Usage Example\n"); try { // 1. Create tracker const llmTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY!, environment: "test_mode", eventName: "your_meter_event_name", }); // 2. Wrap the ai-sdk methods const client = llmTracker.wrap({ client: { generateText }, customerId: "customer_123", metadata: { provider: "ai-sdk", }, }); // 3. Use the wrapped function const response = await client.generateText({ model: google("gemini-2.5-flash"), prompt: "Hello, I am a cool guy! Tell me a fun fact.", maxOutputTokens: 500, }); console.log(response); console.log(response.usage); console.log("✅ Automatically tracked for customer\n"); } catch (error) { console.error(error); } } aiSdkExample().catch(console.error); ``` **Automatic Tracking:** The SDK automatically tracks token usage in the background without modifying the response. Your code remains clean and identical to using the original provider SDKs. *** ## Supported Providers The LLM Blueprint works seamlessly with all major LLM providers and aggregators: Track usage with the Vercel AI SDK for universal LLM support. ```javascript AI SDK Integration theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import { generateText } from 'ai'; import { google } from '@ai-sdk/google'; const llmTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'aisdk.usage', }); const client = llmTracker.wrap({ client: { generateText }, customerId: 'customer_123', metadata: { model: 'gemini-2.0-flash', feature: 'chat' } }); const response = await client.generateText({ model: google('gemini-2.0-flash'), prompt: 'Explain neural networks', maxOutputTokens: 500 }); console.log('Usage:', response.usage); ``` **Tracked Metrics:** * `inputTokens` → `inputTokens` * `outputTokens` + `reasoningTokens` → `outputTokens` * `totalTokens` → `totalTokens` * Model name When using reasoning-capable models through AI SDK (like Google's Gemini 2.5 Flash with thinking mode), reasoning tokens are automatically included in the `outputTokens` count for accurate billing. Track token usage across 200+ models via OpenRouter's unified API. ```javascript OpenRouter Integration theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import OpenAI from 'openai'; // OpenRouter uses OpenAI-compatible API const openrouter = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY }); const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'openrouter.usage' }); const client = tracker.wrap({ client: openrouter, customerId: 'user_123', metadata: { provider: 'openrouter' } }); const response = await client.chat.completions.create({ model: 'qwen/qwen3-max', messages: [{ role: 'user', content: 'What is machine learning?' }], max_tokens: 500 }); console.log('Response:', response.choices[0].message.content); console.log('Usage:', response.usage); ``` **Tracked Metrics:** * `prompt_tokens` → `inputTokens` * `completion_tokens` → `outputTokens` * `total_tokens` → `totalTokens` * Model name OpenRouter provides access to models from OpenAI, Anthropic, Google, Meta, and many more providers through a single API. Track token usage from OpenAI's GPT models automatically. ```javascript OpenAI Integration theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import OpenAI from 'openai'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'openai.usage' }); const client = tracker.wrap({ client: openai, customerId: 'user_123' }); // All OpenAI methods work automatically const response = await client.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: 'Explain quantum computing' }] }); console.log('Total tokens:', response.usage.total_tokens); ``` **Tracked Metrics:** * `prompt_tokens` → `inputTokens` * `completion_tokens` → `outputTokens` * `total_tokens` → `totalTokens` * Model name Track token usage from Anthropic's Claude models. ```javascript Anthropic Integration theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import Anthropic from '@anthropic-ai/sdk'; const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'anthropic.usage' }); const client = tracker.wrap({ client: anthropic, customerId: 'user_123' }); const response = await client.messages.create({ model: 'claude-sonnet-4-0', max_tokens: 1024, messages: [{ role: 'user', content: 'Explain machine learning' }] }); console.log('Input tokens:', response.usage.input_tokens); console.log('Output tokens:', response.usage.output_tokens); ``` **Tracked Metrics:** * `input_tokens` → `inputTokens` * `output_tokens` → `outputTokens` * Calculated `totalTokens` * Model name Track ultra-fast LLM inference with Groq. ```javascript Groq Integration theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import Groq from 'groq-sdk'; const groq = new Groq({ apiKey: process.env.GROQ_API_KEY }); const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'groq.usage' }); const client = tracker.wrap({ client: groq, customerId: 'user_123' }); const response = await client.chat.completions.create({ model: 'llama-3.1-8b-instant', messages: [{ role: 'user', content: 'What is AI?' }] }); console.log('Tokens:', response.usage); ``` **Tracked Metrics:** * `prompt_tokens` → `inputTokens` * `completion_tokens` → `outputTokens` * `total_tokens` → `totalTokens` * Model name Track token usage from Google's Gemini models via the Google GenAI SDK. ```javascript Google Gemini Integration theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import { GoogleGenAI } from '@google/genai'; const googleGenai = new GoogleGenAI({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY }); const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'gemini.usage' }); const client = tracker.wrap({ client: googleGenai, customerId: 'user_123' }); const response = await client.models.generateContent({ model: 'gemini-2.5-flash', contents: 'Explain quantum computing' }); console.log('Response:', response.text); console.log('Usage:', response.usageMetadata); ``` **Tracked Metrics:** * `promptTokenCount` → `inputTokens` * `candidatesTokenCount` + `thoughtsTokenCount` → `outputTokens` * `totalTokenCount` → `totalTokens` * Model version **Gemini Thinking Mode:** When using Gemini models with thinking/reasoning capabilities (like Gemini 2.5 Pro), the SDK automatically includes `thoughtsTokenCount` (reasoning tokens) in `outputTokens` to accurately reflect the full computational cost. *** ## Advanced Usage ### Multiple Providers Track usage across different LLM providers with separate trackers: ```javascript Multiple Provider Setup theme={null} import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import OpenAI from 'openai'; import Groq from 'groq-sdk'; import Anthropic from '@anthropic-ai/sdk'; import { GoogleGenAI } from '@google/genai'; // Create separate trackers for different providers const openaiTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'live_mode', eventName: 'openai.usage' }); const groqTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'live_mode', eventName: 'groq.usage' }); const anthropicTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'live_mode', eventName: 'anthropic.usage' }); const geminiTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'live_mode', eventName: 'gemini.usage' }); const openrouterTracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'live_mode', eventName: 'openrouter.usage' }); // Initialize clients const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const groq = new Groq({ apiKey: process.env.GROQ_API_KEY }); const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); const googleGenai = new GoogleGenAI({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY }); const openrouter = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY }); // Wrap clients const trackedOpenAI = openaiTracker.wrap({ client: openai, customerId: 'user_123' }); const trackedGroq = groqTracker.wrap({ client: groq, customerId: 'user_123' }); const trackedAnthropic = anthropicTracker.wrap({ client: anthropic, customerId: 'user_123' }); const trackedGemini = geminiTracker.wrap({ client: googleGenai, customerId: 'user_123' }); const trackedOpenRouter = openrouterTracker.wrap({ client: openrouter, customerId: 'user_123' }); // Use whichever provider you need const response = await trackedOpenAI.chat.completions.create({...}); // or const geminiResponse = await trackedGemini.models.generateContent({...}); // or const openrouterResponse = await trackedOpenRouter.chat.completions.create({...}); ``` Use different event names for different providers to track usage separately in your meters. ### Express.js API Integration Complete example of integrating LLM tracking into an Express.js API: ```javascript Express.js Server theme={null} import express from 'express'; import { createLLMTracker } from '@dodopayments/ingestion-blueprints'; import OpenAI from 'openai'; const app = express(); app.use(express.json()); // Initialize OpenAI client const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // Create tracker once at startup const tracker = createLLMTracker({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: process.env.NODE_ENV === 'production' ? 'live_mode' : 'test_mode', eventName: 'api.chat_completion' }); // Chat endpoint with automatic tracking app.post('/api/chat', async (req, res) => { try { const { message, userId } = req.body; // Validate input if (!message || !userId) { return res.status(400).json({ error: 'Missing message or userId' }); } // Wrap client for this specific user const trackedClient = tracker.wrap({ client: openai, customerId: userId, metadata: { endpoint: '/api/chat', timestamp: new Date().toISOString() } }); // Make LLM request - automatically tracked const response = await trackedClient.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: message }], temperature: 0.7 }); const completion = response.choices[0].message.content; res.json({ message: completion, usage: response.usage }); } catch (error) { console.error('Chat error:', error); res.status(500).json({ error: 'Internal server error' }); } }); app.listen(3000, () => { console.log('Server running on port 3000'); }); ``` *** ## What Gets Tracked Every LLM API call automatically sends a usage event to Dodo Payments with the following structure: ```json Event Structure theme={null} { "event_id": "llm_1673123456_abc123", "customer_id": "customer_123", "event_name": "llm.chat_completion", "timestamp": "2024-01-08T10:30:00Z", "metadata": { "inputTokens": 10, "outputTokens": 25, "totalTokens": 35, "model": "gpt-4", } } ``` ### Event Fields Unique identifier for this specific event. Automatically generated by the SDK. Format: `llm_[timestamp]_[random]` The customer ID you provided when wrapping the client. Used for billing. The event name that triggers your meter. Matches your tracker configuration. ISO 8601 timestamp when the event occurred. Token usage and additional tracking data: * `inputTokens` - Number of input/prompt tokens used * `outputTokens` - Number of output/completion tokens used (includes reasoning tokens when applicable) * `totalTokens` - Total tokens (input + output) * `model` - The LLM model used (e.g., "gpt-4") * `provider` - The LLM provider (if included in wrapper metadata) * Any custom metadata you provided when wrapping the client **Reasoning Tokens:** For models with reasoning capabilities, `outputTokens` automatically includes both the completion tokens and reasoning tokens. Your Dodo Payments meter uses the `metadata` fields (especially `inputTokens`, `outputTokens` or `totalTokens`) to calculate usage and billing. *** # Object Storage Blueprint Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/object-storage Track file uploads and storage usage for S3, Google Cloud Storage, Azure Blob, and other object storage services. ## Use Cases Explore common scenarios supported by the Object Storage Blueprint: Bill customers based on total storage usage and upload volume. Track backup data uploads and charge per GB stored. Monitor media uploads and bill for storage and bandwidth. Track document uploads per customer for usage-based pricing. Perfect for billing based on storage uploads, file hosting, CDN usage, or backup services. ## Quick Start Track object storage uploads with bytes consumed: ```bash theme={null} npm install @dodopayments/ingestion-blueprints ``` * **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys) * **Storage Provider API Key**: From AWS S3, Google Cloud Storage, Azure, etc. Create a meter in your [Dodo Payments Dashboard](https://app.dodopayments.com/): * **Event Name**: `object_storage_upload` (or your preferred name) * **Aggregation Type**: `sum` to track total bytes uploaded * **Over Property**: `bytes` to bill based on storage size ```javascript AWS S3 Upload theme={null} import { Ingestion, trackObjectStorage } from '@dodopayments/ingestion-blueprints'; import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; import fs from 'fs'; const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'object_storage_upload' }); const s3 = new S3Client({ region: 'us-east-1' }); // Read the file (example: from disk or request) const fileBuffer = fs.readFileSync('./document.pdf'); // Upload to S3 const command = new PutObjectCommand({ Bucket: 'my-bucket', Key: 'uploads/document.pdf', Body: fileBuffer }); await s3.send(command); // Track the upload await trackObjectStorage(ingestion, { customerId: 'customer_123', bytes: fileBuffer.length }); ``` ```javascript Google Cloud Storage theme={null} import { Ingestion, trackObjectStorage } from '@dodopayments/ingestion-blueprints'; import { Storage } from '@google-cloud/storage'; import fs from 'fs'; const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'object_storage_upload' }); const storage = new Storage(); const bucket = storage.bucket('my-bucket'); // Read the file const fileBuffer = fs.readFileSync('./image.png'); // Upload to GCS await bucket.file('uploads/image.png').save(fileBuffer); // Track the upload await trackObjectStorage(ingestion, { customerId: 'customer_456', bytes: fileBuffer.length, metadata: { bucket: 'my-bucket', key: 'uploads/image.png' } }); ``` ## Configuration ### Ingestion Configuration Your Dodo Payments API key from the dashboard. Environment mode: `test_mode` or `live_mode`. Event name that matches your meter configuration. ### Track Object Storage Options The customer ID for billing attribution. Number of bytes uploaded. Required for byte-based billing. Optional metadata about the upload like bucket name, content type, etc. ## Best Practices **Track Before or After Upload**: You can track the event before or after the actual upload depending on your error handling strategy. **Handle Upload Failures**: Only track successful uploads to avoid billing for failed operations. # Stream Blueprint Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/stream Track streaming data consumption for video, audio, live streams, and real-time data transfer billing. ## Use Cases Explore common scenarios supported by the Stream Blueprint: Bill customers based on video bandwidth consumption and streaming quality. Track audio streaming usage per user for subscription tiers. Monitor live stream consumption and charge for bandwidth usage. Track real-time data transfer for IoT and telemetry applications. Perfect for video/audio streaming platforms, live streaming services, and real-time data applications. ## Quick Start Track streaming bytes consumed by your customers: ```bash theme={null} npm install @dodopayments/ingestion-blueprints ``` * **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys) Create a meter in your [Dodo Payments Dashboard](https://app.dodopayments.com/): * **Event Name**: `stream_consumption` (or your preferred name) * **Aggregation Type**: `sum` to track total bytes streamed * **Over Property**: `bytes` to bill based on bandwidth usage ```javascript Video Streaming theme={null} import { Ingestion, trackStreamBytes } from '@dodopayments/ingestion-blueprints'; const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'stream_consumption' }); // Track video stream consumption await trackStreamBytes(ingestion, { customerId: 'customer_123', bytes: 10485760, // 10MB metadata: { stream_type: 'video', } }); ``` ## Configuration ### Ingestion Configuration Your Dodo Payments API key from the dashboard. Environment mode: `test_mode` or `live_mode`. Event name that matches your meter configuration. ### Track Stream Bytes Options The customer ID for billing attribution. Number of bytes consumed in the stream. Required for bandwidth-based billing. Optional metadata about the stream like stream type, quality, sessionId, etc. ## Best Practices **Track by Chunk**: For long streams, track consumption in chunks rather than waiting for the entire stream to complete. **Accurate Byte Counting**: Ensure byte counts include all overhead (headers, protocol overhead) if billing for total bandwidth. # Time Range Blueprint Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/time-range Track resource consumption based on elapsed time for compute, serverless functions, containers, and runtime billing. ## Use Cases Explore common scenarios supported by the Time Range Blueprint: Bill based on function execution time and memory usage. Track container running time for usage-based billing. Monitor VM runtime and charge by the minute or hour. Track processing time for data exports, reports, and batch jobs. Perfect for billing based on compute time, function execution duration, container runtime, or any time-based usage. ## Quick Start Track resource usage by time duration: ```bash theme={null} npm install @dodopayments/ingestion-blueprints ``` * **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys) Create a meter in your [Dodo Payments Dashboard](https://app.dodopayments.com/): * **Event Name**: `time_range_usage` (or your preferred name) * **Aggregation Type**: `sum` to track total duration * **Over Property**: `durationSeconds`, `durationMinutes`, or `durationMs` ```javascript Serverless Functions theme={null} import { Ingestion, trackTimeRange } from '@dodopayments/ingestion-blueprints'; const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'function_execution' }); // Track function execution time const startTime = Date.now(); // Execute your function (example: image processing) const result = await yourImageProcessingLogic(); const durationMs = Date.now() - startTime; await trackTimeRange(ingestion, { customerId: 'customer_123', durationMs: durationMs }); ``` ```javascript Container Runtime theme={null} import { Ingestion, trackTimeRange } from '@dodopayments/ingestion-blueprints'; const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'container_runtime' }); // Track container runtime in seconds await trackTimeRange(ingestion, { customerId: 'customer_456', durationSeconds: 120 }); ``` ```javascript VM Instance Runtime theme={null} import { Ingestion, trackTimeRange } from '@dodopayments/ingestion-blueprints'; const ingestion = new Ingestion({ apiKey: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', eventName: 'vm_runtime' }); // Track VM runtime in minutes await trackTimeRange(ingestion, { customerId: 'customer_789', durationMinutes: 60 }); ``` ## Configuration ### Ingestion Configuration Your Dodo Payments API key from the dashboard. Environment mode: `test_mode` or `live_mode`. Event name that matches your meter configuration. ### Track Time Range Options The customer ID for billing attribution. Duration in milliseconds. Use for sub-second precision. Duration in seconds. Most common for function execution and short tasks. Duration in minutes. Useful for longer-running resources like VMs. Optional metadata about the resource like CPU, memory, region, etc. ## Best Practices **Choose the Right Unit**: Use milliseconds for short operations, seconds for functions, and minutes for longer-running resources. **Accurate Timing**: Use `Date.now()` or `performance.now()` for accurate time tracking, especially for serverless functions. # Next.js Adaptor Source: https://docs.dodopayments.com/developer-resources/nextjs-adaptor Learn how to integrate Dodo Payments with your Next.js App Router project using our NextJS Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup. Integrate Dodo Payments checkout with static, dynamic, and session flows. 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/nextjs ``` Create a .env file in your project root: ```env theme={null} DODO_PAYMENTS_API_KEY=your-api-key DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success DODO_PAYMENTS_ENVIRONMENT="test_mode"or"live_mode" ``` Never commit your .env file or secrets to version control. ## Route Handler Examples All examples assume you are using the Next.js App Router. Use this handler to integrate Dodo Payments checkout into your Next.js app. Supports static (GET), dynamic (POST), and checkout session (POST) payment flows. ```typescript Next.js Route Handler expandable theme={null} // app/checkout/route.ts import { Checkout } from "@dodopayments/nextjs"; export const GET = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "static", // optional, defaults to 'static' }); export const POST = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "dynamic", // for dynamic checkout }); export const POST = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.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 Next.js Route Handler expandable theme={null} // app/customer-portal/route.ts import { CustomerPortal } from "@dodopayments/nextjs"; export const GET = CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: process.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 Next.js app. ```typescript Next.js Route Handler expandable theme={null} // app/api/webhook/dodo-payments/route.ts import { Webhooks } from "@dodopayments/nextjs"; export const POST = Webhooks({ webhookKey: process.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 Next.js 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; onSubscriptionUpdated?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise; onDunningStarted?: (payload: WebhookPayload) => Promise; onDunningRecovered?: (payload: WebhookPayload) => Promise; onCreditAdded?: (payload: WebhookPayload) => Promise; onCreditDeducted?: (payload: WebhookPayload) => Promise; onCreditExpired?: (payload: WebhookPayload) => Promise; onCreditRolledOver?: (payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise; onCreditOverageCharged?: (payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (payload: WebhookPayload) => Promise; onCreditBalanceLow?: (payload: WebhookPayload) => Promise; ``` *** ## Prompt for LLM ``` You are an expert Next.js developer assistant. Your task is to guide a user through integrating the @dodopayments/nextjs adapter into their existing Next.js project. The @dodopayments/nextjs adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for the Next.js 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/nextjs Here's how you should structure your response: Ask the user which functionalities they want to integrate. "Which parts of the @dodopayments/nextjs 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 manages different types of checkout flows. All checkout types (static, dynamic, and sessions) return JSON responses with checkout URLs for programmatic handling. File Creation: Create a new file at app/checkout/route.ts in your Next.js project. Code Snippet: // app/checkout/route.ts import { Checkout } from '@dodopayments/nextjs' export const GET = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY!, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "static", }); export const POST = Checkout({ bearerToken: process.env.DODO_PAYMENTS_API_KEY!, returnUrl: process.env.DODO_PAYMENTS_RETURN_URL, environment: process.env.DODO_PAYMENTS_ENVIRONMENT, type: "session", // or "dynamic" for dynamic link }); 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 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 Next.js project. Code Snippet: // app/customer-portal/route.ts import { CustomerPortal } from '@dodopayments/nextjs' export const GET = CustomerPortal({ bearerToken: process.env.DODO_PAYMENTS_API_KEY!, environment: "test_mode", }); 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 Next.js project. Code Snippet: // app/api/webhook/dodo-payments/route.ts import { Webhooks } from '@dodopayments/nextjs' export const POST = Webhooks({ webhookKey: process.env.DODO_WEBHOOK_SECRET!, 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 onSubscriptionUpdated?: (payload: WebhookPayload) => Promise onLicenseKeyCreated?: (payload: WebhookPayload) => Promise onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise onDunningStarted?: (payload: WebhookPayload) => Promise onDunningRecovered?: (payload: WebhookPayload) => Promise onCreditAdded?: (payload: WebhookPayload) => Promise onCreditDeducted?: (payload: WebhookPayload) => Promise onCreditExpired?: (payload: WebhookPayload) => Promise onCreditRolledOver?: (payload: WebhookPayload) => Promise onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise onCreditOverageCharged?: (payload: WebhookPayload) => Promise onCreditManualAdjustment?: (payload: WebhookPayload) => Promise onCreditBalanceLow?: (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 Next.js 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_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: process.env.DODO_PAYMENTS_API_KEY! webhookKey: process.env.DODO_WEBHOOK_SECRET! 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 ``` # Nuxt Adaptor Source: https://docs.dodopayments.com/developer-resources/nuxt-adaptor Integrate Dodo Payments with your Nuxt project using the official Nuxt module. Covers checkout, customer portal, webhooks, and secure environment setup. Integrate Dodo Payments checkout into your Nuxt app using a server route. Allow customers to manage subscriptions and details via a Nuxt server route. Receive and process Dodo Payments webhook events securely in Nuxt. ## Overview This guide explains how to integrate Dodo Payments into your Nuxt application using the official Nuxt module. You'll learn how to set up checkout, customer portal, and webhook API routes, and how to securely manage environment variables. ## Installation Run the following command in your project root: ```bash theme={null} npm install @dodopayments/nuxt ``` Add @dodopayments/nuxt to your modules array and configure it: ```typescript nuxt.config.ts expandable theme={null} export default defineNuxtConfig({ modules: ["@dodopayments/nuxt"], devtools: { enabled: true }, compatibilityDate: "2025-02-25", runtimeConfig: { private: { bearerToken: process.env.NUXT_PRIVATE_BEARER_TOKEN, webhookKey: process.env.NUXT_PRIVATE_WEBHOOK_KEY, environment: process.env.NUXT_PRIVATE_ENVIRONMENT, returnUrl: process.env.NUXT_PRIVATE_RETURNURL }, } }); ``` Never commit your .env file or secrets to version control. ## API Route Handler Examples All Dodo Payments integrations in Nuxt are handled via server routes in the server/routes/api/ directory. Use this handler to integrate Dodo Payments checkout into your Nuxt app. Supports static (GET), dynamic (POST), and session (POST) payment flows. ```typescript server/routes/api/checkout.get.ts expandable theme={null} export default defineEventHandler((event) => { const { private: { bearerToken, environment, returnUrl }, } = useRuntimeConfig(); const handler = checkoutHandler({ bearerToken: bearerToken, environment: environment, returnUrl: returnUrl, }); return handler(event); }); ``` ```typescript server/routes/api/checkout.post.ts expandable theme={null} export default defineEventHandler((event) => { const { private: { bearerToken, environment, returnUrl }, } = useRuntimeConfig(); const handler = checkoutHandler({ bearerToken: bearerToken, environment: environment, returnUrl: returnUrl, type: "dynamic" }); return handler(event); }); ``` ```typescript server/routes/api/checkout.post.ts (Checkout Sessions) expandable theme={null} export default defineEventHandler((event) => { const { private: { bearerToken, environment, returnUrl }, } = useRuntimeConfig(); const handler = checkoutHandler({ bearerToken: bearerToken, environment: environment, returnUrl: returnUrl, type: "session" }); return handler(event); }); ``` If productId is missing or invalid, the handler returns a 400 response. ```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" }' ``` Create a GET route to allow customers to access their portal. Accepts customer\_id as a query parameter. ```typescript server/routes/api/customer-portal.get.ts expandable theme={null} export default defineEventHandler((event) => { const { private: { bearerToken, environment }, } = useRuntimeConfig(); const handler = customerPortalHandler({ bearerToken, environment: environment, }); return handler(event); }); ``` **Query Parameters:** * customer\_id (required): The customer ID for the portal session (e.g., ?customer\_id=cus\_123) * send\_email (optional, boolean): If true, sends an email to the customer with the portal link. Returns 400 if customer\_id is missing. ```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 ``` Create a POST route to securely receive and process webhook events from Dodo Payments. ```typescript server/routes/api/webhook.post.ts expandable theme={null} export default defineEventHandler((event) => { const { private: { webhookKey }, } = useRuntimeConfig(); const handler = Webhooks({ webhookKey: webhookKey, onPayload: async (payload: any) => { // Handle webhook payload here }, // ...add other event handlers as needed }); return handler(event); }); ``` ## 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 Nuxt 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; onSubscriptionUpdated?: (payload: WebhookPayload) => Promise; onLicenseKeyCreated?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise; onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise; onDunningStarted?: (payload: WebhookPayload) => Promise; onDunningRecovered?: (payload: WebhookPayload) => Promise; onCreditAdded?: (payload: WebhookPayload) => Promise; onCreditDeducted?: (payload: WebhookPayload) => Promise; onCreditExpired?: (payload: WebhookPayload) => Promise; onCreditRolledOver?: (payload: WebhookPayload) => Promise; onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise; onCreditOverageCharged?: (payload: WebhookPayload) => Promise; onCreditManualAdjustment?: (payload: WebhookPayload) => Promise; onCreditBalanceLow?: (payload: WebhookPayload) => Promise; ``` *** ## Prompt for LLM ``` You are an expert Nuxt developer assistant. Your task is to guide a user through integrating the @dodopayments/nuxt module into their existing Nuxt project. The @dodopayments/nuxt module provides API route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for Nuxt 3 server routes. First, install the necessary package: npm install @dodopayments/nuxt Second, add the configuration to nuxt.config.ts export default defineNuxtConfig({ modules: ["@dodopayments/nuxt"], devtools: { enabled: true }, compatibilityDate: "2025-02-25", runtimeConfig: { private: { bearerToken: process.env.NUXT_PRIVATE_BEARER_TOKEN, webhookKey: process.env.NUXT_PRIVATE_BEARER_TOKEN, environment: process.env.NUXT_PRIVATE_ENVIRONMENT, returnUrl: process.env.NUXT_PRIVATE_RETURNURL }, } }); Here's how you should structure your response: Ask the user which functionalities they want to integrate. "Which parts of the @dodopayments/nuxt module would you like to integrate into your project? You can choose one or more of the following: Checkout API Route (for handling product checkouts) Customer Portal API Route (for managing customer subscriptions/details) Webhook API Route (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 API Route is selected: Purpose: This route redirects users to the Dodo Payments checkout page. File Creation: Create a new file at server/routes/api/checkout.get.ts in your Nuxt project. Code Snippet: // server/routes/api/checkout.get.ts export default defineEventHandler((event) => { const { private: { bearerToken, environment, returnUrl }, } = useRuntimeConfig(); const handler = checkoutHandler({ bearerToken: bearerToken, environment: environment, returnUrl: returnUrl, }); return handler(event); }); Configuration & Usage: - bearerToken: Your Dodo Payments API key. Set via the NUXT_PRIVATE_BEARER_TOKEN environment variable. - returnUrl: (Optional) The URL to redirect the user to after a successful checkout. - environment: (Optional) Set to your environment (e.g., "test_mode" or "live_mode"). 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) Dynamic Checkout (POST): Parameters are sent as a JSON body. Supports both one-time and recurring payments. 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 API Route is selected: Purpose: This route allows customers to access their Dodo Payments customer portal. File Creation: Create a new file at server/routes/api/customer-portal.get.ts in your Nuxt project. Code Snippet: // server/routes/api/customer-portal.get.ts export default defineEventHandler((event) => { const { private: { bearerToken, environment }, } = useRuntimeConfig(); const handler = customerPortalHandler({ bearerToken, environment: environment, }); return handler(event); }); 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 API Route is selected: Purpose: This route 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 server/routes/api/webhook.post.ts in your Nuxt project. Code Snippet: // server/routes/api/webhook.post.ts export default defineEventHandler((event) => { const { private: { webhookKey }, } = useRuntimeConfig(); const handler = Webhooks({ webhookKey: webhookKey, onPayload: async (payload) => { // handle the payload }, // ... other event handlers for granular control }); return handler(event); }); Handler Details: - Method: Only POST requests are supported. Other methods return 405. - Signature Verification: The handler verifies the webhook signature using 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 event handlers include: - 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 - onSubscriptionUpdated?: (payload: WebhookPayload) => Promise - onLicenseKeyCreated?: (payload: WebhookPayload) => Promise - onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise - onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise - onDunningStarted?: (payload: WebhookPayload) => Promise - onDunningRecovered?: (payload: WebhookPayload) => Promise - onCreditAdded?: (payload: WebhookPayload) => Promise - onCreditDeducted?: (payload: WebhookPayload) => Promise - onCreditExpired?: (payload: WebhookPayload) => Promise - onCreditRolledOver?: (payload: WebhookPayload) => Promise - onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise - onCreditOverageCharged?: (payload: WebhookPayload) => Promise - onCreditManualAdjustment?: (payload: WebhookPayload) => Promise - onCreditBalanceLow?: (payload: WebhookPayload) => Promise Environment Variable Setup: To ensure the module functions correctly, set up the following environment variables in your Nuxt project's deployment environment (e.g., Vercel, Netlify, AWS, etc.): - NUXT_PRIVATE_BEARER_TOKEN: Your Dodo Payments API Key (required for Checkout and Customer Portal). - NUXT_PRIVATE_WEBHOOK_KEY: Your Dodo Payments Webhook Secret (required for Webhook handler). - NUXT_PRIVATE_ENVIRONMENT: (Optional) Set to your environment (e.g., "test_mode" or "live_mode"). - NUXT_PRIVATE_RETURNURL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler). Usage in your code: bearerToken: useRuntimeConfig().private.bearerToken webhookKey: useRuntimeConfig().private.webhookKey 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. ``` # On-Demand Subscriptions Source: https://docs.dodopayments.com/developer-resources/ondemand-subscriptions Integrate on-demand subscriptions by authorizing mandates, creating variable charges, handling webhooks, and implementing safe retry policies. ## Overview On-demand subscriptions let you authorize a customer's payment method once and then charge variable amounts whenever you need, instead of on a fixed schedule. This feature is available for all accounts—no approval required. Use this guide to: * Create an on-demand subscription (authorize a mandate with optional initial price) * Trigger subsequent charges with custom amounts * Track outcomes using webhooks For a general subscription setup, see the [Subscription Integration Guide](/developer-resources/subscription-integration-guide). ## Prerequisites * Dodo Payments merchant account and API key * Webhook secret configured and an endpoint to receive events * A subscription product in your catalog If you want the customer to approve the mandate via hosted checkout, set `payment_link: true` and provide a `return_url`. ## How on-demand works 1. You create a subscription with the `on_demand` object to authorize a payment method and optionally collect an initial charge. 2. Later, you create charges against that subscription with custom amounts using the dedicated charge endpoint. 3. You listen to webhooks (e.g., `payment.succeeded`, `payment.failed`) to update your system. ## Create an on-demand subscription Endpoint: [POST /checkouts](/api-reference/checkout-sessions/create) Key request fields (body):\ Please find them in [Create Checkout Session](/api-reference/checkout-sessions/create) ### Create an on-demand subscription ```javascript theme={null} import DodoPayments from 'dodopayments'; const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', // defaults to 'live_mode' }); async function main() { const subscription = await client.checkoutSessions.create({ product_cart: [{ product_id: 'pdt_123', quantity: 1 }], billing_address: { city: 'SF', country: 'US', state: 'CA', street: '1 Market St', zipcode: '94105' }, customer: { customer_id: 'cus_123' }, return_url: 'https://example.com/billing/success', subscription_data: { on_demand: { mandate_only: true // set false to collect an initial charge // product_price: 1000, // optional: charge $10.00 now if mandate_only is false // product_currency: 'USD', // product_description: 'Custom initial charge', // adaptive_currency_fees_inclusive: false, } } }); console.log(subscription.checkout_url); } main().catch(console.error); ``` ```python theme={null} import os from dodopayments import DodoPayments client = DodoPayments( bearer_token=os.environ.get('DODO_PAYMENTS_API_KEY'), environment="test_mode", # defaults to "live_mode" ) checkout = client.checkout_sessions.create( product_cart=[ {"product_id": "pdt_123", "quantity": 1} ], billing_address={ "city": "SF", "country": "US", "state": "CA", "street": "1 Market St", "zipcode": "94105", }, customer={ "customer_id": "cus_123", }, return_url="https://example.com/billing/success", subscription_data={ "on_demand":{ "mandate_only": True, # set False to collect an initial charge # "product_price": 1000, # optional: charge $10.00 now if mandate_only is false # "product_currency": "USD", # "product_description": "Custom initial charge", # "adaptive_currency_fees_inclusive": False, } }, ) print(checkout.checkout_url) ``` ```go theme={null} package main import ( "fmt" "os" dodo "github.com/dodopayments/dodopayments-go" ) func main() { client := dodo.NewClient(dodo.Config{ BearerToken: os.Getenv("DODO_PAYMENTS_API_KEY"), Environment: dodo.TestMode, // defaults to LiveMode }) checkout, err := client.CheckoutSessions.Create(dodo.CheckoutSessionCreateParams{ ProductCart: []dodo.ProductCartItem{ { ProductID: "pdt_123", Quantity: 1, }, }, BillingAddress: &dodo.BillingAddress{ City: "SF", Country: "US", State: "CA", Street: "1 Market St", Zipcode: "94105", }, Customer: &dodo.CustomerRef{ CustomerID: "cus_123", }, ReturnURL: "https://example.com/billing/success", SubscriptionData: &dodo.SubscriptionData{ OnDemand: &dodo.OnDemandSubscription{ MandateOnly: true, // set false to collect an initial charge // ProductPrice: 1000, // optional: charge $10.00 now if mandate_only is false // ProductCurrency: "USD", // ProductDescription: "Custom initial charge", // AdaptiveCurrencyFeesInclusive: false, }, }, }) if err != nil { panic(err) } fmt.Println(checkout.CheckoutURL) } ``` ```bash theme={null} curl -X POST "$DODO_API/checkouts" \ -H "Authorization: Bearer $DODO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "product_cart": [ { "product_id": "pdt_123", "quantity": 1 } ], "customer": { "customer_id": "cus_123" }, "billing_address": { "street": "1 Market St", "city": "SF", "state": "CA", "country": "US", "zipcode": "94105" }, "subscription_data": { "on_demand": { "mandate_only": true } }, "return_url": "https://example.com/billing/success" }' ``` ```json Success theme={null} { "session_id": "cks_123", "checkout_url": "https://test.checkout.dodopayments.com/session/cks123" } ``` ## Charge an on-demand subscription After the mandate is authorized, create charges as needed. Endpoint: [POST /subscriptions/\{subscription\_id}/charge](/api-reference/subscriptions/create-charge) Key request fields (body): Amount to charge (in the smallest currency unit). Example: to charge \$25.00, pass 2500. Optional currency override for the charge. Optional description override for this charge. If true, includes adaptive currency fees within product\_price. If false, fees are added on top. Additional metadata for the payment. If omitted, the subscription metadata is used. ```javascript theme={null} import DodoPayments from 'dodopayments'; const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY }); async function chargeNow(subscriptionId) { const res = await client.subscriptions.charge(subscriptionId, { product_price: 2500 }); console.log(res.payment_id); } chargeNow('sub_123').catch(console.error); ``` ```python theme={null} import os from dodopayments import DodoPayments client = DodoPayments(bearer_token=os.environ.get('DODO_PAYMENTS_API_KEY')) response = client.subscriptions.charge( subscription_id="sub_123", product_price=2500, ) print(response.payment_id) ``` ```go theme={null} package main import ( "context" "fmt" "github.com/dodopayments/dodopayments-go" "github.com/dodopayments/dodopayments-go/option" ) func main() { client := dodopayments.NewClient(option.WithBearerToken("YOUR_API_KEY")) res, err := client.Subscriptions.Charge(context.TODO(), "sub_123", dodopayments.SubscriptionChargeParams{ ProductPrice: dodopayments.F(int64(2500)), }) if err != nil { panic(err) } fmt.Println(res.PaymentID) } ``` ```bash theme={null} curl -X POST "$DODO_API/subscriptions/sub_123/charge" \ -H "Authorization: Bearer $DODO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "product_price": 2500, "product_description": "Extra usage for March" }' ``` ```json Success theme={null} { "payment_id": "pay_abc123" } ``` Charging a subscription that is not on-demand may fail. Ensure the subscription has `on_demand: true` in its details before charging. ## Payment retries Our fraud detection system may block aggressive retry patterns (and can flag them as potential card testing). Follow a safe retry policy. Burst retry patterns can be flagged as fraudulent or suspected card testing by our risk systems and processors. Avoid clustered retries; follow the backoff schedule and time alignment guidance below. ### Principles for safe retry policies * **Backoff mechanism**: Use exponential backoff between retries. * **Retry limits**: Cap total retries (3–4 attempts max). * **Intelligent filtering**: Retry only on retryable failures (e.g., network/issuer errors, insufficient funds); never retry hard declines. * **Card testing prevention**: Do not retry failures like `DO_NOT_HONOR`, `STOLEN_CARD`, `LOST_CARD`, `PICKUP_CARD`, `FRAUDULENT`, `AUTHENTICATION_FAILURE`. * **Vary metadata (optional)**: If you maintain your own retry system, differentiate retries via metadata (e.g., `retry_attempt`). ### Suggested retry schedule (subscriptions) * **1st attempt**: Immediate when you create the charge * **2nd attempt**: After 3 days * **3rd attempt**: After 7 more days (10 days total) * **4th attempt (final)**: After another 7 days (17 days total) Final step: if still unpaid, mark the subscription as unpaid or cancel it, based on your policy. Notify the customer during the window to update their payment method. ### Avoid burst retries; align to authorization time * Anchor retries to the original authorization timestamp to avoid "burst" behavior across your portfolio. * Example: If the customer starts a trial or mandate at 1:10 pm today, schedule follow-up retries at 1:10 pm on subsequent days per your backoff (e.g., +3 days → 1:10 pm, +7 days → 1:10 pm). * Alternatively, if you store the last successful payment time `T`, schedule the next attempt at `T + X days` to preserve time-of-day alignment. Time-zone and DST: use a consistent time standard for scheduling and convert for display only to maintain intervals. ### Decline codes you should not retry * `STOLEN_CARD` * `DO_NOT_HONOR` * `FRAUDULENT` * `PICKUP_CARD` * `AUTHENTICATION_FAILURE` * `LOST_CARD` For a comprehensive list of decline reasons and whether they are user-correctable, see the [Transaction Failures](/api-reference/transaction-failures) documentation. Only retry on soft/temporary issues (e.g., `insufficient_funds`, `issuer_unavailable`, `processing_error`, network timeouts). If the same decline repeats, pause further retries. ### Implementation guidelines (no code) * Use a scheduler/queue that persists precise timestamps; compute next attempt at the exact time-of-day offset (e.g., `T + 3 days` at the same HH:MM). * Maintain and reference the last successful payment timestamp `T` to compute the next attempt; do not bunch multiple subscriptions at the same instant. * Always evaluate the last decline reason; stop retries for hard declines in the skip list above. * Cap concurrent retries per customer and per account to prevent accidental surges. * Communicate proactively: email/SMS the customer to update their payment method before the next scheduled attempt. * Use metadata only for observability (e.g., `retry_attempt`); never try to "evade" fraud/risk systems by rotating inconsequential fields. ## Track outcomes with webhooks Implement webhook handling to track the customer journey. See [Implementing Webhooks](/developer-resources/integration-guide#implementing-webhooks). * **subscription.active**: Mandate authorized and subscription activated * **subscription.failed**: Creation failed (e.g., mandate failure) * **subscription.on\_hold**: Subscription placed on hold (e.g., unpaid state) * **payment.succeeded**: Charge succeeded * **payment.failed**: Charge failed For on-demand flows, focus on `payment.succeeded` and `payment.failed` to reconcile usage-based charges. ## Testing and next steps Use your test API key to create the subscription with `payment_link: true`, then open the link and complete the mandate. Call the charge endpoint with a small `product_price` (e.g., `100`) and verify you receive `payment.succeeded`. Switch to your live API key once you have validated events and internal state updates. ## Troubleshooting * **422 Invalid Request**: Ensure `on_demand.mandate_only` is provided on creation and `product_price` is provided for charges. * **Currency errors**: If you override `product_currency`, confirm it's supported for your account and customer. * **No webhooks received**: Verify your webhook URL and signature secret configuration. # React Native Source: https://docs.dodopayments.com/developer-resources/react-native-integration Complete guide to integrating Dodo Payments SDK in your React Native application for secure payment processing # React Native SDK Integration The Dodo Payments React Native SDK enables you to build secure payment experiences in your native Android and iOS apps. Our SDK provides customizable UI components and screens for collecting payment details. * 📦 Install our SDK from [NPM](https://www.npmjs.com/package/dodopayments-react-native-sdk) * 📚 View our [demo repository](https://github.com/dodopayments/dodopayments-react-native-demo) for complete implementation examples * 🎥 Watch our [demo video](https://youtu.be/eicctkqK04Y) to see the Dodo Payments SDK in action