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

# TanStack Adaptor

> 将 Dodo Payments checkout、Customer Portal 和 webhook server routes 添加到带有 @dodopayments/tanstack package 的 TanStack Start project 中。

`@dodopayments/tanstack` package 为你的 TanStack Start project 提供三个 request handlers。`Checkout` 返回 checkout URLs，`CustomerPortal` 将 customer 导向 Customer Portal，而 `Webhooks` 会验证 webhook events 并将其路由到你的代码。每个 handler 都接收标准的 `Request` 并返回 `Response`，因此你可以从 server route handler 中调用它。

> **[Checkout Handler](#checkout-route-handler)**
>
> 使用 static、dynamic 和 checkout session flows 创建 checkout URLs。

> **[Customer Portal](#customer-portal-route-handler)**
>
> 让 customers 管理其 subscriptions 和 details。

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

## 安装

**1. Install the Package**

在 project root 中运行此命令：

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

该 package 还需要 `zod` 3.25 或更高版本，并将其列为 peer dependency。

**2. Set Up Environment Variables**

在 project root 中创建一个 `.env` file。在 **Developer → API Keys** 下创建 API key。在 **Developer → Webhooks** 下添加 webhook endpoint，并将其 **Signing secret** 复制到 `DODO_PAYMENTS_WEBHOOK_KEY`：

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

TanStack Start 会加载 `.env` files，而 server routes 会从 `process.env` 中读取这些值。`DODO_PAYMENTS_RETURN_URL` 是 customers 在 checkout 后到达的页面。如果你不传入 environment，handlers 会使用 `live_mode`。test mode API key 只能与 `test_mode` 配合使用。

> [!WARNING]
>
> 切勿将 `.env` file 或 secrets 提交到 version control。

## Route Handler 示例

> [!NOTE]
>
> 这些示例是位于 `src/routes/api/` 中的 TanStack Start server routes。每个示例都会在 `createFileRoute` 的 `server.handlers` 下定义其 handlers。较旧的 TanStack Start releases（例如 1.129）会从 `@tanstack/react-start/server` 使用 `createServerFileRoute` 定义 server routes，并改用 `.methods()` call。Dodo Payments handlers 在两种 APIs 中的工作方式相同：将 `request` 传给它们。

**Checkout Handler**

> [!NOTE]
>
> 使用此 handler 将 Dodo Payments checkout 添加到你的 app。`GET` handler 提供 static checkout。`POST` handler 提供 checkout sessions；当你设置 `type: "dynamic"` 时，它也提供 dynamic checkout。dynamic checkout 示例假设你已设置 `type: "dynamic"`。

```typescript TanStack Route Handler expandable theme={null}
// src/routes/api/checkout.ts
import { Checkout } from "@dodopayments/tanstack";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/api/checkout")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return Checkout({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
          type: "static", // optional, defaults to 'static'
        })(request);
      },
      POST: async ({ request }) => {
        return Checkout({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
          type: "session", // or "dynamic" for dynamic checkout
        })(request);
      },
    },
  },
});
```

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

```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_QMDuvLkbVzCRWRQjLNcs",
  "quantity": 1,
  "billing_currency": "USD",
  "discount_codes": ["IKHZ23M9GQ"],
  "return_url": "https://example.com",
  "trial_period_days": 10
}'
```

```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_QMDuvLkbVzCRWRQjLNcs",
      "quantity": 1
    }
  ],
  "customer": {
    "email": "customer@example.com",
    "name": "John Doe"
  },
  "return_url": "https://example.com/success"
}'
```

**Customer Portal Handler**

> [!NOTE]
>
> 使用此 handler 将 customers 发送到 Dodo Payments Customer Portal，让他们管理 subscriptions 和 details。

```typescript TanStack Route Handler expandable theme={null}
// src/routes/api/customer-portal.ts
import { CustomerPortal } from "@dodopayments/tanstack";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/api/customer-portal")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return CustomerPortal({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
        })(request);
      },
    },
  },
});
```

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

**Webhook Handler**

> [!NOTE]
>
> 使用此 handler 在 TanStack Start app 中接收 Dodo Payments webhook events 并验证其 signatures。

```typescript TanStack Route Handler expandable theme={null}
// src/routes/api/webhook.ts
import { Webhooks } from "@dodopayments/tanstack";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/api/webhook")({
  server: {
    handlers: {
      POST: async ({ request }) => {
        return Webhooks({
          webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY!,
          onPayload: async (payload) => {
            console.log(payload);
          },
        })(request);
      },
    },
  },
});
```

## Checkout Route Handler

checkout handler 支持使用 Dodo Payments 收款的三种方式：

* \*\*Static Payment Links：\*\*可分享的 URLs，无需代码即可收款。
* \*\*Dynamic Payment Links：\*\*使用自定义 details 生成的 payment links。它们使用 deprecated endpoints。
* \*\*Checkout Sessions：\*\*支持 product cart、customer details 和 customization options 的 hosted checkout。这是推荐的 flow。

`Checkout` 接收以下 options：

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

对于 `GET` requests，handler 提供 static checkout。对于 `POST` requests，当 `type` 为 `dynamic` 时，它会创建 dynamic payment link；否则会创建 checkout session。

> **Static Checkout (GET)**
>
> ### 支持的 Query Parameters
>
> **productId** `string` *required*
>
> Product identifier，例如 `?productId=pdt_nZuwz45WAs64n3l07zpQR`。
>
> **quantity** `integer` (default: 1)
>
> Product 的数量。
>
> **fullName** `string`
>
> Customer 的全名。如果提供了 `firstName` 或 `lastName`，则会忽略此值。
>
> **firstName** `string`
>
> Customer 的名字。
>
> **lastName** `string`
>
> Customer 的姓氏。
>
> **email** `string`
>
> Customer 的 email address。
>
> **country** `string`
>
> Customer 的国家，使用 ISO 3166-1 alpha-2 code。
>
> **addressLine** `string`
>
> Customer 的街道地址。
>
> **city** `string`
>
> Customer 所在的城市。
>
> **state** `string`
>
> Customer 所在的州或省。
>
> **zipCode** `string`
>
> Customer 的 ZIP 或 postal code。
>
> **disableFullName** `boolean`
>
> 设置为 `true` 可禁用 full name field。
>
> **disableFirstName** `boolean`
>
> 设置为 `true` 可禁用 first name field。
>
> **disableLastName** `boolean`
>
> 设置为 `true` 可禁用 last name field。
>
> **disableEmail** `boolean`
>
> 设置为 `true` 可禁用 email field。
>
> **disableCountry** `boolean`
>
> 设置为 `true` 可禁用 country field。
>
> **disableAddressLine** `boolean`
>
> 设置为 `true` 可禁用 address line field。
>
> **disableCity** `boolean`
>
> 设置为 `true` 可禁用 city field。
>
> **disableState** `boolean`
>
> 设置为 `true` 可禁用 state field。
>
> **disableZipCode** `boolean`
>
> 设置为 `true` 可禁用 ZIP code field。
>
> **paymentCurrency** `string`
>
> Payment currency，例如 `USD`。
>
> **showCurrencySelector** `boolean` (default: true)
>
> 显示或隐藏 currency selector。
>
> **paymentAmount** `number`
>
> 固定收取的金额（以 major currency units 表示），例如对于 \$12.50，使用 `12.5`。仅适用于 Pay What You Want products；如果低于 product 的 minimum price，则会忽略该值。
>
> **showDiscounts** `boolean` (default: true)
>
> 显示或隐藏 discounts section。
>
> **metadata\_**\* `string`
>
> 任何以 `metadata_` 开头的 query parameter 都会作为 metadata 传递给 checkout，例如 `metadata_orderId=123`。
>
> 仅当匹配的 field 有值时，disable flag 才会生效，例如 `email` 与 `disableEmail=true` 同时存在时。handler 会将其 config 中的 `returnUrl` 以 `redirect_url` 的形式添加到 link。
>
> > [!WARNING]
> >
> > 如果缺少 `productId`，handler 会返回 400 response。无效的 query parameters，或 account 中不存在的 product，也会返回 400。
>
> ### Response Format
>
> Static checkout 返回包含 checkout URL 的 JSON response。在 test mode 中，URL 使用 `test.checkout.dodopayments.com`：
>
> ```json theme={null}
> {
>   "checkout_url": "https://test.checkout.dodopayments.com/buy/pdt_fqJhl7pxKWiLhwQR042rh?quantity=1&redirect_url=https%3A%2F%2Fyourdomain.com%2Fcheckout%2Fsuccess"
> }
> ```

> **Dynamic Checkout (POST)**
>
> * 在 POST request 中将 parameters 作为 JSON body 发送。
> * 支持 one-time 和 recurring payments。handler 会获取 product；如果 product 是 recurring，则创建 subscription，否则创建 one-time payment。
> * body 需要 `billing`（包括 `street`、`city`、`state`、`country` 和 `zipcode`）以及 `customer`，此外还需要 `product_id` 或 `product_cart`。Subscriptions 需要 `product_id`。
> * 关于所有支持的 body fields，请参阅：
>   * [One Time Payment Product 的 Request body](https://docs.dodopayments.com/api-reference/payments/post-payments)
>
>   * [Subscription Product 的 Request body](https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions)
>
> > [!WARNING]
> >
> > Dynamic checkout 代理已 deprecated 的 `POST /payments` 和 `POST /subscriptions` endpoints。它会继续为现有 integrations 提供支持，但新的 integrations 应使用 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 sessions 会为 one-time purchases 和 subscriptions 创建 hosted checkout，并提供完整的 customization 控制。`product_cart` 是唯一必需的 field，并且至少需要一个 product。如果 body 没有 `return_url`，handler 会使用其 config 中的 `returnUrl`。
>
> 每个 `checkout_url` 只能使用一次，并会在 24 小时后过期；如果传入 `confirm: true`，则会在 15 分钟后过期。使用 `payment_method_id` 创建的 session 不会返回 `checkout_url`，因此 handler 会响应 400。
>
> 如需了解更多详情及所有支持的 fields，请参阅 [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session)。
>
> ### Response Format
>
> Checkout sessions 返回包含 checkout URL 的 JSON response：
>
> ```json theme={null}
> {
>   "checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"
> }
> ```

## Customer Portal Route Handler

Customer Portal route handler 会为你传入的 customer 创建 Customer Portal session，并将 browser 重定向到该 session。`CustomerPortal` 接收与 `Checkout` 相同的 `bearerToken` 和 `environment` options。

> [!WARNING]
>
> handler 不会检查调用者的身份。任何使用 customer ID 请求它的人都可以获取该 customer 的 portal。使用你自己的 authentication 保护该 route，并且只传入已登录用户的 customer ID。

### Query Parameters

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

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

**send\_email** `boolean`

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

如果缺少 `customer_id`，handler 会返回 400；如果无法创建 portal session，则返回 500。

## Webhook Route Handler

webhook route handler 会使用你的 webhook secret 验证每个 request；该 secret 作为 `webhookKey` 传入，然后才运行你的代码：

* \*\*Method：\*\*仅支持 POST requests。其他 methods 返回 405。
* \*\*Signature Verification：\*\*使用 `webhookKey`，按照 Standard Webhooks specification 验证 `webhook-id`、`webhook-timestamp` 和 `webhook-signature` headers。验证失败时返回 401。
* \*\*Payload Validation：\*\*使用 Zod 验证 payload。payload 无效时返回 400。
* **Error Handling：**
  * 401：Invalid signature
  * 400：Invalid payload
  * 500：验证期间发生 Internal error
* \*\*Event Routing：\*\*对每个 event 调用 `onPayload`，然后调用对应 event type 的 handler，并返回 200。

adaptor 不会捕获 handlers 中抛出的 errors。这些 errors 会传播到 TanStack Start，并导致 request 失败。

### 支持的 Webhook Event Handlers

每个 handler 都是可选的 async handler，并接收其 event type 对应的已验证 payload：

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

关于每个 event 的含义，请参阅 [Webhook Event Guide](/developer-resources/webhooks/intents/webhook-events-guide)。

## 给 LLM 的 Prompt

将此 prompt 复制到你的 AI coding assistant 中，让它将 adaptor 添加到你的 project。若还希望向 agent 提供 Dodo Payments docs 和 skills，请安装 [Agent Plugin](/developer-resources/build-with-ai-coding-agents)。

```text expandable theme={null}

You are an expert TanStack developer assistant. Your task is to guide a user through integrating the @dodopayments/tanstack adapter into their existing TanStack Start project.

The @dodopayments/tanstack adapter provides request handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities. Each handler takes a Request and returns a Response, so call it from a TanStack Start server route.

First, install the necessary packages. Use the package manager appropriate for your project (npm, yarn, or bun) based on the presence of lock files (e.g., package-lock.json for npm, yarn.lock for yarn, bun.lockb for bun):

npm install @dodopayments/tanstack

The examples below define server routes with createFileRoute and server.handlers. If the project uses an older TanStack Start release that exports createServerFileRoute from @tanstack/react-start/server instead, use createServerFileRoute("/api/checkout").methods({ GET, POST }) with the same handler bodies.

Here's how you should structure your response:

    Ask the user which functionalities they want to integrate.

"Which parts of the @dodopayments/tanstack 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)"

    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.
File Creation: Create a new file at src/routes/api/checkout.ts in your TanStack Start project.

Code Snippet:

// src/routes/api/checkout.ts
import { Checkout } from "@dodopayments/tanstack";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/api/checkout")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return Checkout({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
          type: "static", // optional, defaults to 'static'
        })(request);
      },
      POST: async ({ request }) => {
        return Checkout({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
          type: "session", // or "dynamic" for dynamic link
        })(request);
      },
    },
  },
});

Configuration & Usage:

    bearerToken: Your Dodo Payments API key. It's recommended to set this via the DODO_PAYMENTS_API_KEY environment variable.

    returnUrl: (Optional) The URL to redirect the user to after a successful checkout. A return_url in the POST body takes precedence.

    environment: (Optional) Set to "test_mode" for testing, or omit/set to "live_mode" for production.

    type: (Optional) Set to "static" for GET/static checkout, "dynamic" for POST/dynamic checkout, or "session" for POST/checkout sessions.

Static Checkout (GET) Query Parameters:

    productId (required): Product identifier (e.g., ?productId=pdt_nZuwz45WAs64n3l07zpQR)

    quantity (optional): Quantity of the product

    Customer Fields (optional): fullName, firstName, lastName, email, country, addressLine, city, state, zipCode

    Disable Flags (optional, set to true to disable): disableFullName, disableFirstName, disableLastName, disableEmail, disableCountry, disableAddressLine, disableCity, disableState, disableZipCode

    Advanced Controls (optional): paymentCurrency, showCurrencySelector, paymentAmount, showDiscounts

    Metadata (optional): Any query parameter starting with metadata_ (e.g., ?metadata_userId=abc123)

    Returns: {"checkout_url": "https://test.checkout.dodopayments.com/buy/pdt_nZuwz45WAs64n3l07zpQR?quantity=1"}

Dynamic Checkout (POST) - Returns JSON with checkout_url: Parameters are sent as a JSON body. It requires billing and customer, plus product_id or product_cart. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://test.checkout.dodopayments.com/cbq"}. For a complete list of supported POST body fields, refer to:

    Docs - One Time Payment Product: https://docs.dodopayments.com/api-reference/payments/post-payments

    Docs - Subscription Product: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions

Checkout Sessions (POST) - (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

Error Handling: If productId is missing or other query parameters are invalid, the handler will return a 400 response.

If Customer Portal Route Handler is selected:

Purpose: This handler redirects a customer to their Dodo Payments customer portal. It does not authenticate the caller, so protect the route and pass only the signed-in user's customer ID.
File Creation: Create a new file at src/routes/api/customer-portal.ts in your TanStack Start project.

Code Snippet:

// src/routes/api/customer-portal.ts
import { CustomerPortal } from "@dodopayments/tanstack";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/api/customer-portal")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return CustomerPortal({
          bearerToken: process.env.DODO_PAYMENTS_API_KEY,
          environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
        })(request);
      },
    },
  },
});

Query Parameters:

    customer_id (required): The customer ID for the portal session (e.g., ?customer_id=cus_123)

    send_email (optional, boolean): If set to true, sends an email to the customer with the portal link.

    Returns 400 if customer_id is missing.

If Webhook Route Handler is selected:

Purpose: This handler processes incoming webhook events from Dodo Payments, allowing your application to react to events like successful payments, refunds, or subscription changes.
File Creation: Create a new file at src/routes/api/webhook.ts in your TanStack Start project.

Code Snippet:

// src/routes/api/webhook.ts
import { Webhooks } from "@dodopayments/tanstack";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/api/webhook")({
  server: {
    handlers: {
      POST: async ({ request }) => {
        return Webhooks({
          webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY!,
          onPayload: async (payload) => {
            console.log(payload);
          },
        })(request);
      },
    },
  },
});

Handler Details:

    Method: Only POST requests are supported. Other methods return 405.

    Signature Verification: The handler verifies the webhook signature using the webhookKey and returns 401 if verification fails.

    Payload Validation: The payload is validated with Zod. Returns 400 for invalid payloads.

Error Handling:

    401: Invalid signature

    400: Invalid payload

    500: Internal error during verification

Event Routing: Calls onPayload for every event, then the event handler that matches the payload type.

Supported Webhook Event Handlers:

    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>

    Environment Variable Setup:

To ensure the adapter functions correctly, you will need to manually set up the following environment variables in your TanStack project's deployment environment (e.g., Vercel, Netlify, AWS, etc.):

    DODO_PAYMENTS_API_KEY: Your Dodo Payments API Key (required for Checkout and Customer Portal).

    DODO_PAYMENTS_RETURN_URL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler).

    DODO_PAYMENTS_WEBHOOK_KEY: Your Dodo Payments Webhook Secret (required for Webhook handler).

    DODO_PAYMENTS_ENVIRONMENT: test_mode or live_mode. The handlers default to live_mode.

Example .env file:

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

Usage in your code:

bearerToken: process.env.DODO_PAYMENTS_API_KEY
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY!

Important: Never commit sensitive environment variables directly into your version control. Use environment variables for all sensitive information.

If the user needs assistance setting up environment variables for their specific deployment environment, ask them what platform they are using (e.g., Vercel, Netlify, AWS, etc.), and provide guidance. You can also add comments to their PR or chat depending on the context

```


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