Stripe Entitlements 上线前最容易踩的坑,是把它当成产品里的全部权限系统。它更适合做「付费状态 -> 当前 feature 权益」这一层事实源;真正的运行时开关、席位占用、人工补偿、灰度开关和客服备注,仍然要落在你自己的数据库里。

2026 年 5 月 27 日查看 Stripe 文档时,Entitlements 的官方路径已经很清楚:创建 Feature,把 Feature attach 到 Product,客户订阅对应 Product 后,Stripe 会生成 Active Entitlement;订阅创建、升级、降级或取消时,entitlements.active_entitlement_summary.updated webhook 会推送最新权益摘要。一个人 SaaS 先把这条链路跑稳,比一次性重写整个 billing 后台更现实。

什么东西交给 Stripe,什么留在产品库?

Stripe Billing 管钱和订阅生命周期,Stripe Entitlements 管客户当前被授予哪些 feature。产品库要管「用户点击按钮时能不能进」以及「客服为什么给他开了三天补偿」。

最小分层可以这样定:

对象放在 Stripe放在产品库上线前判断
套餐商品Product、Price、Subscriptionplan_code、展示文案、排序Product 是付费容器,不等于页面文案
功能权益Feature、Product Feature、Active Entitlementfeature_code、运行时开关、默认限制Stripe 给付费来源,产品决定入口开关
席位数量Subscription Item quantityseat_limit、已占席位、邀请状态quantity 只能做输入,不能代替席位表
试用和补偿Trial、discount、credit 或手工订阅调整manual_grant、到期时间、操作者客服补偿必须能解释来源
查账状态Customer、Subscription、Invoice、Entitlementsentitlement snapshot、event log、客服备注两边状态不一致时要有重放入口

默认建议:Stripe Entitlements 只写入「当前可用 feature 列表」,不要把它设计成每次请求都同步查 Stripe。Stripe 文档也建议把 entitlements 持久化到内部系统,这样授权判断更快,webhook 失败时也能补拉。

功能开关命名怎么定,lookup_key 别改成营销文案

Feature 的 lookup_key 是产品权限里最硬的字段。Stripe API 创建 Feature 时要求提供唯一 lookup_key,Dashboard 创建后不能编辑 lookup key;它不应该叫 pro-plan-best-value,而应该叫 api_accessteam_seatsadvanced_reports 这种能长期留在代码里的名字。

一套稳定命名可以分三层:

权限层示例 key谁会用命名规则
能力型 featureapi_access后端鉴权、菜单显示名字描述能力,不描述套餐
限额型 featuremonthly_exports任务队列、用量页另配本地限制值,不只靠 Entitlements
服务型 featurepriority_support客服系统、工单路由和客服 SLA 文案分开
席位型 featureteam_workspace邀请成员、组织设置席位数来自 subscription item 或本地表
内部灰度new_editor_beta实验系统不放 Stripe,放产品 feature flag

Stripe Entitlements 的 Feature name 是内部用途,不是给客户展示的价格页文案。价格页可以写「团队协作」或「高级报表」,代码和 webhook 里仍然读 team_workspaceadvanced_reports

套餐和 feature 怎么映射?

不要从「有几个套餐」开始建 Feature。更稳的顺序是先列产品能力,再把能力 attach 到不同 Product。Stripe 的 Product Feature 表示 Feature 和 Product 之间的挂接关系,同一个 Feature 可以分配给多个 Product。

一个常见 B2B SaaS 可以这样落:

SaaS 计划Stripe ProductPrice / Subscription ItemStripe Feature lookup_key产品侧限制
Freeprod_free无付费订阅或 0 元 Price不依赖 Entitlements1 个 workspace,100 条记录
Starterprod_starter$19/month base itemapi_accessbasic_reports3 个 seat,1,000 次 API
Proprod_pro$49/month base itemapi_accessadvanced_reportswebhook_outbound10 个 seat,10,000 次 API
Teamprod_teambase item + seat item quantityapi_accessadvanced_reportswebhook_outboundteam_workspacepriority_supportseat 数按 item quantity,支持 SSO 白名单

这里有两个细节。第一,seat quantity = 10 不应该生成十个 Feature;它只是 team_workspace 这个 feature 的限制值。第二,Free 计划不一定要走 Entitlements;未付费用户的默认权限可以直接来自本地 plan_code=free,否则会把授权链路拉得太长。

如果你已经有 plans 表,建议加一张映射表,而不是把 Stripe ID 写死在代码里:

create table plan_feature_map (
  plan_code text not null,
  stripe_product_id text not null,
  stripe_feature_lookup_key text not null,
  local_feature_code text not null,
  limit_json jsonb not null default '{}',
  active boolean not null default true,
  primary key (plan_code, local_feature_code)
);

limit_json 可以放 { "seat_limit": 10, "api_calls_monthly": 10000 }。Stripe 告诉你客户有 team_workspace,产品库再决定这个 workspace 能邀请几个人。

迁移老订阅时,哪些客户不能一起切?

老 SaaS 往往已经有 plan=prois_premium=truemax_projects=20 这类本地权限。接 Stripe Entitlements 时,不要第一晚把所有客户都切到 webhook 驱动;先把老状态和 Stripe 当前状态并排展示,找出不能自动迁移的客户。

迁移批次可以按风险分:

客户类型迁移动作暂停条件
新客户Checkout 后直接走 Entitlements + webhookcheckout.session.completed 有订单但无 entitlement snapshot
正常月付老客户保留 legacy 权限;等下一账期、订阅变更或补拉验证后再生成新快照本地 plan 和 Stripe Product 对不上,或 Active Entitlements 为空
年付老客户保留原权益到当前 period end,再切到 Product Feature曾经人工赠送过额外功能
企业合同客户手工映射 feature 和限制值合同里有非公开权益、PO 或特殊 SLA
退款或争议客户不自动迁移,先冻结自动开通脚本Invoice、Subscription、客服备注三边不一致

Stripe Entitlements 文档说明,对已有订阅添加或移除 Product Feature 时,相关 active entitlement 变化会在下一账期开始时生效。迁移老客户时不要把「现在查不到 Active Entitlement」解释成应该关权限;先保留 legacy 或本地 override,等续费、订阅变更、补拉验证或人工批次确认后,再把运行时判断切到新快照。

Stripe 订阅可以修改,不必取消重建;但改 Price、数量、计费周期、增删 subscription items 可能触发 proration 或新 invoice。迁移时把「计费变化」和「权限变化」拆开:先让 Product Feature 映射稳定,再处理套餐升级、降级和账单影响。

一个实用做法是加双读开关:

function canUseFeature(user, featureCode) {
  if (user.entitlements_v2_enabled && user.entitlements_v2_verified) {
    return entitlementSnapshot.has(featureCode);
  }

  return legacyPlanRules.allow(user.plan_code, featureCode);
}

双读期里,客服页同时显示 legacy plan、Stripe Product、Active Entitlement、entitlements_v2_verified 和最后一个 webhook event id。差异超过 24 小时还没解释清楚,就不要继续扩大迁移批次;老客户默认保留原产品访问,不在账期中途因为新快照缺失而降级。

客服查账要看哪三张表?

客户说「我付费了,为什么看不到高级报表」,客服不要只看 Stripe Dashboard 的 invoice 是否 paid。正确问题是四个:付款有没有成功,订阅状态是否允许开通,Stripe 有没有 active entitlement,本地权限快照有没有写入。

最小查账表建议三张:

表名关键字段客服能回答什么
billing_customersuser_idstripe_customer_iddefault_workspace_id这个用户对应哪个 Stripe Customer
entitlement_snapshotsstripe_customer_idlookup_keysource_event_idsynced_atactive当前哪些功能已经开通或撤销
billing_event_logevent_idevent_typesubscription_idinvoice_idprocessed_atstatuserrorwebhook 是否到达、是否处理失败

客服页面上不要只显示绿色或红色。要显示「Stripe 侧看到的权益」和「产品侧实际启用的权益」。如果 advanced_reports 在 Stripe Active Entitlements 里存在,但产品侧菜单仍关闭,问题通常在 webhook 处理器、缓存刷新或 workspace 绑定上。

推荐客服查询顺序:

  1. 用用户邮箱找到 stripe_customer_id
  2. 调 Stripe List Active Entitlements,确认 lookup_key 列表。
  3. 打开本地 entitlement_snapshots,看 source_event_idsynced_at
  4. billing_event_log 里同一个 event 是否处理失败。
  5. 如果是 seat 问题,再查 subscription item quantity 和已占席位表。

这套顺序不会给税务、收入确认或合同解释下结论,只解决产品访问和客服查账。

Stripe Dashboard 多人操作怎么留痕?

一个人公司也会出现多人操作:创始人改 Product,兼职客服查发票,工程师重放 webhook,外包开发看日志。Stripe Dashboard、部署后台、数据库和客服系统分散在不同环境时,最怕同一笔订单被两个人各改一次。

涉及 Product Feature、Subscription、Customer 和 webhook replay 的动作,建议固定后台负责人,并用Stripe Dashboard 稳定访问承载核心后台操作。它只是让后台操作环境更可控,不能替代 Stripe 的身份验证、真实订单记录、权限分配和工单证据。

团队内部可以把高风险动作拆成三类:

动作谁能做必留字段
attach / remove Product Feature负责 billing 的工程师product id、feature lookup_key、操作者、变更原因
手工补开权益客服负责人 + 工程确认user id、feature、到期时间、ticket id
重放 webhook工程师event id、重放时间、处理结果、错误信息

如果使用 Customer Portal 给用户自助管理订阅,也不要把 Portal 当成权限事实源。Portal 解决客户侧账单和订阅管理入口,产品开关仍以 webhook 和本地 snapshot 为准。

审计流水要记到什么粒度?

这里的审计流水指产品内部事故回放,不是会计或合规承诺。目标很简单:三个月后客户问为什么 4 月 12 日失去了 priority_support,你能把 Subscription、Active Entitlement、本地快照和人工动作串起来。

建议记录五类事件:

事件触发来源最少字段
entitlement_snapshot_refreshedwebhook 或手工补拉customer、lookup_key 列表、source event id
feature_access_granted本地权限服务user、workspace、feature、reason
feature_access_revoked本地权限服务user、workspace、feature、reason
manual_entitlement_override客服或工程后台operator、ticket、expires_at、old value、new value
subscription_quantity_changedStripe subscription itemsubscription item id、old quantity、new quantity

entitlements.active_entitlement_summary.updated 的摘要 payload 里最多只有 10 条 entitlements。客户权益多于 10 条时,要按 payload 里的 URL 或 List Active Entitlements API 拉完整分页列表,再把 snapshot 覆盖成同一个版本。否则客服页可能只看到前 10 个功能,误判成权限丢失。

哪些事情别交给 Entitlements?

Entitlements 不适合处理所有付费例外。运行时 kill switch、内部灰度、黑名单、企业合同特殊条款、一次性人工补偿、税务处理、收入确认和合规判断,都不要塞进 Stripe Feature。

也不要把营销功能和真实权限混在一起。Stripe Product 支持 pricing page 上的 marketing features,但那是展示给客户看的卖点;Entitlements Feature 是系统里可授权的能力。价格页写「10x productivity」没有问题,权限表里应该只有 workflow_automationbulk_export

上线前的停止条件很明确:

信号为什么要停下一步
lookup_key 还在频繁改代码、webhook 和客服表都会被牵连冻结命名表,先只改前台文案
老客户手工权益很多自动迁移会误伤合同或补偿单独建 override 表
webhook 没有幂等重放可能重复开通或撤销给 event id 和 customer-feature 加唯一约束
客服只能看 invoice付费成功不等于权限写入成功补 entitlement snapshot 页面
席位只存在 Stripe quantity无法解释已占席位和邀请状态建 workspace seat 表

等这几项补齐后,再把新客户全量切到 Entitlements。老客户继续按批次迁移,发现 Stripe 和本地状态不一致时,以保留现有产品访问为默认动作,再让工程查账,不要在客服窗口里现场改套餐。

相关阅读