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

# Componente Convex

> Añade el checkout de Dodo Payments, Customer Portal y la gestión de webhooks a tu backend de Convex con el componente @dodopayments/convex.

El componente `@dodopayments/convex` añade Dodo Payments a tu backend de Convex. Proporciona una función `checkout` que crea sesiones de checkout, una función `customerPortal` que abre el Customer Portal para el usuario que ha iniciado sesión y `createDodoWebhookHandler`, que verifica los webhooks en una acción HTTP de Convex. Requiere Convex 1.26 o posterior.

<CardGroup cols={2}>
  <Card title="Checkout Function" icon="cart-shopping" href="#checkout-function">
    Crea sesiones de checkout desde las acciones de Convex.
  </Card>

  <Card title="Customer Portal" icon="user" href="#customer-portal-function">
    Permite que los clientes gestionen sus suscripciones y datos.
  </Card>

  <Card title="Webhooks" icon="bell" href="#webhook-handler">
    Recibe y procesa eventos de webhook de Dodo Payments.
  </Card>
</CardGroup>

## Instalación

<Steps>
  <Step title="Install the Package">
    Ejecuta este comando en la raíz de tu proyecto:

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

  <Step title="Add Component to Convex Config">
    Añade el componente de Dodo Payments a tu configuración de 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;
    ```

    Después de editar `convex.config.ts`, ejecuta `npx convex dev` una vez para generar los tipos.
  </Step>

  <Step title="Set Up Environment Variables">
    Configura las variables de entorno en tu panel de Convex, en **Settings → Environment Variables**. Para abrir el panel, ejecuta:

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

    Añade estas variables de entorno:

    * `DODO_PAYMENTS_API_KEY`: tu clave de API de Dodo Payments, disponible en **Developer → API Keys** en el panel de Dodo Payments.
    * `DODO_PAYMENTS_ENVIRONMENT`: `test_mode` o `live_mode`.
    * `DODO_PAYMENTS_WEBHOOK_SECRET`: tu secreto de webhook, disponible en **Developer → Webhooks**. Es obligatorio para gestionar webhooks. El controlador de webhooks lee exactamente este nombre de variable.

    <Warning>
      Almacena los secretos como variables de entorno de Convex. Las funciones del backend de Convex no leen archivos `.env`. Nunca incluyas secretos en el control de versiones.
    </Warning>
  </Step>
</Steps>

## Ejemplos de configuración del componente

<Steps>
  <Step title="Create Internal Query">
    Crea una consulta interna que busque un cliente en tu base de datos mediante su ID de autenticación. La función `identify` del siguiente paso la utiliza para obtener el ID de cliente de Dodo Payments del usuario que ha iniciado sesión para el portal de clientes.

    <Warning>
      El componente no define un esquema. Antes de utilizar esta consulta, define una tabla `customers` con un índice `by_auth_id` en `convex/schema.ts`, o cambia la consulta para que coincida con tu esquema existente.
    </Warning>

    ```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();
      },
    });
    ```
  </Step>

  <Step title="Configure DodoPayments Component">
    Crea el cliente. `identify` asigna el usuario de Convex que ha iniciado sesión a un ID de cliente de Dodo Payments. Devuelve `null` si ningún usuario ha iniciado sesión o no hay ningún cliente coincidente.

    <CodeGroup>
      ```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();
      ```
    </CodeGroup>
  </Step>
</Steps>

A continuación, añade las funciones que necesites:

<Tabs>
  <Tab title="Checkout Function Setup">
    <Info>
      Utiliza esta función para añadir el checkout de Dodo Payments a tu aplicación de Convex. Crea una sesión de checkout a partir de los campos aceptados por el validador de payload de checkout del componente.
    </Info>

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

  <Tab title="Customer Portal Setup">
    <Info>
      Utiliza esta función para permitir que los clientes gestionen sus suscripciones y datos en el Customer Portal de Dodo Payments. La función `identify` busca al cliente.
    </Info>

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

  <Tab title="Webhook Handler Setup">
    <Info>
      Utiliza este controlador para recibir eventos de webhook de Dodo Payments en tu aplicación de Convex y verificar sus firmas. Cada controlador de webhook recibe el `ActionCtx` de Convex como primer parámetro, por lo que puedes llamar a `ctx.runQuery()` y `ctx.runMutation()` para trabajar con tu base de datos.
    </Info>

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

    <Warning>
      Define la mutación de base de datos que llama cada controlador. Por ejemplo, crea una mutación `createPayment` para registrar pagos completados, o una mutación `createSubscription` para realizar un seguimiento del estado de la suscripción. Este ejemplo espera encontrarlas en `convex/webhooks.ts`.
    </Warning>

    En el panel de Dodo Payments, añade el endpoint en **Developer → Webhooks**. Convex sirve acciones HTTP desde el dominio `.convex.site` de tu implementación, por lo que la URL es `https://<your-deployment-name>.convex.site/dodopayments-webhook`.
  </Tab>
</Tabs>

## Función de checkout

El componente de Convex crea sesiones de checkout, el flujo de checkout recomendado para todos los pagos. Una sesión contiene el carrito de productos, los datos del cliente y las opciones de checkout.

### Uso

Llama a `checkout` desde una acción de Convex, con los campos de la sesión de checkout en `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` no llama a `identify`. Para asociar un cliente existente, pasa `customer: { customer_id }` en el payload. Para obtener más información y la lista completa de campos compatibles, consulta [Checkout Sessions](/developer-resources/checkout-session).

Una sesión creada con `payment_method_id` no devuelve ninguna URL de checkout, por lo que `checkout` genera un error en ese caso.

### Formato de respuesta

La función de checkout devuelve un objeto con la URL de checkout:

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

## Función de Customer Portal

La función del portal de clientes devuelve una URL de Customer Portal para el usuario que ha iniciado sesión.

### Uso

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

Devuelve un objeto con un campo `portal_url`.

### Parámetros

<ParamField query="send_email" type="boolean" default="false">
  Si se establece en `true`, Dodo Payments también envía por correo electrónico el enlace al portal al cliente.
</ParamField>

<Info>
  `customerPortal` obtiene el cliente de la función `identify` en tu configuración de DodoPayments, que debe devolver el `dodoCustomerId` del cliente. Si `identify` devuelve `null`, `customerPortal` genera un error `User is not authenticated.`.
</Info>

## Controlador de webhook

`createDodoWebhookHandler` verifica cada solicitud antes de ejecutar tu código:

* **Method:** Registra la ruta con `method: "POST"`. Las solicitudes con otros métodos no llegan al controlador.
* **Signature Verification:** Verifica la firma de Standard Webhooks con la variable de entorno `DODO_PAYMENTS_WEBHOOK_SECRET`. Devuelve 400 si la verificación falla.
* **Payload Validation:** Se valida con Zod. Devuelve 400 para payloads no válidos.
* **Error Handling:**
  * 400: Firma no válida, payload no válido o un error generado por uno de tus controladores
  * 200: Todos los controladores han finalizado
  * Si `DODO_PAYMENTS_WEBHOOK_SECRET` no está configurada, el controlador genera un error y la solicitud falla.
* **Event Routing:** Llama a `onPayload` para cada evento y, después, al controlador correspondiente al tipo de evento.

### Controladores de eventos de webhook compatibles

Cada controlador recibe el `ActionCtx` de Convex y el payload verificado correspondiente a su tipo de evento:

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

## Uso en el frontend

Llama a las acciones de checkout y del portal desde tus componentes de React con el hook `useAction` de `convex/react`.

<CodeGroup>
  ```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>;
  }
  ```
</CodeGroup>

<CodeGroup>
  ```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>;
  }
  ```
</CodeGroup>

## Prompt para LLM

Copia este prompt en tu asistente de programación con IA para que añada el componente a tu proyecto. Para proporcionar también a tu agente la documentación y las skills de Dodo Payments, instala el [Agent Plugin](/developer-resources/build-with-ai-coding-agents).

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