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

# Quyền cấp Feature Flag

> Kiểm soát các tính năng trong ứng dụng của riêng bạn dựa trên giao dịch mua. Quyền cấp feature flag cung cấp một capability boolean ngay lập tức khi thanh toán và tự động thu hồi khi hủy.

<Info>
  Quyền cấp feature flag biến Dodo Payments thành một kho feature flag nhận biết thông tin thanh toán. Gắn một flag như `advanced_reports` vào một sản phẩm, và mọi khách hàng thanh toán đều nhận được một grant mà ứng dụng của bạn có thể kiểm tra qua API hoặc đồng bộ qua webhooks. Không cần nền tảng bên ngoài, không cần OAuth, không có bước phân phối — chính grant là capability.
</Info>

## Nội dung được cung cấp

Không có gì rời khỏi Dodo Payments — grant **chính là** nội dung được cung cấp:

* Khi mua hàng, grant được tạo và chuyển thẳng sang `delivered`. Không có giai đoạn `pending`, không cần khách hàng thực hiện thao tác và không có khả năng phân phối thất bại.
* Grant chứa payload `feature` có kiểu: `{ "feature_type": "boolean", "feature_id": "advanced_reports" }`. Ứng dụng của bạn đọc `feature_id` để quyết định nội dung cần mở khóa.
* Khi hủy, hoàn tiền hoặc thu hồi thủ công, grant chuyển sang `revoked`, và ứng dụng của bạn nhận thấy flag biến mất.

Các trường hợp sử dụng phổ biến gồm kiểm soát tính năng theo plan (Pro mở khóa analytics), capability bổ sung (nâng cấp "API access") và các chương trình truy cập sớm được bán dưới dạng giao dịch mua một lần.

<Note>
  `feature_id` là một mã định danh do merchant lựa chọn, không có tính duy nhất giữa các entitlement. Hai entitlement có thể cấp cùng một `feature_id` — ví dụ: plan Pro hàng tháng và hàng năm đều cấp `advanced_reports`.
</Note>

## Tạo feature flag

<Steps>
  <Step title="Open Entitlements">
    Trong dashboard Dodo Payments của bạn, đi đến **Entitlements** và nhấp vào **+** để bắt đầu tạo entitlement mới, sau đó chọn **Feature Flags**.
  </Step>

  <Step title="Name the flag">
    Đặt **Display Name** cho flag để hiển thị trong dashboard, một **Feature ID** để ứng dụng kiểm tra (dashboard sẽ đề xuất ID dựa trên tên) và một **Description** để đội ngũ của bạn biết flag kiểm soát điều gì.

    <Frame caption="Creating a feature flag. The Feature ID is what your application checks; Meta Data attaches limits alongside the flag.">
      <img src="https://mintcdn.com/dodopayments/oS2MTbJuY6MeBjjs/images/entitlements/feature-flags/create.png?fit=max&auto=format&n=oS2MTbJuY6MeBjjs&q=85&s=08d8fc2cfee102ff08fd150c76b5fcf9" alt="Biểu mẫu New Feature Flag với display name, feature ID, description và các mục key-value của metadata" style={{ maxHeight: '500px', width: 'auto' }} width="1196" height="776" data-path="images/entitlements/feature-flags/create.png" />
    </Frame>
  </Step>

  <Step title="Optionally add metadata">
    Bật **Meta Data** để đính kèm cấu hình key-value — giới hạn, tên tier, quota — được cung cấp cho ứng dụng của bạn cùng với flag. Xem [Đính kèm giới hạn bằng metadata](#attach-limits-with-metadata).
  </Step>

  <Step title="Confirm">
    Nhấp vào **Confirm**. Flag sẽ xuất hiện trong danh sách entitlements, sẵn sàng để đính kèm vào các sản phẩm.

    <Frame caption="The created feature flag. The right pane tracks every customer grant issued from it.">
      <img src="https://mintcdn.com/dodopayments/oS2MTbJuY6MeBjjs/images/entitlements/feature-flags/list.png?fit=max&auto=format&n=oS2MTbJuY6MeBjjs&q=85&s=02eedcbbf7ece9f67d375c984c5515b9" alt="Dashboard Entitlements hiển thị feature flag Advanced Reports cùng khung hoạt động grant của flag" style={{ maxHeight: '500px', width: 'auto' }} width="1316" height="898" data-path="images/entitlements/feature-flags/list.png" />
    </Frame>
  </Step>
</Steps>

## Đính kèm vào sản phẩm

Mở một sản phẩm (hoặc tạo sản phẩm mới), tìm thẻ **Entitlements** và nhấp vào **+** để đính kèm các entitlement hiện có. Chọn feature flag của bạn và nhấp vào **Done**.

<Frame caption="Attaching the feature flag to a product. One product can deliver multiple entitlements.">
  <img src="https://mintcdn.com/dodopayments/oS2MTbJuY6MeBjjs/images/entitlements/feature-flags/attach-picker.png?fit=max&auto=format&n=oS2MTbJuY6MeBjjs&q=85&s=da2137873e285cc6ce0ce234fe899ed3" alt="Bảng đính kèm Entitlements với feature flag Advanced Reports được chọn" style={{ maxHeight: '500px', width: 'auto' }} width="1316" height="898" data-path="images/entitlements/feature-flags/attach-picker.png" />
</Frame>

Flag đã đính kèm sẽ hiển thị trên biểu mẫu sản phẩm, và bản xem trước checkout sẽ liệt kê flag bên dưới **Includes**.

<Frame caption="The product now includes the feature flag. Every successful purchase or active subscription grants it.">
  <img src="https://mintcdn.com/dodopayments/oS2MTbJuY6MeBjjs/images/entitlements/feature-flags/attach-to-product.png?fit=max&auto=format&n=oS2MTbJuY6MeBjjs&q=85&s=3556181389997edddde9c396d0ce408d" alt="Biểu mẫu sản phẩm với feature flag Advanced Reports được đính kèm trong thẻ Entitlements" style={{ maxHeight: '500px', width: 'auto' }} width="1316" height="898" data-path="images/entitlements/feature-flags/attach-to-product.png" />
</Frame>

## Cấu hình bắt buộc

| Trường         | Bắt buộc | Mô tả                                                                                                     |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `feature_id`   | Có       | Mã định danh mà ứng dụng kiểm tra, ví dụ `advanced_reports`. Không có tính duy nhất giữa các entitlement. |
| `feature_type` | Có       | Loại capability được cấp. Hiện chỉ hỗ trợ `boolean`.                                                      |

### Tạo qua API

<CodeGroup>
  ```typescript TypeScript theme={null} theme={null}
  import DodoPayments from 'dodopayments';

  const client = new DodoPayments({
    bearerToken: process.env['DODO_PAYMENTS_API_KEY'],
    environment: 'test_mode',
  });

  const entitlement = await client.entitlements.create({
    name: 'Advanced Reports',
    integration_type: 'feature_flag',
    integration_config: {
      feature_type: 'boolean',
      feature_id: 'advanced_reports',
    },
    metadata: {
      tier: 'pro',
      monthly_report_limit: 100,
    },
  });
  ```

  ```python Python theme={null} theme={null}
  client.entitlements.create(
      name="Advanced Reports",
      integration_type="feature_flag",
      integration_config={
          "feature_type": "boolean",
          "feature_id": "advanced_reports",
      },
      metadata={
          "tier": "pro",
          "monthly_report_limit": 100,
      },
  )
  ```

  ```go Go theme={null} theme={null}
  client.Entitlements.New(ctx, dodopayments.EntitlementNewParams{
    Name:            dodopayments.F("Advanced Reports"),
    IntegrationType: dodopayments.F(dodopayments.EntitlementIntegrationTypeFeatureFlag),
    IntegrationConfig: dodopayments.F[dodopayments.IntegrationConfigUnionParam](
      dodopayments.IntegrationConfigFeatureFlagConfigParam{
        FeatureType: dodopayments.F(dodopayments.FeatureTypeBoolean),
        FeatureID:   dodopayments.F("advanced_reports"),
      },
    ),
  })
  ```
</CodeGroup>

***

## Đính kèm giới hạn bằng metadata

Một flag boolean trả lời câu hỏi "khách hàng này có feature không?". Metadata trả lời câu hỏi "với cấu hình nào?". Metadata của entitlement chấp nhận các giá trị string, integer, number và boolean, đồng thời mỗi grant nhận một **ảnh chụp cố định** của metadata entitlement tại thời điểm được tạo.

Cơ chế ảnh chụp đó giúp metadata an toàn khi dùng cho các giới hạn của plan:

* Việc chỉnh sửa metadata của entitlement sau đó chỉ ảnh hưởng đến các grant **trong tương lai**. Khách hàng vẫn giữ các giới hạn đã mua.
* Ảnh chụp được trả về trên mỗi grant dưới dạng trường `metadata`, nên một lần gọi API cung cấp cả flag và cấu hình của flag.

Ví dụ, một flag `advanced_reports` với `{ "tier": "pro", "monthly_report_limit": 100 }` cho phép ứng dụng mở khóa dashboard *và* thực thi quota 100 báo cáo mà không cần tra cứu lần thứ hai. Nếu sau đó bạn tăng giới hạn lên 250, khách hàng hiện tại vẫn giữ giới hạn 100 cho đến khi nhận grant mới (ví dụ sau khi thay đổi plan).

<Tip>
  Dùng metadata cho giới hạn và cấu hình; chỉ dùng `feature_id` cho mục đích nhận diện. Việc mã hóa giới hạn trong id (`advanced_reports_100`) buộc bạn phải tạo flag mới cho mỗi lần thay đổi giới hạn và làm hỏng các kiểm tra của ứng dụng.
</Tip>

***

## Kiểm tra các feature của khách hàng

Liệt kê các grant feature flag đã được cung cấp cho khách hàng và xây dựng tập hợp các feature đã bật. Endpoint trả về một dòng cho mỗi grant trên tất cả entitlement, có thể lọc theo `integration_type` và `status`.

<CodeGroup>
  ```typescript TypeScript theme={null} theme={null}
  const features = new Map<string, Record<string, unknown>>();

  for await (const grant of client.customers.listEntitlementGrants('cus_abc123', {
    integration_type: 'feature_flag',
    status: 'Delivered',
  })) {
    if (grant.feature) {
      features.set(grant.feature.feature_id, grant.metadata ?? {});
    }
  }

  if (features.has('advanced_reports')) {
    const limit = features.get('advanced_reports')?.monthly_report_limit;
    // unlock the dashboard, enforce the limit
  }
  ```

  ```python Python theme={null} theme={null}
  page = client.customers.list_entitlement_grants(
      customer_id="cus_abc123",
      integration_type="feature_flag",
      status="Delivered",
  )

  features = {
      grant.feature.feature_id: grant.metadata
      for grant in page.items
      if grant.feature
  }

  if "advanced_reports" in features:
      limit = features["advanced_reports"].get("monthly_report_limit")
  ```

  ```go Go theme={null} theme={null}
  page, _ := client.Customers.ListEntitlementGrants(
    ctx, "cus_abc123",
    dodopayments.CustomerListEntitlementGrantsParams{
      IntegrationType: dodopayments.F("feature_flag"),
      Status:          dodopayments.F("Delivered"),
    },
  )

  features := map[string]bool{}
  for _, grant := range page.Items {
    if grant.Feature.FeatureID != "" {
      features[grant.Feature.FeatureID] = true
    }
  }
  ```
</CodeGroup>

<Note>
  Payload `feature` chỉ được điền trên các grant `feature_flag`; với mọi loại integration khác, payload là `null`. Xem tài liệu tham khảo API [List Customer Grants](/api-reference/entitlements/list-customer-grants) để biết đầy đủ cấu trúc response.
</Note>

Kiểm tra API trong mỗi request sẽ làm tăng độ trễ trên đường dẫn xử lý chính. Hãy cache tập hợp feature theo từng khách hàng với TTL ngắn (tính bằng phút, không phải giờ), và vô hiệu hóa cache từ webhook handler khi trạng thái grant thay đổi — sự kết hợp này giúp các lần kiểm tra nhanh và việc thu hồi gần như tức thì.

***

## Vòng đời

Grant feature flag tuân theo [vòng đời grant tiêu chuẩn](/features/entitlements/introduction#how-grants-work) với một điểm đơn giản hóa: không có bước phân phối, nên grant không bao giờ ở trạng thái `pending` và không bao giờ chuyển sang `failed`.

| Trigger                                                         | Hiệu ứng                                                                                              |
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| Thanh toán một lần thành công / subscription chuyển sang active | Grant được tạo với `status: delivered` và `delivered_at` được thiết lập.                              |
| Subscription bị tạm giữ, hủy hoặc hết hạn                       | Grant bị thu hồi với `revocation_reason` tương ứng.                                                   |
| Hoàn tiền cho khoản thanh toán một lần                          | Grant bị thu hồi với `revocation_reason: refund`.                                                     |
| Subscription được khôi phục (ví dụ, dunning thành công)         | Grant đã bị thu hồi được khôi phục về `delivered` — cùng `id` của grant, các trường thu hồi được xóa. |
| Thu hồi thủ công qua API                                        | Grant bị thu hồi với `revocation_reason: manual`. Không tự động khôi phục khi gia hạn.                |

Grant có tính idempotent theo từng entitlement và khách hàng: khi khách hàng đang có grant chưa bị thu hồi cho một flag, các giao dịch mua lặp lại và các lần gia hạn sẽ không tạo bản sao.

***

## Webhooks

Đăng ký các [sự kiện `entitlement_grant.*`](/developer-resources/webhooks/intents/entitlement-grant) để phản ánh các flag vào cơ sở dữ liệu riêng thay vì polling:

* `entitlement_grant.created` — đã ở trạng thái `delivered` khi đến nơi, cùng payload `feature`. Bật feature.
* `entitlement_grant.delivered` — được phát khi một grant trước đó đã bị thu hồi được khôi phục. Bật lại feature.
* `entitlement_grant.revoked` — quyền truy cập bị rút. Tắt feature và kiểm tra `revocation_reason` để quyết định nội dung thông báo.

```typescript TypeScript theme={null} theme={null}
app.post('/webhooks/dodo', async (req, res) => {
  const event = req.body;

  if (event.type.startsWith('entitlement_grant.') && event.data.feature) {
    const { customer_id, feature } = event.data;
    const enabled = event.type !== 'entitlement_grant.revoked';

    await db.customerFeatures.upsert({
      customerId: customer_id,
      featureId: feature.feature_id,
      enabled,
      config: event.data.metadata ?? {},
    });
  }

  res.sendStatus(200);
});
```

Không có `entitlement_grant.failed` cho feature flag — việc phân phối diễn ra hoàn toàn bên trong Dodo Payments và không thể thất bại.

***

## Ví dụ: plan Pro mở khóa báo cáo nâng cao

1. **Tạo flag.** `feature_id: advanced_reports` với metadata `{ "tier": "pro", "monthly_report_limit": 100 }`.
2. **Đính kèm flag** vào sản phẩm subscription Pro Plan.
3. **Khách hàng đăng ký.** Dodo Payments tạo grant `delivered` và phát `entitlement_grant.created`; webhook handler của bạn bật `advanced_reports` cho khách hàng với giới hạn 100.
4. **Ứng dụng kiểm soát feature.** Khi tải dashboard, kiểm tra tập hợp feature đã cache (hoặc gọi `listEntitlementGrants`) và chỉ hiển thị tab báo cáo khi có `advanced_reports`.
5. **Khách hàng hủy.** Dodo Payments thu hồi grant và phát `entitlement_grant.revoked`; handler của bạn tắt feature. Nếu sau đó khách hàng khôi phục subscription qua dunning, `entitlement_grant.delivered` sẽ khôi phục feature — không cần thay đổi code.

***

## Thực hành tốt nhất

* **Dùng feature id ổn định theo snake\_case.** Code ứng dụng của bạn kiểm tra các chuỗi này; việc đổi tên một chuỗi là thay đổi breaking ở cả hai phía.
* **Mỗi capability dùng một flag.** Ưu tiên `advanced_reports` + `api_access` dưới dạng hai entitlement thay vì một `pro_bundle` duy nhất — việc thu hồi và kết hợp các plan sẽ rõ ràng hơn.
* **Điều khiển trạng thái từ webhooks, xác minh bằng API.** Webhooks giữ cho cơ sở dữ liệu luôn được cập nhật; endpoint list là nguồn dữ liệu chính xác để reconciliation job và xử lý cache miss.
* **Xử lý `revoked` ngay lập tức.** Flag bị thu hồi nghĩa là khách hàng không còn trả tiền cho feature. Hãy kiểm soát ở request tiếp theo, không phải session tiếp theo.
* **Đặt giới hạn trong metadata, không đặt trong code.** Khi thay đổi quota, bạn chỉ cần chỉnh sửa entitlement — khách hàng mới sẽ tự động nhận giới hạn mới, trong khi các grant hiện tại giữ nguyên ảnh chụp đã mua.
