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

# Bun Adaptor

> Tìm hiểu cách tích hợp <CardGroup cols={2}> với dự án máy chủ Bun bằng Bun Adaptor của chúng tôi. Bao gồm checkout, Customer Portal, webhook và thiết lập môi trường an toàn.

<CardGroup cols={2}>
  <Card title="Checkout Handler" icon="cart-shopping" href="#checkout-route-handler">
    Tích hợp checkout của Dodo Payments vào máy chủ Bun của bạn.
  </Card>

  <Card title="Customer Portal" icon="user" href="#customer-portal-route-handler">
    Cho phép khách hàng quản lý các subscription và thông tin chi tiết.
  </Card>

  <Card title="Webhooks" icon="bell" href="#webhook-route-handler">
    Nhận và xử lý các sự kiện webhook của Dodo Payments.
  </Card>
</CardGroup>

## Cài đặt

<Steps>
  <Step title="Install the package">
    Chạy lệnh sau tại thư mục gốc của dự án:

    ```bash theme={null}
    bun add @dodopayments/bun
    ```
  </Step>

  <Step title="Set up environment variables">
    Tạo tệp <code>.env</code> tại thư mục gốc của dự án:

    ```env expandable theme={null}
    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=your-return-url
    ```

    <Warning>
      Không bao giờ commit tệp <code>.env</code> hoặc secrets vào hệ thống quản lý phiên bản.
    </Warning>
  </Step>
</Steps>

## Ví dụ Route Handler

<Info>
  Tất cả ví dụ đều giả định bạn đang sử dụng máy chủ native của Bun với <code>Bun.serve()</code>.
</Info>

<Tabs>
  <Tab title="Checkout Handler">
    <Info>
      Sử dụng handler này để tích hợp checkout của Dodo Payments vào máy chủ Bun của bạn. Hỗ trợ các luồng thanh toán static (GET), dynamic (POST) và session (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>
      ```curl Static Checkout Curl example expandable theme={null}
      curl --request GET \
      --url 'https://example.com/api/checkout?productId=pdt_xxx' \
      --header 'User-Agent: insomnia/11.2.0' \
      --cookie mode=test
      ```
    </CodeGroup>

    <CodeGroup>
      ```curl Dynamic Checkout Curl example expandable theme={null}
      curl --request POST \
      --url https://example.com/api/checkout \
      --header 'Content-Type: application/json' \
      --header 'User-Agent: insomnia/11.2.0' \
      --cookie mode=test \
      --data '{
      "billing": {
        "city": "Texas",
        "country": "US",
        "state": "Texas",
        "street": "56, hhh",
        "zipcode": "560000"
      },
      "customer": {
        "email": "test@example.com",
        	"name": "test"
      },
      "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>
      ```curl Checkout Session Curl example expandable theme={null}
      curl --request POST \
      --url https://example.com/api/checkout \
      --header 'Content-Type: application/json' \
      --header 'User-Agent: insomnia/11.2.0' \
      --cookie mode=test \
      --data '{
      "product_cart": [
        {
          "product_id": "pdt_xxx",
          "quantity": 1
        }
      ],
      "customer": {
        "email": "test@example.com",
        "name": "test"
      },
      "return_url": "https://example.com/success"
      }'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Customer Portal Handler">
    <Info>
      Sử dụng handler này để cho phép khách hàng quản lý các subscription và thông tin chi tiết của họ thông qua Customer Portal của Dodo Payments.
    </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>
      ```curl Customer Portal Curl example expandable theme={null}
      curl --request GET \
      --url 'https://example.com/api/customer-portal?customer_id=cus_123&send_email=true' \
      --header 'User-Agent: insomnia/11.2.0' \
      --cookie mode=test
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Webhook Handler">
    <Info>
      Sử dụng handler này để nhận và xử lý an toàn các sự kiện webhook của Dodo Payments trong máy chủ Bun của bạn.
    </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>

## Checkout Route Handler

<Info>
  Dodo Payments hỗ trợ ba loại luồng thanh toán để tích hợp thanh toán vào website của bạn; adaptor này hỗ trợ tất cả các loại luồng thanh toán.
</Info>

* **Liên kết thanh toán static:** URL có thể chia sẻ ngay để thu tiền nhanh chóng mà không cần code.
* **Liên kết thanh toán dynamic:** Tạo liên kết thanh toán theo chương trình với thông tin tùy chỉnh bằng API hoặc SDK.
* **Checkout Sessions:** Tạo trải nghiệm checkout được lưu trữ an toàn và có thể tùy chỉnh, với giỏ sản phẩm và thông tin khách hàng được cấu hình trước.

<AccordionGroup>
  <Accordion title="Static Checkout (GET)">
    ### Query Parameters được hỗ trợ

    <ParamField query="productId" type="string" required>
      Mã định danh sản phẩm (ví dụ: <code>?productId=pdt\_xxx</code>).
    </ParamField>

    <ParamField query="quantity" type="integer">
      Số lượng sản phẩm.
    </ParamField>

    <ParamField query="fullName" type="string">
      Họ tên đầy đủ của khách hàng.
    </ParamField>

    <ParamField query="firstName" type="string">
      Tên của khách hàng.
    </ParamField>

    <ParamField query="lastName" type="string">
      Họ của khách hàng.
    </ParamField>

    <ParamField query="email" type="string">
      Địa chỉ email của khách hàng.
    </ParamField>

    <ParamField query="country" type="string">
      Quốc gia của khách hàng.
    </ParamField>

    <ParamField query="addressLine" type="string">
      Dòng địa chỉ của khách hàng.
    </ParamField>

    <ParamField query="city" type="string">
      Thành phố của khách hàng.
    </ParamField>

    <ParamField query="state" type="string">
      Bang/tỉnh của khách hàng.
    </ParamField>

    <ParamField query="zipCode" type="string">
      Mã ZIP/mã bưu chính của khách hàng.
    </ParamField>

    <ParamField query="disableFullName" type="boolean">
      Tắt trường họ tên đầy đủ.
    </ParamField>

    <ParamField query="disableFirstName" type="boolean">
      Tắt trường tên.
    </ParamField>

    <ParamField query="disableLastName" type="boolean">
      Tắt trường họ.
    </ParamField>

    <ParamField query="disableEmail" type="boolean">
      Tắt trường email.
    </ParamField>

    <ParamField query="disableCountry" type="boolean">
      Tắt trường quốc gia.
    </ParamField>

    <ParamField query="disableAddressLine" type="boolean">
      Tắt dòng địa chỉ.
    </ParamField>

    <ParamField query="disableCity" type="boolean">
      Tắt trường thành phố.
    </ParamField>

    <ParamField query="disableState" type="boolean">
      Tắt trường bang.
    </ParamField>

    <ParamField query="disableZipCode" type="boolean">
      Tắt trường mã ZIP.
    </ParamField>

    <ParamField query="paymentCurrency" type="string">
      Chỉ định currency thanh toán (ví dụ: <code>USD</code>).
    </ParamField>

    <ParamField query="showCurrencySelector" type="boolean">
      Hiển thị bộ chọn currency.
    </ParamField>

    <ParamField query="paymentAmount" type="integer">
      Chỉ định số tiền thanh toán (ví dụ: <code>1000</code> cho \$10.00).
    </ParamField>

    <ParamField query="showDiscounts" type="boolean">
      Hiển thị các trường giảm giá.
    </ParamField>

    <ParamField query="metadata_*" type="string">
      Mọi query parameter bắt đầu bằng <code>metadata\_</code> sẽ được truyền dưới dạng metadata.
    </ParamField>

    <Warning>
      Nếu thiếu <code>productId</code>, handler sẽ trả về response 400. Query parameter không hợp lệ cũng dẫn đến response 400.
    </Warning>

    ### Định dạng response

    Checkout static trả về response JSON với URL checkout:

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

  <Accordion title="Dynamic Checkout (POST)">
    * Gửi các tham số dưới dạng JSON body trong request POST.
    * Hỗ trợ cả thanh toán một lần và thanh toán định kỳ.
    * Để xem danh sách đầy đủ các field được hỗ trợ trong POST body, tham khảo:
      * [Request body cho sản phẩm thanh toán một lần](https://docs.dodopayments.com/api-reference/payments/post-payments)
      * [Request body cho sản phẩm subscription](https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions)

    ### Định dạng response

    Checkout dynamic trả về response JSON với URL checkout:

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

  <Accordion title="Checkout Sessions (POST)">
    Checkout sessions cung cấp trải nghiệm checkout được lưu trữ an toàn hơn, xử lý toàn bộ luồng thanh toán cho cả giao dịch mua một lần và subscription, đồng thời cho phép tùy chỉnh toàn diện.

    Tham khảo [Hướng dẫn tích hợp Checkout Sessions](https://docs.dodopayments.com/developer-resources/checkout-session) để biết thêm chi tiết và xem danh sách đầy đủ các field được hỗ trợ.

    ### Định dạng response

    Checkout sessions trả về response JSON với URL checkout:

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

## Customer Portal Route Handler

Customer Portal Route Handler cho phép bạn tích hợp liền mạch Customer Portal của Dodo Payments vào ứng dụng máy chủ Bun.

### Query Parameters

<ParamField query="customer_id" type="string" required>
  ID khách hàng cho portal session (ví dụ: <code>?customer\_id=cus\_123</code>).
</ParamField>

<ParamField query="send_email" type="boolean">
  Nếu được đặt thành <code>true</code>, sẽ gửi email cho khách hàng kèm liên kết portal.
</ParamField>

<Warning>
  Trả về 400 nếu thiếu <code>customer\_id</code>.
</Warning>

## Webhook Route Handler

* **Method:** Chỉ hỗ trợ request POST. Các method khác sẽ trả về 405.
* **Xác minh chữ ký:** Xác minh chữ ký webhook bằng <code>webhookKey</code>. Trả về 401 nếu xác minh thất bại.
* **Xác thực payload:** Được xác thực bằng Zod. Trả về 400 nếu payload không hợp lệ.
* **Xử lý lỗi:**
  * 401: Chữ ký không hợp lệ
  * 400: Payload không hợp lệ
  * 500: Lỗi nội bộ trong quá trình xác minh
* **Định tuyến sự kiện:** Gọi event handler phù hợp dựa trên loại payload.

### Event Handler webhook được hỗ trợ

<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>;
  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>;
  onCreditManualAdjustment?: (payload: WebhookPayload) => Promise<void>;
  onCreditBalanceLow?: (payload: WebhookPayload) => Promise<void>;
  ```
</CodeGroup>

***

## Prompt cho LLM

````

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, designed for Bun's native server with 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:

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=your-return-url

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)

Provide the appropriate handler code for their selection.

    For Customer Portal Integration

Provide a complete example showing how to integrate the customer portal handler into their Bun server.

    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 are Bun-native and work seamlessly with Bun.serve()
• Highlight the use of Web Standard Request and Response objects
• Mention that error handling is built-in and returns appropriate HTTP status codes
• Provide links to the Dodo Payments documentation for more details
````
