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

# Adaptador Fastify

> Añade checkout de Dodo Payments, Customer Portal y controladores de rutas de webhook a una aplicación Fastify con el adaptador @dodopayments/fastify.

El adaptador `@dodopayments/fastify` proporciona a tu aplicación Fastify tres controladores de rutas: `Checkout` devuelve URL de checkout, `CustomerPortal` envía a un cliente al Customer Portal y `Webhooks` verifica las solicitudes de webhook y llama a tus controladores de eventos.

<CardGroup cols={2}>
  <Card title="Checkout Handler" icon="cart-shopping" href="#checkout-route-handler">
    Crea enlaces de pago y sesiones de checkout desde tu aplicación Fastify.
  </Card>

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

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

## Instalación

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

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

    El paquete requiere Fastify 5.4.0 o posterior.
  </Step>

  <Step title="Set Up Environment Variables">
    Crea un archivo `.env` en la raíz de tu proyecto:

    ```env expandable theme={null}
    DODO_PAYMENTS_API_KEY=your-api-key
    DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
    # test_mode or live_mode
    DODO_PAYMENTS_ENVIRONMENT=test_mode
    DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success
    ```

    Crea la API key en **Developer → API Keys**. Añade tu endpoint de webhook en **Developer → Webhooks** y copia su secreto de firma en `DODO_PAYMENTS_WEBHOOK_KEY`. Mientras desarrollas, utiliza una API key de modo de prueba con `DODO_PAYMENTS_ENVIRONMENT=test_mode`, porque una key de modo de prueba solo funciona con el modo de prueba. `DODO_PAYMENTS_RETURN_URL` es opcional.

    <Warning>
      Nunca confirmes tu archivo `.env` ni tus secretos en el control de versiones.
    </Warning>
  </Step>
</Steps>

## Ejemplos de controladores de rutas

<Info>
  Los ejemplos registran rutas en una instancia de Fastify creada con `Fastify()`. La ruta de webhook necesita el cuerpo sin procesar de la solicitud, por lo que su ejemplo añade un analizador de cuerpo de tipo string dentro de un plugin que contiene únicamente la ruta de webhook.
</Info>

<Tabs>
  <Tab title="Checkout Handler">
    <Info>
      Utiliza este controlador para integrar el checkout de Dodo Payments en tu aplicación Fastify. Admite flujos de pago estáticos (GET), dinámicos (POST) y de sesión (POST). `Checkout()` devuelve un `getHandler` para el flujo estático y un `postHandler` para los flujos dinámico y de sesión. Registra cada flujo POST en su propia ruta.
    </Info>

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

    <CodeGroup>
      ```bash Static Checkout cURL theme={null}
      curl --request GET \
        --url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh'
      ```
    </CodeGroup>

    <CodeGroup>
      ```bash Dynamic Checkout cURL expandable theme={null}
      curl --request POST \
        --url https://example.com/api/checkout \
        --header 'Content-Type: application/json' \
        --data '{
        "billing": {
          "city": "San Francisco",
          "country": "US",
          "state": "CA",
          "street": "123 Main St",
          "zipcode": "94102"
        },
        "customer": {
          "email": "test@example.com",
          "name": "John Doe"
        },
        "metadata": {},
        "payment_link": true,
        "product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
        "quantity": 1,
        "billing_currency": "USD",
        "discount_codes": ["IKHZ23M9GQ"],
        "return_url": "https://example.com",
        "trial_period_days": 10
      }'
      ```
    </CodeGroup>

    <CodeGroup>
      ```bash Checkout Session cURL expandable theme={null}
      curl --request POST \
        --url https://example.com/api/checkout-session \
        --header 'Content-Type: application/json' \
        --data '{
        "product_cart": [
          {
            "product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
            "quantity": 1
          }
        ],
        "customer": {
          "email": "test@example.com",
          "name": "John Doe"
        },
        "return_url": "https://example.com/success"
      }'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Customer Portal Handler">
    <Info>
      Utiliza este controlador para permitir que los clientes gestionen sus suscripciones y datos en el Customer Portal de Dodo Payments.
    </Info>

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

    <CodeGroup>
      ```bash Customer Portal cURL theme={null}
      curl --request GET \
        --url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Webhook Handler">
    <Info>
      Utiliza este controlador para recibir, verificar y procesar eventos de webhook de Dodo Payments en tu aplicación Fastify.
    </Info>

    <CodeGroup>
      ```typescript Fastify Route Handler expandable theme={null}
      // route.ts
      import Fastify from 'fastify'
      import { Webhooks } from '@dodopayments/fastify'

      const fastify = Fastify({})

      // Keep the raw-body parser inside this plugin, so other routes still receive parsed JSON
      fastify.register(async (webhookRoutes) => {
        webhookRoutes.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
          done(null, body)
        })

        webhookRoutes.post('/api/webhooks', Webhooks({
          webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
          onPayload: async (payload) => {
            // Handle Payload Here
            console.log(payload)
          }
        }))
      })
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Controlador de rutas de checkout

<Info>
  El adaptador admite los tres flujos de checkout de Dodo Payments. Define `type` en la configuración del controlador para elegir el flujo que sirve una ruta. Cada flujo responde con JSON que contiene un `checkout_url` que el cliente puede abrir.
</Info>

* **Enlaces de pago estáticos:** `type: "static"`, GET. Crea un enlace de pago para un producto a partir de parámetros de consulta, después de comprobar que el producto existe.
* **Enlaces de pago dinámicos:** `type: "dynamic"`, POST. Crea un pago único o una suscripción con un enlace de pago, según si el producto es recurrente.
* **Sesiones de checkout:** `type: "session"`, POST. Crea una sesión de checkout a partir de un carrito de productos y los datos del cliente. Utiliza este flujo para nuevas integraciones.

`Checkout` acepta estas opciones:

| Opción | Tipo | Descripción |
| - | - | - |
| `bearerToken` | `string` | Tu API key de Dodo Payments. |
| `environment` | `string` | `test_mode` o `live_mode`. El valor predeterminado es `live_mode`. |
| `returnUrl` | `string` | Opcional. La URL a la que checkout redirige al cliente después del pago. Un `return_url` en el cuerpo POST tiene prioridad. |
| `type` | `string` | El flujo de checkout: `static`, `dynamic` o `session`. |

`Checkout` devuelve un objeto con dos controladores. Registra `getHandler` para GET cuando `type` sea `static`, y `postHandler` para POST cuando `type` sea `dynamic` o `session`.

<AccordionGroup>
  <Accordion title="Static Checkout (GET)">
    ### Parámetros de consulta admitidos

    <ParamField query="productId" type="string" required>
      Identificador del producto, por ejemplo `?productId=pdt_nZuwz45WAs64n3l07zpQR`.
    </ParamField>

    <ParamField query="quantity" type="integer" default="1">
      Cantidad del producto.
    </ParamField>

    <ParamField query="fullName" type="string">
      Nombre completo del cliente. Se ignora si se proporciona `firstName` o `lastName`.
    </ParamField>

    <ParamField query="firstName" type="string">
      Nombre del cliente.
    </ParamField>

    <ParamField query="lastName" type="string">
      Apellido del cliente.
    </ParamField>

    <ParamField query="email" type="string">
      Dirección de correo electrónico del cliente.
    </ParamField>

    <ParamField query="country" type="string">
      País del cliente, como código ISO 3166-1 alpha-2.
    </ParamField>

    <ParamField query="addressLine" type="string">
      Dirección postal del cliente.
    </ParamField>

    <ParamField query="city" type="string">
      Ciudad del cliente.
    </ParamField>

    <ParamField query="state" type="string">
      Estado o provincia del cliente.
    </ParamField>

    <ParamField query="zipCode" type="string">
      Código postal del cliente.
    </ParamField>

    <ParamField query="disableFullName" type="boolean">
      Define `true` para desactivar el campo de nombre completo.
    </ParamField>

    <ParamField query="disableFirstName" type="boolean">
      Define `true` para desactivar el campo de nombre.
    </ParamField>

    <ParamField query="disableLastName" type="boolean">
      Define `true` para desactivar el campo de apellido.
    </ParamField>

    <ParamField query="disableEmail" type="boolean">
      Define `true` para desactivar el campo de correo electrónico.
    </ParamField>

    <ParamField query="disableCountry" type="boolean">
      Define `true` para desactivar el campo de país.
    </ParamField>

    <ParamField query="disableAddressLine" type="boolean">
      Define `true` para desactivar el campo de dirección.
    </ParamField>

    <ParamField query="disableCity" type="boolean">
      Define `true` para desactivar el campo de ciudad.
    </ParamField>

    <ParamField query="disableState" type="boolean">
      Define `true` para desactivar el campo de estado.
    </ParamField>

    <ParamField query="disableZipCode" type="boolean">
      Define `true` para desactivar el campo de código postal.
    </ParamField>

    <ParamField query="paymentCurrency" type="string">
      La moneda de pago, por ejemplo `USD`.
    </ParamField>

    <ParamField query="showCurrencySelector" type="boolean" default="true">
      Muestra u oculta el selector de moneda.
    </ParamField>

    <ParamField query="paymentAmount" type="number">
      Fija el importe cobrado, en unidades principales de moneda, por ejemplo `12.5` para \$12.50. Solo funciona con productos Pay What You Want y se ignora si está por debajo del precio mínimo del producto.
    </ParamField>

    <ParamField query="showDiscounts" type="boolean" default="true">
      Muestra u oculta la sección de descuentos.
    </ParamField>

    <ParamField query="metadata_*" type="string">
      Cualquier parámetro de consulta que comience con `metadata_` se pasa al checkout como metadata, por ejemplo `metadata_orderId=123`.
    </ParamField>

    Una bandera de desactivación solo tiene efecto cuando es `true` y el campo correspondiente tiene un valor, por ejemplo `email` con `disableEmail`. El controlador pasa estos parámetros a un [enlace de pago estático](/developer-resources/integration-guide#static-payment-links).

    <Warning>
      Si falta `productId`, el controlador devuelve una respuesta 400. Los parámetros de consulta no válidos o un producto que no exista en tu cuenta también producen una respuesta 400.
    </Warning>

    ### Formato de respuesta

    El checkout estático devuelve una respuesta JSON con la URL de checkout:

    ```json theme={null}
    {
      "checkout_url": "https://test.checkout.dodopayments.com/buy/pdt_fqJhl7pxKWiLhwQR042rh?quantity=1&redirect_url=https%3A%2F%2Fyourapp.com%2Fsuccess"
    }
    ```
  </Accordion>

  <Accordion title="Dynamic Checkout (POST)">
    * Envía los parámetros como un cuerpo JSON en una solicitud POST.
    * Admite pagos únicos y recurrentes. El controlador recupera el producto y crea una suscripción si el producto es recurrente; de lo contrario, crea un pago único.
    * El cuerpo necesita `billing` (con `street`, `city`, `state`, `country` y `zipcode`) e `customer`, además de `product_id` (con un `quantity` opcional) o `product_cart`. Las suscripciones necesitan `product_id`.
    * El controlador también reenvía `metadata`, `allowed_payment_method_types`, `billing_currency`, `discount_codes` (o el obsoleto `discount_code`), `return_url`, `show_saved_payment_methods` e `tax_id`. Para las suscripciones, también reenvía `addons`, `on_demand` e `trial_period_days`. Ignora los demás campos.
    * Para obtener detalles de los campos, consulta:
      * [Cuerpo de solicitud para un producto de pago único](https://docs.dodopayments.com/api-reference/payments/post-payments)
      * [Cuerpo de solicitud para un producto de suscripción](https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions)

    <Warning>
      El checkout dinámico llama a los endpoints obsoletos `POST /payments` e `POST /subscriptions`. Utiliza Checkout Sessions para nuevas integraciones.
    </Warning>

    ### Formato de respuesta

    El checkout dinámico devuelve una respuesta JSON con el enlace de pago como URL de checkout:

    ```json theme={null}
    {
      "checkout_url": "https://test.checkout.dodopayments.com/cbq"
    }
    ```
  </Accordion>

  <Accordion title="Checkout Sessions (POST)">
    Envía un payload de sesión de checkout como cuerpo JSON. El controlador crea una sesión de checkout que gestiona el flujo de pago completo para compras únicas y suscripciones, y devuelve su `checkout_url`. `product_cart` es obligatorio y debe contener al menos un producto.

    Cada `checkout_url` funciona una vez y caduca después de 24 horas, o después de 15 minutos cuando pasas `confirm: true`. Una sesión creada con `payment_method_id` no devuelve `checkout_url`, por lo que el controlador responde con 400.

    Consulta la [Guía de integración de Checkout Sessions](https://docs.dodopayments.com/developer-resources/checkout-session) para obtener más información y la lista completa de campos admitidos.

    ### Formato de respuesta

    Las sesiones de checkout devuelven una respuesta JSON con la URL de checkout:

    ```json theme={null}
    {
      "checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"
    }
    ```
  </Accordion>
</AccordionGroup>

## Controlador de rutas de Customer Portal

El controlador de rutas de Customer Portal crea una sesión de Customer Portal para el cliente en `customer_id` y redirige la solicitud al enlace del portal. `CustomerPortal` acepta las opciones `bearerToken` e `environment`, igual que `Checkout`. Si Dodo Payments no puede crear la sesión, el controlador devuelve 500.

### Parámetros de consulta

<ParamField query="customer_id" type="string" required>
  El ID del cliente para la sesión del portal, por ejemplo `?customer_id=cus_123`.
</ParamField>

<ParamField query="send_email" type="boolean">
  Si se define como `true`, envía al cliente un correo electrónico con el enlace del portal.
</ParamField>

<Warning>
  Devuelve 400 si falta `customer_id`. El controlador no autentica la solicitud y abre el portal para cualquier `customer_id` que reciba, por lo que debes proteger la ruta con tu propia autenticación y pasar únicamente el ID de cliente del usuario que ha iniciado sesión.
</Warning>

## Controlador de rutas de webhook

El controlador de webhook verifica cada solicitud con tu secreto de webhook, proporcionado como `webhookKey`, y después llama a tus controladores de eventos.

<Warning>
  El controlador de webhook necesita el cuerpo sin procesar de la solicitud como string, por lo que debes añadir un analizador de tipo de contenido para `application/json` con `parseAs: 'string'`. Fastify aplica un analizador a todas las rutas del ámbito en el que lo añadas. Añádelo dentro de un plugin que registre únicamente la ruta de webhook, como en el ejemplo. En la instancia raíz, también pasaría un string a los controladores POST de checkout, que devolverían 400.
</Warning>

* **Método:** Solo se admiten solicitudes POST. Los demás métodos devuelven 405.
* **Verificación de firma:** Verifica los encabezados `webhook-id`, `webhook-timestamp` e `webhook-signature` con `webhookKey`, siguiendo la especificación de [Standard Webhooks](https://standardwebhooks.com/). Devuelve 401 si la verificación falla.
* **Validación del payload:** Se valida con Zod. Devuelve 400 para payloads no válidos.
* **Gestión de errores:**
  * 401: Firma no válida
  * 400: Payload no válido
  * 500: Error interno durante la verificación
* **Enrutamiento de eventos:** Llama a `onPayload` para cada evento, después al controlador correspondiente al tipo de evento y devuelve 200 cuando terminan. El controlador no captura los errores que arrojen tus controladores de eventos.

### Controladores de eventos de webhook admitidos

Todos los controladores son opcionales y asíncronos. Para consultar el payload de cada evento, consulta la [Guía de eventos de webhook](/developer-resources/webhooks/intents/webhook-events-guide).

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

***

## Prompt para LLM

```text theme={null}
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 a 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

The package requires Fastify 5.4.0 or later.

---

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 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 Fastify app for static (GET), dynamic (POST), and checkout sessions (POST). Checkout() returns getHandler for the static flow and postHandler for the dynamic and session flows. Give each POST flow its own path.

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. A return_url in the POST body takes precedence.

    environment: "test_mode" or "live_mode" (defaults to "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://test.checkout.dodopayments.com/buy/pdt_fqJhl7pxKWiLhwQR042rh?quantity=1"}

POST (dynamic checkout) expects a JSON body with payment details (one-time or subscription). It requires billing and customer, plus product_id or product_cart. Returns: {"checkout_url": "https://test.checkout.dodopayments.com/cbq"}. 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, and product_cart is required. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"}. 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. It redirects the request to the portal link.

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. The handler does not authenticate the request, so protect the route with the app's own authentication and pass only the signed-in user's customer ID.

If Webhook Route Handler is selected:

Purpose: Processes incoming webhook events from Dodo Payments to trigger events in your app.

Integration:

The handler needs the raw request body as a string. Add the string content type parser inside a plugin that contains only the webhook route, so the POST checkout handlers still receive parsed JSON.

import Fastify from 'fastify'
import { Webhooks } from '@dodopayments/fastify'

const fastify = Fastify({})

fastify.register(async (webhookRoutes) => {
  webhookRoutes.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
    done(null, body)
  })

  webhookRoutes.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. onPayload runs for every event, before the event-specific handler.

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, onSubscriptionPaused, onSubscriptionUnpaused, onSubscriptionUpdatePaymentMethod

    onLicenseKeyCreated

    onAbandonedCheckoutDetected, onAbandonedCheckoutRecovered

    onDunningStarted, onDunningRecovered

    onCreditAdded, onCreditDeducted, onCreditExpired, onCreditRolledOver, onCreditRolloverForfeited, onCreditOverageCharged, onCreditOverageReset, onCreditManualAdjustment, onCreditBalanceLow

    onEntitlementGrantCreated, onEntitlementGrantDelivered, onEntitlementGrantFailed, onEntitlementGrantRevoked

    onPayoutCreated, onPayoutOnHold, onPayoutInProgress, onPayoutFailed, onPayoutSuccess

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=https://yourapp.com/success

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


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