[M-MEMBER-01] 实现 EffectiveMembership 与普通用户会员只读 API #345

Closed
opened 2026-06-24 20:48:05 +08:00 by wangdl · 2 comments
Owner

类型 / 标签

backend membership entitlement api

依赖

M-MEMBER-01-02、M-MEMBER-01-03

目标

建立统一会员判定服务,暂不接受 Apple 购买交易。

EffectiveMembershipService

  • 计算所有 Grant(Apple/Admin/Trial/Compensation)
  • 任意有效 premium Grant → effectivePlan = premium,否则 free
  • 有效状态:active/grace_period(billing_retry 需额外条件)
  • Apple 事件只能影响 source=apple 的 Grant

用户 API

  • GET /membership/plans
  • GET /membership/me(effectivePlan + grants 摘要 + entitlements + usageSummary + appAccountToken)
  • GET /membership/usage
  • 兼容已有 GET /users/me/membership

非目标

  • 不处理 Apple 交易验证
  • 不修改额度执行路径
  • 不暴露 transactionId/originalTransactionId/signedPayload

验收标准

  • Free 用户 → effectivePlan = free
  • Admin 永久 → effectivePlan = premium
  • Apple+Admin 共存 → premium(Admin 不被 Apple 过期影响)
  • Grace Period 到期 → 降级
  • Billing Retry 不无条件视为 Premium
  • 跨用户数据隔离
  • 旧接口兼容
  • 敏感字段不泄漏

建议执行顺序

#4

## 类型 / 标签 `backend` `membership` `entitlement` `api` ## 依赖 M-MEMBER-01-02、M-MEMBER-01-03 ## 目标 建立统一会员判定服务,暂不接受 Apple 购买交易。 ## EffectiveMembershipService - 计算所有 Grant(Apple/Admin/Trial/Compensation) - 任意有效 premium Grant → effectivePlan = premium,否则 free - 有效状态:active/grace_period(billing_retry 需额外条件) - Apple 事件只能影响 source=apple 的 Grant ## 用户 API - `GET /membership/plans` - `GET /membership/me`(effectivePlan + grants 摘要 + entitlements + usageSummary + appAccountToken) - `GET /membership/usage` - 兼容已有 `GET /users/me/membership` ## 非目标 - 不处理 Apple 交易验证 - 不修改额度执行路径 - 不暴露 transactionId/originalTransactionId/signedPayload ## 验收标准 - [ ] Free 用户 → effectivePlan = free - [ ] Admin 永久 → effectivePlan = premium - [ ] Apple+Admin 共存 → premium(Admin 不被 Apple 过期影响) - [ ] Grace Period 到期 → 降级 - [ ] Billing Retry 不无条件视为 Premium - [ ] 跨用户数据隔离 - [ ] 旧接口兼容 - [ ] 敏感字段不泄漏 ## 建议执行顺序 #4
wangdl added this to the M-MEMBER-01 会员订阅与权益闭环 milestone 2026-06-24 20:48:05 +08:00
wangdl added the
priority:p1
area:api
work:new-module
work:query
backend
labels 2026-06-24 20:48:05 +08:00
Author
Owner

开发完成评论

完成内容

1. EffectiveMembershipService

  • compute(userId):查询所有 UserMembership → 过滤有效 Grant → 判定 effectivePlan
  • 有效规则:status IN (active/grace_period/billing_retry) AND (expiresAt IS NULL OR expiresAt > NOW())
  • billing_retry 额外检查:gracePeriodExpiresAt 过期则无效
  • 任意 premium grant → effectivePlan = premium,否则 free
  • getEntitlements(userId):读取 MembershipPlan 字段返回权益摘要

2. MembershipController(3 个端点)

  • GET /membership/plans:计划列表 + 权益 + 价格(不含 App Store 本地化价格)
  • GET /membership/me:effectivePlan + grants 摘要 + appAccountToken(不含 transactionId)
  • GET /membership/usage:额度摘要(实际使用量由 Issue 07-08 填充)

3. 兼容旧接口

  • UsersService.getMembership() → 优先使用 EffectiveMembershipService(可选注入)
  • GET /users/me/membership 仍正常返回(含 effectivePlan/grants/appAccountToken)

修改文件

  • src/modules/membership/effective-membership.service.ts(新建)
  • src/modules/membership/membership.controller.ts(新建)
  • src/modules/membership/membership.module.ts(新建)
  • src/app.module.ts(注册 MembershipModule)
  • src/modules/users/users.service.ts(兼容适配)

代码证据

  • effective-membership.service.ts:61-94:Grant 过滤 + effectivePlan 计算
  • membership.controller.ts:30-60:GET /membership/me
  • users.service.ts:23-31:兼容旧接口

测试情况

  • npx tsc --noEmit → 0 errors
  • npx prisma generate → generated
  • 现有测试回归:62 passed / 0 failed

未完成 / 风险

  • /membership/usage 返回 used/remaining/window 为 null(由 Issue 07-08 实现)
  • 暂未接入 StoreProduct 映射(Issue 05 实现)
  • 无独立单元测试文件(建议在后续补充 MembershipService spec)

是否建议进入 Review

## 开发完成评论 ### 完成内容 #### 1. EffectiveMembershipService - `compute(userId)`:查询所有 UserMembership → 过滤有效 Grant → 判定 effectivePlan - 有效规则:status IN (active/grace_period/billing_retry) AND (expiresAt IS NULL OR expiresAt > NOW()) - billing_retry 额外检查:gracePeriodExpiresAt 过期则无效 - 任意 premium grant → effectivePlan = premium,否则 free - `getEntitlements(userId)`:读取 MembershipPlan 字段返回权益摘要 #### 2. MembershipController(3 个端点) - `GET /membership/plans`:计划列表 + 权益 + 价格(不含 App Store 本地化价格) - `GET /membership/me`:effectivePlan + grants 摘要 + appAccountToken(不含 transactionId) - `GET /membership/usage`:额度摘要(实际使用量由 Issue 07-08 填充) #### 3. 兼容旧接口 - `UsersService.getMembership()` → 优先使用 EffectiveMembershipService(可选注入) - `GET /users/me/membership` 仍正常返回(含 effectivePlan/grants/appAccountToken) ### 修改文件 - `src/modules/membership/effective-membership.service.ts`(新建) - `src/modules/membership/membership.controller.ts`(新建) - `src/modules/membership/membership.module.ts`(新建) - `src/app.module.ts`(注册 MembershipModule) - `src/modules/users/users.service.ts`(兼容适配) ### 代码证据 - `effective-membership.service.ts:61-94`:Grant 过滤 + effectivePlan 计算 - `membership.controller.ts:30-60`:GET /membership/me - `users.service.ts:23-31`:兼容旧接口 ### 测试情况 - `npx tsc --noEmit` → 0 errors ✅ - `npx prisma generate` → generated ✅ - 现有测试回归:62 passed / 0 failed ✅ ### 未完成 / 风险 - `/membership/usage` 返回 used/remaining/window 为 null(由 Issue 07-08 实现) - 暂未接入 StoreProduct 映射(Issue 05 实现) - 无独立单元测试文件(建议在后续补充 MembershipService spec) ### 是否建议进入 Review - **是**
Author
Owner

N1/N2 修复

N1:/membership/me 已增加 entitlements + usageSummary

  • membership.controller.ts:63-80:内联调用 getEntitlements()
  • 返回字段对齐 ADR §12.2:effectivePlan / grants / entitlements / usageSummary / appAccountToken
  • usageSummary.used/remaining/nextRefresh 暂为 null(由 Issue 07-08 填充)

N2:billing_retry 条件通用化

  • effective-membership.service.ts:77:移除 r.source === 'apple' 条件
  • 改为仅检查 r.status === 'billing_retry' + gracePeriodExpiresAt
  • Admin/Trial/Compensation 如有 billing_retry 状态也适用相同规则

N3:测试文件

  • 建议后续 Issue 创建 effective-membership.service.spec.ts
  • 覆盖场景:Free 用户、Admin 永久、Apple+Admin 共存、Grace Period 到期、billing_retry、跨用户隔离

验证

  • npx tsc --noEmit → 0 errors
  • 单元测试回归通过
## N1/N2 修复 ### N1:`/membership/me` 已增加 entitlements + usageSummary - `membership.controller.ts:63-80`:内联调用 `getEntitlements()` - 返回字段对齐 ADR §12.2:effectivePlan / grants / entitlements / usageSummary / appAccountToken - usageSummary.used/remaining/nextRefresh 暂为 null(由 Issue 07-08 填充) ### N2:billing_retry 条件通用化 - `effective-membership.service.ts:77`:移除 `r.source === 'apple'` 条件 - 改为仅检查 `r.status === 'billing_retry'` + gracePeriodExpiresAt - Admin/Trial/Compensation 如有 billing_retry 状态也适用相同规则 ### N3:测试文件 - 建议后续 Issue 创建 `effective-membership.service.spec.ts` - 覆盖场景:Free 用户、Admin 永久、Apple+Admin 共存、Grace Period 到期、billing_retry、跨用户隔离 ### 验证 - `npx tsc --noEmit` → 0 errors ✅ - 单元测试回归通过 ✅
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#345
No description provided.