> ## 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 de Bun

> Añade el checkout de Dodo Payments, Customer Portal y manejadores de webhooks a un servidor Bun creado con Bun.serve() mediante el paquete @dodopayments/bun.

El paquete `@dodopayments/bun` proporciona a tu servidor Bun tres manejadores de solicitudes. `Checkout` devuelve URLs de checkout, `CustomerPortal` envía al cliente a Customer Portal y `Webhooks` verifica los eventos de webhook y los redirige a tu código. Cada manejador recibe un `Request` estándar y devuelve un `Response`, por lo que debes llamarlo desde el manejador `fetch` de `Bun.serve()`.

<CardGroup cols={2}>
  <Card title="Checkout Handler" icon="cart-shopping" href="#checkout-route-handler">
    Crea URLs de checkout mediante flujos estáticos, dinámicos y de sesiones de checkout.
  </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">
    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}
    bun add @dodopayments/bun
    ```

    El paquete también necesita `zod` 3.25 o posterior, que aparece como dependencia entre pares.
  </Step>

  <Step title="Set Up Environment Variables">
    Crea un archivo `.env` en la raíz de tu proyecto. Crea la API key en **Developer → API Keys**. Añade tu endpoint de webhook en **Developer → Webhooks** y copia su **Signing secret** en `DODO_PAYMENTS_WEBHOOK_KEY`:

    ```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://yourdomain.com/checkout/success
    ```

    Bun lee automáticamente los archivos `.env`, por lo que los ejemplos leen estos valores desde `process.env`. `DODO_PAYMENTS_RETURN_URL` es el destino al que llegan los clientes después del checkout. Si no proporcionas un entorno, los manejadores usan `live_mode`. Una API key del modo de prueba solo funciona con `test_mode`.

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

## Ejemplos de manejadores de rutas

<Info>
  Todos los ejemplos usan el servidor nativo de Bun, `Bun.serve()`, y enrutan las solicitudes según la ruta y el método en su manejador `fetch`.
</Info>

<Tabs>
  <Tab title="Checkout Handler">
    <Info>
      Usa este manejador para añadir el checkout de Dodo Payments a tu servidor Bun. El manejador estático sirve las solicitudes `GET`. Los manejadores de sesión y dinámico sirven las solicitudes `POST`. El ejemplo de checkout dinámico supone que el servidor devuelve `dynamicCheckoutHandler(request)` para las solicitudes `POST`.
    </Info>

    <CodeGroup>
      ```typescript Bun Server Handler expandable theme={null}
      import { Checkout } from '@dodopayments/bun';

      const staticCheckoutHandler = Checkout({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
          type: "static"
      });

      const sessionCheckoutHandler = Checkout({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
          type: "session"
      });

      const dynamicCheckoutHandler = Checkout({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
          type: "dynamic"
      });

      Bun.serve({
        port: 3000,
        fetch(request) {
          const url = new URL(request.url);
          
          if (url.pathname === "/api/checkout") {
            if (request.method === "GET") {
              return staticCheckoutHandler(request);
            }
            if (request.method === "POST") {
              return sessionCheckoutHandler(request);
              // or return dynamicCheckoutHandler(request);
            }
          }
          
          return new Response("Not Found", { status: 404 });
        },
      });
      ```
    </CodeGroup>

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

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

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

  <Tab title="Customer Portal Handler">
    <Info>
      Usa este manejador para enviar a los clientes a Dodo Payments Customer Portal, donde pueden gestionar sus suscripciones y datos.
    </Info>

    <CodeGroup>
      ```typescript Bun Server Handler expandable theme={null}
      import { CustomerPortal } from "@dodopayments/bun";

      const customerPortalHandler = CustomerPortal({
        bearerToken: process.env.DODO_PAYMENTS_API_KEY,
        environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
      });

      Bun.serve({
        port: 3000,
        fetch(request) {
          const url = new URL(request.url);
          
          if (url.pathname === "/api/customer-portal" && request.method === "GET") {
            return customerPortalHandler(request);
          }
          
          return new Response("Not Found", { status: 404 });
        },
      });
      ```
    </CodeGroup>

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

  <Tab title="Webhook Handler">
    <Info>
      Usa este manejador para recibir eventos de webhook de Dodo Payments en tu servidor Bun y verificar sus firmas.
    </Info>

    <CodeGroup>
      ```typescript Bun Server Handler expandable theme={null}
      import { Webhooks } from "@dodopayments/bun";

      const webhookHandler = Webhooks({
        webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
        onPayload: async (payload) => {
          // handle the payload
        },
        // ... other event handlers for granular control
      });

      Bun.serve({
        port: 3000,
        fetch(request) {
          const url = new URL(request.url);
          
          if (url.pathname === "/api/webhook/dodo-payments" && request.method === "POST") {
            return webhookHandler(request);
          }
          
          return new Response("Not Found", { status: 404 });
        },
      });
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Manejador de rutas de checkout

El manejador de checkout admite las tres formas de aceptar pagos con Dodo Payments:

* **Static Payment Links:** URLs que se pueden compartir y que recaudan pagos sin código.
* **Dynamic Payment Links:** enlaces de pago que generas con detalles personalizados. Usan endpoints obsoletos.
* **Checkout Sessions:** checkout alojado con un carrito de productos, datos del cliente y opciones de personalización. Este es el flujo recomendado.

`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 el checkout redirige al cliente después del pago. Un `return_url` en el cuerpo de un POST tiene prioridad. |
| `type` | `string` | El flujo de checkout: `static`, `dynamic` o `session`. El valor predeterminado es `static`. |

El manejador sirve el checkout estático para las solicitudes `GET`. Para las solicitudes `POST`, crea un enlace de pago dinámico cuando `type` es `dynamic`, y una sesión de checkout en los demás casos.

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

    <ParamField query="productId" type="string" required>
      Identificador del producto, por ejemplo `?productId=pdt_xxx`.
    </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">
      Establécelo en `true` para desactivar el campo de nombre completo.
    </ParamField>

    <ParamField query="disableFirstName" type="boolean">
      Establécelo en `true` para desactivar el campo de nombre.
    </ParamField>

    <ParamField query="disableLastName" type="boolean">
      Establécelo en `true` para desactivar el campo de apellido.
    </ParamField>

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

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

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

    <ParamField query="disableCity" type="boolean">
      Establécelo en `true` para desactivar el campo de ciudad.
    </ParamField>

    <ParamField query="disableState" type="boolean">
      Establécelo en `true` para desactivar el campo de estado.
    </ParamField>

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

    <ParamField query="paymentCurrency" type="string">
      Moneda del 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 la 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 por `metadata_` se envía al checkout como metadata, por ejemplo `metadata_orderId=123`.
    </ParamField>

    Una marca de desactivación solo tiene efecto cuando el campo correspondiente tiene un valor, por ejemplo `email` con `disableEmail=true`. El manejador añade `returnUrl` de su configuración al enlace como `redirect_url`.

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

    ### Formato de respuesta

    El checkout estático devuelve una respuesta JSON con la URL de checkout. En el modo de prueba, la URL usa `test.checkout.dodopayments.com`:

    ```json theme={null}
    {
      "checkout_url": "https://test.checkout.dodopayments.com/buy/pdt_xxx?quantity=1&redirect_url=https%3A%2F%2Fyourdomain.com%2Fcheckout%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 manejador recupera el producto y después crea una suscripción si el producto es recurrente, o un pago único en caso contrario.
    * El cuerpo necesita `billing` (con `street`, `city`, `state`, `country` y `zipcode`) e `customer`, además de `product_id` o `product_cart`. Las suscripciones necesitan `product_id`.
    * Para consultar todos los campos compatibles del cuerpo, visita:
      * [Cuerpo de la solicitud para un producto de pago único](https://docs.dodopayments.com/api-reference/payments/post-payments)
      * [Cuerpo de la solicitud para un producto de suscripción](https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions)

    <Warning>
      El checkout dinámico actúa como proxy de los endpoints obsoletos `POST /payments` y `POST /subscriptions`. Sigue funcionando para las integraciones existentes, pero las integraciones nuevas deben usar sesiones de checkout.
    </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)">
    Las sesiones de checkout crean un checkout alojado para compras únicas y suscripciones, con control total sobre la personalización. `product_cart` es el único campo obligatorio y necesita al menos un producto. Si el cuerpo no contiene `return_url`, el manejador usa `returnUrl` de su configuración.

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

    Para obtener más información y consultar todos los campos compatibles, visita la [Guía de integración de Checkout Sessions](https://docs.dodopayments.com/developer-resources/checkout-session).

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

## Manejador de rutas de Customer Portal

El manejador de rutas de Customer Portal crea una sesión de Customer Portal para el cliente proporcionado y redirige el navegador a ella. `CustomerPortal` acepta las mismas opciones `bearerToken` e `environment` que `Checkout`.

<Warning>
  El manejador no comprueba quién lo está llamando. Cualquiera que lo solicite con un ID de cliente obtiene el portal de ese cliente. Protege la ruta con tu propia autenticación y pasa únicamente el ID de cliente del usuario que ha iniciado sesión.
</Warning>

### Parámetros de consulta

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

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

El manejador devuelve 400 si falta `customer_id` y 500 si no se puede crear la sesión del portal.

## Manejador de rutas de webhook

El manejador de rutas de webhook verifica cada solicitud con tu secreto de webhook, proporcionado como `webhookKey`, antes de ejecutar tu código:

* **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`, conforme a la especificación Standard Webhooks. Devuelve 401 si la verificación falla.
* **Validación de la carga útil:** Analiza el cuerpo como JSON y lo valida con Zod. Devuelve 400 si el JSON o la carga útil no son válidos.
* **Gestión de errores:**
  * 401: Firma no válida
  * 400: Carga útil no válida
  * 500: Error interno durante la verificación
* **Enrutamiento de eventos:** Llama a `onPayload` para cada evento, después al manejador correspondiente al tipo de evento y devuelve 200.

El adaptador no captura los errores lanzados en tus manejadores. Se propagan a `Bun.serve()` y la solicitud falla.

### Manejadores de eventos de webhook compatibles

Todos los manejadores son opcionales y asíncronos, y reciben la carga útil verificada correspondiente a su tipo de evento:

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

Para saber qué significa cada evento, consulta la [Guía de eventos de webhook](/developer-resources/webhooks/intents/webhook-events-guide).

## Prompt para LLM

Copia este prompt en tu asistente de programación con IA para que añada el adaptador 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 Bun developer assistant. Your task is to guide a user through integrating the @dodopayments/bun adapter into their existing Bun project.

The @dodopayments/bun adapter provides handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities. Each handler takes a Web Standard Request and returns a Response, so it works with Bun's native server, Bun.serve().

First, install the necessary package:

bun add @dodopayments/bun

Here's how you should structure your response:

    Ask the user which functionalities they want to integrate.

"Which parts of the @dodopayments/bun adapter would you like to integrate into your project? You can choose one or more of the following:
1. Checkout (static, dynamic, or session-based)
2. Customer Portal
3. Webhooks"

    Based on the user's selection, provide step-by-step integration instructions.

For each selected functionality, show:

    The environment variables required
    The exact code to add to their server file
    Where to place the code in their Bun.serve() configuration

Provide complete, working examples that the user can copy and paste directly into their project.

    Environment Variables Setup

Always include instructions for setting up the .env file. Bun reads .env files automatically:

DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT=test_mode
DODO_PAYMENTS_RETURN_URL=your-return-url

DODO_PAYMENTS_ENVIRONMENT is test_mode or live_mode. The handlers default to live_mode. Remind the user to never commit their .env file to version control.

    For Checkout Integration

If the user selects Checkout, ask which type they need:

    Static checkout (GET requests with query parameters)
    Dynamic checkout (POST requests with JSON body)
    Checkout sessions (POST requests with product cart, recommended for new integrations)

Provide the appropriate handler code for their selection. Every checkout type returns JSON with a checkout_url.

    For Customer Portal Integration

Provide a complete example showing how to integrate the customer portal handler into their Bun server. The handler does not authenticate the caller, so protect the route and pass only the signed-in user's customer ID.

    For Webhook Integration

Provide a complete example showing how to integrate the webhook handler with available event handlers for granular control.

    Full Example

If the user wants to integrate all three functionalities, provide a complete Bun server example that combines all handlers.

```typescript
import { Checkout, CustomerPortal, Webhooks } from "@dodopayments/bun";

const staticCheckoutHandler = Checkout({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
  type: "static",
});

const sessionCheckoutHandler = Checkout({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
  type: "session",
});

const customerPortalHandler = CustomerPortal({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
});

const webhookHandler = Webhooks({
  webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
  onPaymentSucceeded: async (payload) => {
    console.log("Payment succeeded:", payload);
    // Your business logic here
  },
  onSubscriptionActive: async (payload) => {
    console.log("Subscription activated:", payload);
    // Your business logic here
  },
});

Bun.serve({
  port: 3000,
  fetch(request) {
    const url = new URL(request.url);
    
    // Checkout routes
    if (url.pathname === "/api/checkout") {
      if (request.method === "GET") {
        return staticCheckoutHandler(request);
      }
      if (request.method === "POST") {
        return sessionCheckoutHandler(request);
      }
    }
    
    // Customer portal route
    if (url.pathname === "/api/customer-portal" && request.method === "GET") {
      return customerPortalHandler(request);
    }
    
    // Webhook route
    if (url.pathname === "/api/webhook/dodo-payments" && request.method === "POST") {
      return webhookHandler(request);
    }
    
    return new Response("Not Found", { status: 404 });
  },
});

console.log("Server running on http://localhost:3000");

Additional Guidance:
• Explain that the handlers plug directly into the fetch handler of Bun.serve()
• Highlight the use of Web Standard Request and Response objects
• Mention that the handlers return HTTP status codes for errors: 400 for invalid input, 401 for an invalid webhook signature, 405 for a webhook request that is not POST, and 500 for internal errors
• Provide links to the Dodo Payments documentation for more details
````


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