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

# Fastify 适配器

> 将 Dodo Payments checkout、Customer Portal 和 webhook 路由处理程序添加到使用 @dodopayments/fastify adaptor 的 Fastify 应用中。

`@dodopayments/fastify` adaptor 为您的 Fastify 应用提供三个路由处理程序：`Checkout` 返回 checkout URL，`CustomerPortal` 将客户发送到 Customer Portal，而 `Webhooks` 验证 webhook 请求并调用您的事件处理程序。

> **[Checkout Handler](#checkout-route-handler)**
>
> 从 Fastify 应用创建 payment links 和 checkout sessions。

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

> **[Webhooks](#webhook-route-handler)**
>
> 验证并处理 Dodo Payments webhook 事件。

## 安装

**1. Install the Package**

在项目根目录运行以下命令：

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

此软件包需要 Fastify 5.4.0 或更高版本。

**2. Set Up Environment Variables**

在项目根目录创建一个 `.env` 文件：

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

在 **Developer → API Keys** 下创建 API key。在 **Developer → Webhooks** 下添加 webhook endpoint，并将其 signing secret 复制到 `DODO_PAYMENTS_WEBHOOK_KEY`。开发期间，请使用带有 `DODO_PAYMENTS_ENVIRONMENT=test_mode` 的 test mode API key，因为 test mode key 只能用于 test mode。`DODO_PAYMENTS_RETURN_URL` 是可选的。

> [!WARNING]
>
> 请勿将 `.env` 文件或 secrets 提交到版本控制系统。

## 路由处理程序示例

> [!NOTE]
>
> 这些示例会在使用 `Fastify()` 创建的 Fastify 实例上注册路由。webhook 路由需要原始请求正文，因此示例会在一个仅包含 webhook 路由的 plugin 中添加 string body parser。

**Checkout Handler**

> [!NOTE]
>
> 使用此处理程序将 Dodo Payments checkout 集成到您的 Fastify 应用中。支持 static (GET)、dynamic (POST) 和 session (POST) payment flows。对于 static flow，`Checkout()` 返回一个 `getHandler`；对于 dynamic 和 session flows，返回一个 `postHandler`。为每个 POST flow 注册单独的路径。

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

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

```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
}'
```

```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"
}'
```

**Customer Portal Handler**

> [!NOTE]
>
> 使用此处理程序，让客户在 Dodo Payments Customer Portal 中管理其订阅和详细信息。

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

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

**Webhook Handler**

> [!NOTE]
>
> 使用此处理程序在 Fastify 应用中接收、验证和处理 Dodo Payments webhook 事件。

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

## Checkout 路由处理程序

> [!NOTE]
>
> adaptor 支持全部三种 Dodo Payments checkout flows。在处理程序配置中设置 `type`，以选择路由提供的 flow。每种 flow 都会返回包含 `checkout_url` 的 JSON，供客户打开。

* **Static Payment Links：** `type: "static"`，GET。根据 query parameters 为一个产品创建 payment link，并先检查该产品是否存在。
* **Dynamic Payment Links：** `type: "dynamic"`，POST。根据产品是否为 recurring，使用 payment link 创建一次性 payment 或 subscription。
* **Checkout Sessions：** `type: "session"`，POST。根据 product cart 和 customer details 创建 checkout session。新集成应使用此 flow。

`Checkout` 接受以下 options：

| Option | Type | Description |
| - | - | - |
| `bearerToken` | `string` | 您的 Dodo Payments API key。 |
| `environment` | `string` | `test_mode` 或 `live_mode`。默认值为 `live_mode`。 |
| `returnUrl` | `string` | 可选。checkout 在 payment 后将客户重定向到的 URL。POST body 中的 `return_url` 优先级更高。 |
| `type` | `string` | checkout flow：`static`、`dynamic` 或 `session`。 |

`Checkout` 返回一个包含两个处理程序的对象。当 `type` 为 `static` 时，为 GET 注册 `getHandler`；当 `type` 为 `dynamic` 或 `session` 时，为 POST 注册 `postHandler`。

> **Static Checkout (GET)**
>
> ### 支持的 Query Parameters
>
> **productId** `string` *required*
>
> 产品标识符，例如 `?productId=pdt_nZuwz45WAs64n3l07zpQR`。
>
> **quantity** `integer` (default: 1)
>
> 产品数量。
>
> **fullName** `string`
>
> 客户的全名。如果提供了 `firstName` 或 `lastName`，则会忽略此项。
>
> **firstName** `string`
>
> 客户的名字。
>
> **lastName** `string`
>
> 客户的姓氏。
>
> **email** `string`
>
> 客户的 email address。
>
> **country** `string`
>
> 客户的国家，以 ISO 3166-1 alpha-2 code 表示。
>
> **addressLine** `string`
>
> 客户的街道地址。
>
> **city** `string`
>
> 客户所在的城市。
>
> **state** `string`
>
> 客户所在的州或省。
>
> **zipCode** `string`
>
> 客户的邮政编码或 ZIP code。
>
> **disableFullName** `boolean`
>
> 设置为 `true` 可禁用全名字段。
>
> **disableFirstName** `boolean`
>
> 设置为 `true` 可禁用名字字段。
>
> **disableLastName** `boolean`
>
> 设置为 `true` 可禁用姓氏字段。
>
> **disableEmail** `boolean`
>
> 设置为 `true` 可禁用 email 字段。
>
> **disableCountry** `boolean`
>
> 设置为 `true` 可禁用 country 字段。
>
> **disableAddressLine** `boolean`
>
> 设置为 `true` 可禁用 address line 字段。
>
> **disableCity** `boolean`
>
> 设置为 `true` 可禁用 city 字段。
>
> **disableState** `boolean`
>
> 设置为 `true` 可禁用 state 字段。
>
> **disableZipCode** `boolean`
>
> 设置为 `true` 可禁用 ZIP code 字段。
>
> **paymentCurrency** `string`
>
> 支付货币，例如 `USD`。
>
> **showCurrencySelector** `boolean` (default: true)
>
> 显示或隐藏货币选择器。
>
> **paymentAmount** `number`
>
> 固定收取的金额，以主要货币单位表示，例如 `12.5` 表示 \$12.50。仅适用于 Pay What You Want products；如果金额低于产品最低价格，则会忽略此项。
>
> **showDiscounts** `boolean` (default: true)
>
> 显示或隐藏 discounts section。
>
> **metadata\_**\* `string`
>
> 任何以 `metadata_` 开头的 query parameter 都会作为 metadata 传递给 checkout，例如 `metadata_orderId=123`。
>
> 只有当 disable flag 为 `true` 且对应字段有值时，该 flag 才会生效，例如 `email` 与 `disableEmail`。处理程序会将这些 parameters 传递给 [static payment link](https://docs.dodopayments.com/developer-resources/integration-guide.md#static-payment-links)。
>
> > [!WARNING]
> >
> > 如果缺少 `productId`，处理程序会返回 400 response。无效的 query parameters 或账户中不存在的产品也会导致 400 response。
>
> ### Response Format
>
> Static checkout 返回包含 checkout URL 的 JSON response：
>
> ```json theme={null}
> {
>   "checkout_url": "https://test.checkout.dodopayments.com/buy/pdt_fqJhl7pxKWiLhwQR042rh?quantity=1&redirect_url=https%3A%2F%2Fyourapp.com%2Fsuccess"
> }
> ```

> **Dynamic Checkout (POST)**
>
> * 在 POST request 中将 parameters 作为 JSON body 发送。
> * 同时支持一次性和 recurring payments。处理程序会获取产品，然后在产品为 recurring 时创建 subscription，否则创建一次性 payment。
> * body 需要 `billing`（包含 `street`、`city`、`state`、`country` 和 `zipcode`）以及 `customer`，此外还需要 `product_id`（可选的 `quantity`）或 `product_cart`。Subscriptions 需要 `product_id`。
> * 处理程序还会转发 `metadata`、`allowed_payment_method_types`、`billing_currency`、`discount_codes`（或已弃用的 `discount_code`）、`return_url`、`show_saved_payment_methods` 和 `tax_id`。对于 subscriptions，还会转发 `addons`、`on_demand` 和 `trial_period_days`。其他字段会被忽略。
> * 有关字段详细信息，请参阅：
>   * [Request body for a One Time Payment Product](https://docs.dodopayments.com/api-reference/payments/post-payments.md)
>
>   * [Request body for a Subscription Product](https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions.md)
>
> > [!WARNING]
> >
> > Dynamic Checkout 会调用已弃用的 `POST /payments` 和 `POST /subscriptions` endpoints。新集成请使用 Checkout Sessions。
>
> ### Response Format
>
> Dynamic checkout 返回包含 payment link（作为 checkout URL）的 JSON response：
>
> ```json theme={null}
> {
>   "checkout_url": "https://test.checkout.dodopayments.com/cbq"
> }
> ```

> **Checkout Sessions (POST)**
>
> 将 checkout session payload 作为 JSON body 发送。处理程序会创建 checkout session，该 session 负责处理一次性购买和 subscriptions 的完整 payment flow，并返回其 `checkout_url`。必须提供 `product_cart`，且其中至少包含一个产品。
>
> 每个 `checkout_url` 只能使用一次，并会在 24 小时后过期；如果传递 `confirm: true`，则会在 15 分钟后过期。使用 `payment_method_id` 创建的 session 不会返回 `checkout_url`，因此处理程序会响应 400。
>
> 有关更多详细信息和完整的 supported fields 列表，请参阅 [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session.md)。
>
> ### Response Format
>
> Checkout sessions 返回包含 checkout URL 的 JSON response：
>
> ```json theme={null}
> {
>   "checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"
> }
> ```

## Customer Portal 路由处理程序

Customer Portal Route Handler 会为 `customer_id` 中的客户创建 Customer Portal session，并将 request 重定向到 portal link。`CustomerPortal` 接受 `bearerToken` 和 `environment` options，与 `Checkout` 相同。如果 Dodo Payments 无法创建 session，处理程序会返回 500。

### Query Parameters

**customer\_id** `string` *required*

portal session 的 customer ID，例如 `?customer_id=cus_123`。

**send\_email** `boolean`

如果设置为 `true`，则向客户发送包含 portal link 的 email。

> [!WARNING]
>
> 如果缺少 `customer_id`，则返回 400。处理程序不会验证 request，并会为其接收到的任何 `customer_id` 打开 portal，因此请将该路由置于您自己的 authentication 之后，并仅传递已登录用户的 customer ID。

## Webhook 路由处理程序

webhook handler 会使用作为 `webhookKey` 传入的 webhook secret 验证每个 request，然后调用您的 event handlers。

> [!WARNING]
>
> webhook handler 需要原始 request body（类型为 string），因此请为 `application/json` 添加带有 `parseAs: 'string'` 的 content type parser。Fastify 会将 parser 应用到添加 parser 的 scope 中的每个 route。请像示例一样，将其添加到仅注册 webhook route 的 plugin 中。如果添加到 root instance，它也会向 POST checkout handlers 传递 string，后者随后会返回 400。

* \*\*Method：\*\*仅支持 POST requests。其他 methods 返回 405。
* \*\*Signature Verification：\*\*使用 `webhookKey`，按照 [Standard Webhooks](https://standardwebhooks.com/) specification 验证 `webhook-id`、`webhook-timestamp` 和 `webhook-signature` headers。验证失败时返回 401。
* \*\*Payload Validation：\*\*使用 Zod 验证。payload 无效时返回 400。
* **Error Handling：**
  * 401：无效 signature
  * 400：无效 payload
  * 500：验证期间发生内部错误
* \*\*Event Routing：\*\*对每个 event 调用 `onPayload`，然后调用该 event type 对应的 handler，并在它们完成后返回 200。处理程序不会捕获您的 event handlers 抛出的 errors。

### 支持的 Webhook Event Handlers

每个 handler 都是可选的 async handler。有关每个 event 的 payload，请参阅 [Webhook Event Guide](https://docs.dodopayments.com/developer-resources/webhooks/intents/webhook-events-guide.md)。

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

***

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