> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 凸包组件

> 通过 @dodopayments/convex component 将 Dodo Payments checkout、Customer Portal 和 webhook 处理添加到您的 Convex 后端。

`@dodopayments/convex` component 将 Dodo Payments 添加到您的 Convex 后端。它提供一个 `checkout` 函数来创建 checkout sessions，一个 `customerPortal` 函数来为已登录用户打开 Customer Portal，以及 `createDodoWebhookHandler`，用于在 Convex HTTP action 中验证 webhooks。要求 Convex 1.26 或更高版本。

> **[Checkout Function](#checkout-function)**
>
> 从 Convex actions 创建 checkout sessions。

> **[Customer Portal](#customer-portal-function)**
>
> 让客户管理其订阅和详细信息。

> **[Webhooks](#webhook-handler)**
>
> 接收并处理 Dodo Payments webhook events。

## 安装

**1. Install the Package**

在项目根目录中运行此命令：

```bash theme={null}
npm install @dodopayments/convex
```

**2. Add Component to Convex Config**

将 Dodo Payments component 添加到您的 Convex 配置中：

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

编辑 `convex.config.ts` 后，运行一次 `npx convex dev` 以生成 types。

**3. Set Up Environment Variables**

在 Convex dashboard 的 **Settings → Environment Variables** 下设置环境变量。要打开 dashboard，请运行：

```bash theme={null}
npx convex dashboard
```

添加以下环境变量：

* `DODO_PAYMENTS_API_KEY`：您的 Dodo Payments API key，可在 Dodo Payments dashboard 的 **Developer → API Keys** 中获取。
* `DODO_PAYMENTS_ENVIRONMENT`：`test_mode` 或 `live_mode`。
* `DODO_PAYMENTS_WEBHOOK_SECRET`：您的 webhook secret，可在 **Developer → Webhooks** 中获取。处理 webhook 时必需。webhook handler 会读取此确切的变量名称。

> [!WARNING]
>
> 将 secrets 存储为 Convex environment variables。Convex backend functions 不会读取 `.env` 文件。切勿将 secrets 提交到版本控制系统。

## Component 设置示例

**1. Create Internal Query**

创建一个 internal query，通过 auth ID 在您的数据库中查找客户。下一步中的 `identify` 函数会使用它获取已登录用户的 Dodo Payments customer ID，以用于 customer portal。

> [!WARNING]
>
> 该 component 不定义 schema。在使用此 query 前，请在 `convex/schema.ts` 中定义一个带有 `by_auth_id` index 的 `customers` table，或修改 query 以匹配您现有的 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();
  },
});
```

**2. Configure DodoPayments Component**

创建 client。`identify` 会将已登录的 Convex user 映射到 Dodo Payments customer ID。如果没有用户登录或没有匹配的 customer，它会返回 `null`。

```typescript Convex Component Setup expandable theme={null}
// convex/dodo.ts
import { DodoPayments, type 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();
```

然后添加您需要的 functions：

**Checkout Function Setup**

> [!NOTE]
>
> 使用此 function 将 Dodo Payments checkout 添加到您的 Convex app。它会根据 component 的 checkout payload validator 所接受的字段创建 checkout session。

```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.");
    }
  },
});
```

**Customer Portal Setup**

> [!NOTE]
>
> 使用此 function 让客户在 Dodo Payments Customer Portal 中管理其订阅和详细信息。`identify` function 会查找 customer。

```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.");
    }
  },
});
```

**Webhook Handler Setup**

> [!NOTE]
>
> 使用此 handler 在您的 Convex app 中接收 Dodo Payments webhook events 并验证其 signatures。每个 webhook handler 都会将 Convex `ActionCtx` 作为第一个参数，因此您可以调用 `ctx.runQuery()` 和 `ctx.runMutation()` 来操作数据库。

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

> [!WARNING]
>
> 定义每个 handler 调用的 database mutation。例如，创建一个 `createPayment` mutation 来记录成功的 payments，或创建一个 `createSubscription` mutation 来跟踪 subscription state。此示例要求它们位于 `convex/webhooks.ts` 中。

在 Dodo Payments dashboard 中，于 **Developer → Webhooks** 下添加 endpoint。Convex 会从您的 deployment 的 `.convex.site` domain 提供 HTTP actions，因此 URL 为 `https://<your-deployment-name>.convex.site/dodopayments-webhook`。

## Checkout Function

Convex component 会创建 checkout sessions，这是所有 payments 推荐的 checkout flow。一个 session 包含 product cart、customer details 和 checkout options。

### 用法

从 Convex action 调用 `checkout`，并将 checkout session fields 传入 `payload`：

```typescript theme={null}
const result = await checkout(ctx, {
  payload: {
    product_cart: [{ product_id: "pdt_123", quantity: 1 }],
    customer: { email: "user@example.com" },
    return_url: "https://example.com/success"
  }
});
```

`checkout` 不会调用 `identify`。要关联现有 customer，请在 payload 中传入 `customer: { customer_id }`。有关更多详细信息及支持字段的完整列表，请参阅 [Checkout Sessions](https://docs.dodopayments.com/developer-resources/checkout-session.md)。

使用 `payment_method_id` 创建的 session 不会返回 checkout URL，因此 `checkout` 会对其抛出 error。

### Response Format

checkout function 返回一个包含 checkout URL 的 object：

```json theme={null}
{
  "checkout_url": "https://checkout.dodopayments.com/session/..."
}
```

## Customer Portal Function

customer portal function 会为已登录用户返回一个 Customer Portal URL。

### 用法

```typescript theme={null}
const result = await customerPortal(ctx, {
  send_email: false
});
```

它会返回一个包含 `portal_url` field 的 object。

### Parameters

**send\_email** `boolean` (default: false)

如果设置为 `true`，Dodo Payments 还会将 portal link 通过 email 发送给 customer。

> [!NOTE]
>
> `customerPortal` 会从 DodoPayments setup 中的 `identify` function 获取 customer，该 function 必须返回 customer 的 `dodoCustomerId`。如果 `identify` 返回 `null`，`customerPortal` 会抛出 `User is not authenticated.` error。

## Webhook Handler

`createDodoWebhookHandler` 会在运行您的代码前验证每个 request：

* **Method：** 使用 `method: "POST"` 注册 route。使用其他 methods 的 requests 不会到达 handler。
* **Signature Verification：** 使用 `DODO_PAYMENTS_WEBHOOK_SECRET` environment variable 验证 Standard Webhooks signature。验证失败时返回 400。
* **Payload Validation：** 使用 Zod 验证。payload 无效时返回 400。
* **Error Handling：**
  * 400：signature 无效、payload 无效，或某个 handler 抛出 error
  * 200：所有 handlers 已完成
  * 如果未设置 `DODO_PAYMENTS_WEBHOOK_SECRET`，handler 会抛出 error，请求失败。
* **Event Routing：** 对每个 event 调用 `onPayload`，然后调用该 event type 对应的 handler。

### 支持的 Webhook Event Handlers

每个 handler 都会接收 Convex `ActionCtx` 以及其 event type 对应的已验证 payload：

```typescript TypeScript expandable theme={null}
onPayload?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPaymentSucceeded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPaymentFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPaymentProcessing?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPaymentCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onRefundSucceeded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onRefundFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeOpened?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeAccepted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeChallenged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeWon?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeLost?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionActive?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionOnHold?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionRenewed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionPlanChanged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionUpdated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionPaused?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionUnpaused?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionUpdatePaymentMethod?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onLicenseKeyCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onAbandonedCheckoutDetected?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onAbandonedCheckoutRecovered?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDunningStarted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDunningRecovered?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditAdded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditDeducted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditRolledOver?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditRolloverForfeited?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditOverageCharged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditOverageReset?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditManualAdjustment?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditBalanceLow?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onEntitlementGrantCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onEntitlementGrantDelivered?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onEntitlementGrantFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onEntitlementGrantRevoked?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutOnHold?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutInProgress?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutSuccess?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
```

## Frontend 用法

在 React components 中使用 `convex/react` 的 `useAction` hook 调用 checkout 和 portal actions。

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

```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 <button onClick={handlePortal}>Manage Subscription</button>;
}
```

## 面向 LLM 的 Prompt

将此 prompt 复制到您的 AI coding assistant 中，让它将 component 添加到您的项目中。若还要向 agent 提供 Dodo Payments 文档和 skills，请安装 [Agent Plugin](https://docs.dodopayments.com/developer-resources/build-with-ai-coding-agents.md)。

```text expandable theme={null}
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, type 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: "pdt_123", quantity: 1 }],
    });
    window.location.href = checkout_url;
  };

  return <button onClick={handleCheckout}>Buy Now</button>;
}

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 <button onClick={handlePortal}>Manage Subscription</button>;
}

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.

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. The webhook handler reads this exact variable name.

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://<your-deployment-name>.convex.site/dodopayments-webhook`. Convex serves HTTP actions from the deployment's .convex.site domain, not .convex.cloud.

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.
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.