# Dodo Payments

Dodo Payments component

- Package: @bijectionhq/dodo-payments
- Category: Payments
- Status: Preview
- License: GPL-3.0
- Bijection source (repository access required): https://github.com/bijectionhq/dodo-payments/tree/7f87230383f3c3bad91a82e26671e5d5fee32136/packages/convex
- Upstream: https://github.com/dodopayments/dodo-adapters/tree/41f2a2c6d6f9541733780ddfb1ad0b84879e1e60/packages/convex
- Documentation: https://docs.bijection.com/components/using
- Directory: https://bijection.com/components/dodo-payments

## Get the source

Bijection repository access is required. Request access at https://bijection.com/contact.

```sh
git clone https://github.com/bijectionhq/dodo-payments.git
cd dodo-payments
git checkout 7f87230383f3c3bad91a82e26671e5d5fee32136
cd packages/convex
```

## Before using this component

Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute.

## Upstream package documentation

Recorded 2026-10-23. This describes @dodopayments/convex; check the Bijection adaptation separately.

The Dodo Payments Convex component integrates checkout sessions, customer portals, and webhook handling directly into Convex applications. It provides a complete billing solution with built-in webhook verification, customer identification mapping, and payment flow management. The component handles the complexity of payment processing while letting you focus on your business logic within Convex's serverless architecture.

### Use cases

• **SaaS subscription billing** - Handle recurring subscriptions, plan upgrades, and customer self-service portals for your AI or SaaS application
• **One-time product sales** - Create checkout flows for digital products, credits, or feature unlocks with automatic payment confirmation
• **Usage-based billing** - Process payments for API usage, compute credits, or consumption-based pricing models
• **Multi-tenant billing** - Manage separate billing for different organizations or workspaces within your application
• **Payment webhook processing** - Automatically sync payment status, subscription changes, and customer data to your Convex database

### How it works

The component follows Convex's component architecture pattern, requiring installation via `app.use(dodopayments)` in your convex.config.ts. You configure a `DodoPayments` instance with an `identify` function that maps your authenticated Convex users to Dodo Payments customers using internal queries.

Checkout and customer portal functionality is exposed through the `dodo.api()` methods, which you call from Convex actions. The `checkout` function accepts product carts and billing configuration, returning checkout URLs for frontend redirection. The `customerPortal` function generates self-service management links.

Webhook handling uses `createDodoWebhookHandler` in your http.ts file, providing event-specific handlers like `onPaymentSucceeded` and `onSubscriptionActive`. Each webhook handler receives Convex's `ActionCtx`, allowing you to use `ctx.runMutation()` to persist payment data directly to your database with automatic webhook signature verification.

### Upstream README

# Dodo Payments Convex Component

This component is the official way to integrate the Dodo Payments in your Convex project.

Features:

- Checkout Session: Integrate Dodo Payments Checkout with a couple of lines code.

- Customer Portal: Allow customers to manage subscriptions and details

- Webhooks: Handle webhooks efficiently. Just write your business logic and handle the events you want. Webhook verification is taken care by us

Detailed documentation can be found at [Dodo Payments Convex Component](https://docs.dodopayments.com/developer-resources/convex-component)

## Installation

```bash
npm install @dodopayments/convex
```

## Quick Start

> **Note:** This component does not define a schema. Define your own schema based on your application's needs.

### 1. Add Component to Convex Config

```typescript
// 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;
```

### 2. Set Up Environment Variables

Create a [Dodo Payments](https://dodopayments.com) account and get the API_KEY and WEBHOOK_SECRET

Add your environment variables in the Convex dashboard (**Settings** → **Environment Variables**). You can open the dashboard by running:

```sh
npx convex dashboard
```

Set the following variables:

- `DODO_PAYMENTS_API_KEY=your-api-key`
- `DODO_PAYMENTS_ENVIRONMENT=test_mode`
- `DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret` (if using webhooks)

> **Note:** Convex backend functions only read environment variables set in the Convex dashboard. `.env` files are ignored. Always set secrets in the Convex dashboard for both production and development.

### 3. Create Internal Query

First, create an internal query to fetch customers from your database. This will be used in the payment functions to identify customers.

```typescript
// convex/customers.ts
import { internalQuery } from "./_generated/server";
import { v } from "convex/values";

// Internal query to fetch customer by auth ID
// Customize this based on your database schema and authentication provider
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();
  },
});
```

### 4. Configure DodoPayments Component

```typescript
// 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 customer to a Dodo Payments customer
  // Customize it based on your authentication provider and customer database

  identify: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      return null; // Customer 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();
```

### 5. Define Payment Actions

```typescript
// 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) => {
    return await checkout(ctx, {
      payload: {
        product_cart: args.product_cart,
        return_url: args.returnUrl,
        billing_currency: "USD",
        feature_flags: {
          allow_discount_code: true,
        },
      },
    });
  },
});
```

### 6. Frontend Usage

```tsx
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 <button onClick={handleCheckout}>Buy Now</button>;
}
```

### 7. Set Up Webhooks (Optional)

For handling Dodo Payments webhooks, create a file `convex/http.ts`:

```typescript
// 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;
```

**Important:** 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.

**Important:** 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.

Add your webhook secret in the Convex dashboard (**Settings** → **Environment Variables**):

- `DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret`

---

## Prompt for LLM

```text
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 different types of 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.

DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_ENVIRONMENT=test_mode

Step 3: Create an internal query to fetch customers from your database.

// convex/customers.ts
import { internalQuery } from "./_generated/server";
import { v } from "convex/values";

// Internal query to fetch customer by auth ID
// Customize this based on your database schema and authentication provider
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 customer 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", 

[README truncated for prompt length]