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

# 功能标志权限

> 通过购买在您自己的应用程序中控制功能。功能标志权限在付款时立即提供布尔能力，并在取消时自动撤销。

<Info>
  一个功能标志权限将 Dodo Payments 转变为一个与账单相关的功能标志存储。将类似于 `advanced_reports` 的标志附加到产品上，每个支付客户都能通过 API 检查或使用 webhooks 保持同步来获得授权。无需外部平台，无需 OAuth，无需交付步骤 — 授权本身就是能力。
</Info>

## 交付内容

没有任何东西离开 Dodo Payments — 授权**就是**交付物:

* 购买时，创建授权，并直接移动到 `delivered`。没有 `pending` 阶段，无需客户操作，也没有交付失败的方式。
* 授权携带一个类型 `feature` 负载: `{ "feature_type": "boolean", "feature_id": "advanced_reports" }`。您的应用程序读取 `feature_id` 以决定解锁什么。
* 取消、退款或手动撤销后，授权移动到 `revoked`，您的应用程序将看到标志消失。

常见用途包括基于计划的功能控制（Pro 解锁分析）、附加能力（"API 访问" 升级）和作为一次性购买出售的早期访问计划。

<Note>
  `feature_id` 是商家选择的标识符，不在所有权限中唯一。两个权限可以提供相同的 `feature_id` —— 例如，月度和年度 Pro 计划都授予 `advanced_reports`。
</Note>

## 创建功能标志

<Steps>
  <Step title="Open Entitlements">
    在您的 Dodo Payments 控制面板中，转到 **权限** 并点击 **+** 开始一个新权限，然后选择 **功能标志**。
  </Step>

  <Step title="Name the flag">
    为您的控制面板提供一个标志的 **显示名称**，一个您的应用程序将检查的 **功能 ID**（控制面板从名称中建议一个），以及一个 **描述** 以便您的团队知道它控制什么。

    <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="新功能标志表单，具有显示名称、功能ID、描述和元数据键-值条目" style={{ maxHeight: '500px', width: 'auto' }} width="1196" height="776" data-path="images/entitlements/feature-flags/create.png" />
    </Frame>
  </Step>

  <Step title="Optionally add metadata">
    切换 **元数据** 以附加键-值配置 — 限制、层级名称、配额 — 这些将与标志一起交付到您的应用程序。参见 [用元数据附加限制](#attach-limits-with-metadata)。
  </Step>

  <Step title="Confirm">
    点击 **确认**。标志会出现在您的权限列表中，准备附加到产品上。

    <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="权限控制面板显示高级报告功能标志及其授权活动面板" style={{ maxHeight: '500px', width: 'auto' }} width="1316" height="898" data-path="images/entitlements/feature-flags/list.png" />
    </Frame>
  </Step>
</Steps>

## 附加到产品

打开一个产品（或创建一个），找到 **权限** 卡，然后点击 **+** 以附加现有权限。选择您的功能标志并点击 **完成**。

<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="权限附加面板，选择了高级报告功能标志" style={{ maxHeight: '500px', width: 'auto' }} width="1316" height="898" data-path="images/entitlements/feature-flags/attach-picker.png" />
</Frame>

附加的标志显示在产品表单上，结账预览在**包含**下列出。

<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="产品表单，权限卡中附有高级报告功能标志" style={{ maxHeight: '500px', width: 'auto' }} width="1316" height="898" data-path="images/entitlements/feature-flags/attach-to-product.png" />
</Frame>

## 必需配置

| 字段             | 必需 | 描述                                          |
| -------------- | -- | ------------------------------------------- |
| `feature_id`   | 是  | 您的应用程序检查的标识符，例如 `advanced_reports`。未在权限中唯一。 |
| `feature_type` | 是  | 授予的能力类型。当前仅支持 `boolean`。                    |

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

***

## 用元数据附加限制

一个布尔标志回答“此客户是否拥有该功能？”。元数据回答“使用什么配置？”。权限元数据接受字符串、整数、数字和布尔值，每个授权在创建时会对权限的元数据进行**冻结快照**。

这种快照行为使得元数据在计划限制中使用安全：

* 稍后编辑权限的元数据仅影响**未来**的授权。客户保留他们购买时的限制。
* 每个授权都在其 `metadata` 字段中返回快照，因此一个 API 调用可以同时提供标志及其配置。

例如，一个具有 `{ "tier": "pro", "monthly_report_limit": 100 }` 的 `advanced_reports` 标志让您的应用程序解锁仪表板*并*执行 100 份报告配额，而无需第二次查找。如果您后来将限制提高到 250，现有客户仍为 100，直到他们收到新的授权（例如，计划更改后）。

<Tip>
  使用元数据进行限制和配置；仅对身份使用 `feature_id`。在 id 中编码限制（`advanced_reports_100`）会在每次限制更改时强制创建新标志，并破坏应用程序的检查。
</Tip>

***

## 检查客户的功能

列出客户交付的功能标志授权，并构建已启用功能集。终端返回每个授权跨所有权限的一行，支持按 `integration_type` 和 `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>
  `feature` 负载仅在 `feature_flag` 授权上填充；对于每种其他集成类型，它为 `null`。完整响应格式请参见 [列出客户授权](/api-reference/entitlements/list-customer-grants) API 参考。
</Note>

在每个请求上检查 API 会增加热路径的延迟。使用短 TTL（分钟而非小时）为每个客户缓存功能集，并在授权变更状态时从您的 webhook 处理程序中使缓存无效 —— 这种结合保持检查快速，并使撤销接近于即时。

***

## 生命周期

功能标志授权遵循标准的[授权生命周期](/features/entitlements/introduction#how-grants-work)，但有一项简化：没有交付步骤，因此授权从未停留在 `pending` 或移动到 `failed`。

| 触发器                | 效果                                                |
| ------------------ | ------------------------------------------------- |
| 一次性支付成功 / 订阅变为活动状态 | 使用已设置的 `status: delivered` 和 `delivered_at` 创建授权。 |
| 订阅被搁置、取消或过期        | 使用匹配的 `revocation_reason` 撤销授权。                   |
| 一次性支付退款            | 使用 `revocation_reason: refund` 撤销授权。              |
| 订阅恢复（例如，扣款成功）      | 撤销的授权恢复到 `delivered` — 相同的授权 `id`，撤销字段清除。         |
| 手动 API 撤销          | 使用 `revocation_reason: manual` 撤销授权。续订时不会自动恢复。    |

授权对于每个权限和客户是幂等的：当客户对一个标志拥有未撤销的授权时，重复购买和续订不会创建重复项。

***

## Webhooks

订阅 [`entitlement_grant.*` 事件](/developer-resources/webhooks/intents/entitlement-grant) 以将标志镜像到您自己的数据库而不是轮询：

* `entitlement_grant.created` — 已到达 `delivered`，带有 `feature` 负载。启用功能。
* `entitlement_grant.delivered` — 在先前撤销的授权恢复时触发。重新启用功能。
* `entitlement_grant.revoked` — 已取回访问。禁用功能并查看 `revocation_reason` 以决定您的消息传递。

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

功能标志没有 `entitlement_grant.failed` — 交付完全在 Dodo Payments 内部进行，不能失败。

***

## 示例：Pro 计划解锁高级报告

1. **创建标志。** `feature_id: advanced_reports`，带有元数据 `{ "tier": "pro", "monthly_report_limit": 100 }`。
2. **将其附加**到您的 Pro 计划订购产品。
3. **客户进行订购。** Dodo Payments 创建一个 `delivered` 授权并触发 `entitlement_grant.created`；您的 webhook 处理程序为客户启用 `advanced_reports`，限制为 100。
4. **您的应用程序控制功能。** 在加载仪表板时，检查缓存的功能集（或调用 `listEntitlementGrants`），并仅在存在 `advanced_reports` 时渲染报告选项卡。
5. **客户取消。** Dodo Payments 撤销授权并触发 `entitlement_grant.revoked`；您的处理程序禁用功能。如果客户稍后通过扣款恢复，`entitlement_grant.delivered` 会恢复它 — 无需代码更改。

***

## 最佳实践

* **使用稳定的 snake\_case 功能 ID。** 您的应用程序代码检查这些字符串；重命名其中一个是双方的重大更改。
* **每个能力一个标志。** 优先选择 `advanced_reports` + `api_access` 作为两个权限，而不是单一的 `pro_bundle` —— 撤销和计划混合保持整洁。
* **从 webhook 驱动状态，通过 API 验证。** Webhook 保持您的数据库当前；列表终端是对账工作和缓存丢失的真相来源。
* **立即将 `revoked` 视为有效。** 撤销标志意味着客户不再为该功能付费。控制下一个请求，而不是下一次会话。
* **将限制放在元数据中，而不是代码中。** 更改配额然后只需编辑权限 — 新客户会自动获取，而现有授权保留他们购买的快照。
