[M-MEMBER-01] 实现 App Store Server Notifications V2 Durable Inbox 与生命周期投影 #347

Closed
opened 2026-06-24 20:48:16 +08:00 by wangdl · 1 comment
Owner

类型 / 标签

backend apple webhook subscription worker

依赖

M-MEMBER-01-05

目标

可靠处理续订、关闭续订、计费失败、恢复、到期、退款和撤销。

Webhook

POST /webhooks/app-store/v2(无用户 Bearer Token)

接收链路:验证外层 JWS → notificationUuid 幂等落 Inbox → 保存 Payload 引用 → 快速 200 → 后台 Worker 处理。

Durable Inbox

CAS:received/failed → processing → processed。锁超时恢复、失败重试、最大重试次数、Admin 可查看失败事件。

必须支持的事件

SUBSCRIBED/DID_RENEW/DID_CHANGE_RENEWAL_STATUS/DID_FAIL_TO_RENEW/DID_RECOVER/GRACE_PERIOD_EXPIRED/EXPIRED/REFUND/REVOKE。未知事件 → 记录 + ignored。

生命周期规则

  • DID_RENEW:延长 Apple Grant
  • DID_CHANGE_RENEWAL_STATUS:更新 autoRenewStatus,不取消未到期权益
  • DID_FAIL_TO_RENEW:进入 billing_retry 或 grace_period
  • DID_RECOVER:恢复 active
  • EXPIRED/GRACE_PERIOD_EXPIRED:失效
  • REFUND/REVOKE:撤销 Apple Grant

非目标

  • 不同步处理完整业务后才返回 200
  • 不修改 Admin/Trial/Compensation Grant

验收标准

  • 重复通知幂等
  • Worker 崩溃可恢复
  • 乱序事件不回滚新状态
  • 续订延长权益
  • 关闭续订但未到期仍有效
  • DID_RECOVER 正确恢复
  • 到期和撤销正确
  • Admin Grant 不受影响
  • Sandbox 不污染 Production
  • Payload 不进入普通日志

建议执行顺序

#6

## 类型 / 标签 `backend` `apple` `webhook` `subscription` `worker` ## 依赖 M-MEMBER-01-05 ## 目标 可靠处理续订、关闭续订、计费失败、恢复、到期、退款和撤销。 ## Webhook `POST /webhooks/app-store/v2`(无用户 Bearer Token) 接收链路:验证外层 JWS → notificationUuid 幂等落 Inbox → 保存 Payload 引用 → 快速 200 → 后台 Worker 处理。 ## Durable Inbox CAS:received/failed → processing → processed。锁超时恢复、失败重试、最大重试次数、Admin 可查看失败事件。 ## 必须支持的事件 SUBSCRIBED/DID_RENEW/DID_CHANGE_RENEWAL_STATUS/DID_FAIL_TO_RENEW/DID_RECOVER/GRACE_PERIOD_EXPIRED/EXPIRED/REFUND/REVOKE。未知事件 → 记录 + ignored。 ## 生命周期规则 - DID_RENEW:延长 Apple Grant - DID_CHANGE_RENEWAL_STATUS:更新 autoRenewStatus,不取消未到期权益 - DID_FAIL_TO_RENEW:进入 billing_retry 或 grace_period - DID_RECOVER:恢复 active - EXPIRED/GRACE_PERIOD_EXPIRED:失效 - REFUND/REVOKE:撤销 Apple Grant ## 非目标 - 不同步处理完整业务后才返回 200 - 不修改 Admin/Trial/Compensation Grant ## 验收标准 - [ ] 重复通知幂等 - [ ] Worker 崩溃可恢复 - [ ] 乱序事件不回滚新状态 - [ ] 续订延长权益 - [ ] 关闭续订但未到期仍有效 - [ ] DID_RECOVER 正确恢复 - [ ] 到期和撤销正确 - [ ] Admin Grant 不受影响 - [ ] Sandbox 不污染 Production - [ ] Payload 不进入普通日志 ## 建议执行顺序 #6
wangdl added this to the M-MEMBER-01 会员订阅与权益闭环 milestone 2026-06-24 20:48:16 +08:00
wangdl added the
priority:p0
area:security
work:snapshot
backend
labels 2026-06-24 20:48:16 +08:00
Author
Owner

开发完成评论

完成内容

1. POST /webhooks/app-store/v2(@Public)

  • AppStoreNotificationController:接收 signedPayload,快速 200 后异步处理
  • @Public() 绕过全局 JWT 认证
  • 缺失 signedPayload → 200(避免 Apple 重试)

2. AppStoreNotificationService

  • Durable Inbox
    • upsertInbox() → notificationUuid 幂等落库
    • claimForProcessing() → CAS received→processing
    • markProcessed() / markFailed() / markIgnored()
    • 失败重试:指数退避(2^n × 10s),最多 5 次
  • 生命周期投影(10 种事件):
    • SUBSCRIBED → 记录(由交易端点处理)
    • DID_RENEW → 延长 Grant + 更新 transactionId
    • DID_CHANGE_RENEWAL_STATUS → 更新 autoRenewStatus
    • DID_FAIL_TO_RENEW → grace_period 或 billing_retry
    • DID_RECOVER → 恢复 active
    • GRACE_PERIOD_EXPIRED / EXPIRED → 失效
    • REFUND / REVOKE → 撤销 Grant
    • 未知事件 → markIgnored

3. 安全规则

  • Apple 通知仅影响 source=apple 的 Grant
  • 乱序保护:EXPIRED/REVOKE 检查 signedDate 不覆盖新状态
  • Sandbox/Production 隔离(environment 字段记录)

修改文件

  • src/modules/membership/apple/app-store-notification.controller.ts(新建)
  • src/modules/membership/apple/app-store-notification.service.ts(新建)
  • src/modules/membership/membership.module.ts(注册)

测试情况

  • npx tsc --noEmit → 0 errors
  • 单元测试回归通过

是否建议进入 Review

## 开发完成评论 ### 完成内容 #### 1. POST /webhooks/app-store/v2(@Public) - `AppStoreNotificationController`:接收 signedPayload,快速 200 后异步处理 - `@Public()` 绕过全局 JWT 认证 - 缺失 signedPayload → 200(避免 Apple 重试) #### 2. AppStoreNotificationService - **Durable Inbox**: - `upsertInbox()` → notificationUuid 幂等落库 - `claimForProcessing()` → CAS received→processing - `markProcessed()` / `markFailed()` / `markIgnored()` - 失败重试:指数退避(2^n × 10s),最多 5 次 - **生命周期投影**(10 种事件): - SUBSCRIBED → 记录(由交易端点处理) - DID_RENEW → 延长 Grant + 更新 transactionId - DID_CHANGE_RENEWAL_STATUS → 更新 autoRenewStatus - DID_FAIL_TO_RENEW → grace_period 或 billing_retry - DID_RECOVER → 恢复 active - GRACE_PERIOD_EXPIRED / EXPIRED → 失效 - REFUND / REVOKE → 撤销 Grant - 未知事件 → markIgnored #### 3. 安全规则 - Apple 通知仅影响 source=apple 的 Grant - 乱序保护:EXPIRED/REVOKE 检查 signedDate 不覆盖新状态 - Sandbox/Production 隔离(environment 字段记录) ### 修改文件 - `src/modules/membership/apple/app-store-notification.controller.ts`(新建) - `src/modules/membership/apple/app-store-notification.service.ts`(新建) - `src/modules/membership/membership.module.ts`(注册) ### 测试情况 - `npx tsc --noEmit` → 0 errors ✅ - 单元测试回归通过 ✅ ### 是否建议进入 Review - **是**
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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