Herald
积分

支付墙配置(支付授予角色)

在 Entitlement Mapping 上配置「授予角色」就是 Herald 的支付墙:支付成功自动授角色,取消和全额退款自动回收(部分退款保留买断角色),你的应用用现有 RBAC 判断放行。

在 Entitlement Mapping 上配置「授予角色」就是 Herald 的支付墙:支付成功自动授角色,取消和全额退款自动回收(部分退款保留买断角色),你的应用用现有 RBAC 判断放行。

Herald 没有独立的 paywall 模块,也不解释「功能」的语义。它提供一条映射管道:支付方商品 → entitlement_key → 角色 → 权限。你的应用照常查权限,权限从哪来(手工授予还是支付授予)对检查代码不可见。

给谁看

两类人。配置映射的 Realm Admin(需要 billing.manage),以及在应用里做权限判断的开发者。如果你还不知道积分账户、映射、订阅投影这些概念,先读计费架构。

三个正交维度

每条 Entitlement Mapping 上有三个互不影响的配置维度:

  • billing_type:购买形态(recurring / one_time / non_renewing)
  • 积分分发规则:发不发积分、发多少、有效期多长
  • 授予角色:支付成功后解锁哪些角色

三个维度可以任意组合,任一为空都合法:

组合含义重复购买角色撤销
recurring + 积分 + 角色会员订阅(发积分 + 解锁)续费续授取消、过期、退款撤
recurring + 无积分 + 角色纯会员墙(只解锁)续费续授取消、过期、退款撤
non_renewing + 可选积分 + 角色固定期限权益受活跃订阅约束到期、取消、退款撤
one_time + 积分 + 无角色积分包可重复买不适用
one_time + 无积分 + 角色纯永久权益墙(买断)一人一次部分退款保留;累计退款达原支付金额时回收
one_time + 积分 + 角色买断礼包一人一次部分退款保留;累计退款达原支付金额时回收

买断不设过期:validity_days 只约束积分有效期,不约束角色。订阅角色同理,user_roles.expires_at 只保存支付周期信息,权限检查不按它过滤,回收靠 webhook 事件驱动。

配置步骤

前置条件

  1. 至少一家支付方已配置。见 Stripe、Creem、IAP、WeChat Pay 对接指南。
  2. 在角色管理里创建要授予的自定义角色。映射表单的角色多选只列非内置角色。
  3. 权限:侧边栏入口需要 billing.view,创建和修改映射需要 billing.manage。如果同时改积分规则,还需要 points.manage。

创建映射

管理后台侧边栏 Billing → Entitlement Mappings → Create,路径 /manage/billing/entitlement-mappings/new。和支付墙相关的字段:

字段说明
payment_providerstripe / creem / apple / google / wechat
external_product_id支付方侧的商品 ID;Stripe 价格另填 external_price_id
entitlement_key内部权益标识,如 pro-plan
billing_typerecurring 需配 billing_period;non_renewing 需配 service_duration_days;WeChat 不支持 recurring
point_rules积分分发规则。纯权益墙留空即可,不会报错
granted_role_ids授予角色多选,可绑多个。留空 = 纯积分或纯支付记录
price / currency仅 WeChat:没有商品目录,价格(分为单位)直接配在映射上

Stripe 和 Creem 也可以先从支付方目录同步生成映射,再在列表页补配角色,效果一样。

修改授予角色

映射列表页每个商品行的编辑器里有「授予角色」多选,保存走批量更新端点,整批一个事务。两个常见报错:

  • 400 role_not_in_realm:选了不属于本 Realm 的角色。角色选择是 Realm 作用域的,重新选本 Realm 的角色即可。
  • 409 mapping_in_use:批量里有仍被活跃订阅保护的行要做禁用。整批已回滚,处理完订阅再改。

购买侧的表现

one_time + 授予角色的商品强制一人一次。判断依据两条:用户已拥有该映射配置的任一角色,或已存在该商品的成功支付尝试。命中任意一条,用户购买页(/user/purchase-points)会禁用该选项并标记「已拥有」;绕过前端直接创建支付尝试,后端返回 409:

{ "code": "already_owned", "entitlementKey": "pro-plan" }

并发双购由数据库兜底:payment_attempts 上 (user_id, target_id) 且 status='Succeeded' AND is_one_time_role=TRUE 的部分唯一索引保证至多一单成功。

积分包(one_time 无角色)不受限,可以重复购买;订阅续费也不受限。

撤销规则

角色回收只动「支付授予」来源的关联。user_roles.source 区分支付授予与手工授予,管理员手工给用户挂的同名角色在回收时保留。

场景行为
订阅取消、过期、退款回收支付来源角色,容忍窗口分钟级(webhook + 补偿框架,最终一致)
订阅续费续授角色
non_renewing 到期、取消、退款回收支付来源角色
买断退款覆盖整笔支付(单笔全额,或多笔部分退款累计到全额)回收该笔支付来源角色,清掉该来源剩余积分
买断部分退款角色保留;积分照常按单笔退款比例回收
买断正常取消、到期不触碰角色(一次性 = 永久解锁)

两个边界情况:

  • customer.subscription.deleted 是终态。终态之后迟到的 invoice.payment_succeeded 续费事件按非法状态迁移拒绝(400),已回收的角色不会复活。
  • WeChat 渠道没有退款信号源。商户侧退款在 Herald 内不产生任何事件,买断退款后角色无法自动回收。要退款,先在 Herald 手工移除该用户的支付来源角色,再走商户退款流程。
  • 争议事件不回收买断角色。Stripe、Creem 的争议处理只覆盖订阅,买断被争议时角色保留,部分退款换来的保留也不会因争议失效。该能力缺口另行方案解决。

应用侧怎么判断

你的应用不接新接口,也不需要知道角色是支付授予的还是手工授予的。推荐做法是给授予的角色挂上权限点(比如 pro:read),然后走现有 RBAC 检查:

  • 服务端:按第三方应用集成的 Herald 认证中间件接入,把请求路径映射成 resource/action 对,中间件拿着用户 token 查权限。Herald 内置动作层级:manage 覆盖 view,view 只匹配自己。
  • 浏览器端:herald-auth-web 的 client.getStatus() 返回会话快照,里面带 permissions 数组,直接判断。
  • 需要角色列表本身时,用 GET /api/user/roles 查当前用户的角色。

权限点的 resource/action 完全由你的应用定义,Herald 不存储也不解释功能语义。

上线前自查

  1. 买一单:用户在支付方完成付款后,检查用户角色里出现了映射配置的角色(管理后台用户页的角色对话框,或 GET /api/user/roles)。
  2. 全额退一单(Stripe / Creem / IAP 渠道):支付来源角色被回收,手工授予的同名角色还在。部分退款则保留角色,积分按比例回收。
  3. 同一买断商品买第二次:购买页显示「已拥有」,直接调 API 得到 409 already_owned。
  4. WeChat 渠道:确认商户退款流程里包含「先手工移除支付来源角色」这一步,否则退款用户永久保留角色。

相关文档

On this page