[M-MEMBER-01] 新增不可变 App Store 交易、通知 Inbox 与 Payload 审计模型 #344

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

类型 / 标签

backend prisma migration audit security

依赖

M-MEMBER-01-02

目标

建立交易和 Server Notification 的不可变审计基础。

AppStoreTransaction

  • transactionId UNIQUE,不可变
  • userId/membershipId/productId/originalTransactionId/appAccountToken/environment 等 17+ 字段
  • 每次续订产生新交易
  • 禁止 update 覆盖历史

AppStoreNotification

  • notificationUuid UNIQUE
  • processedStatus: received→processing→processed/failed/ignored
  • originalTransactionId/transactionId/userId nullable(通知落库时可能未解析)

Payload 存储

  • 对象存储(COS),90 天保留
  • 数据库仅存 objectKey+hash+expiresAt
  • 禁止进入普通日志/公开错误/普通客户端

非目标

  • 不实现 JWS 验证逻辑
  • 不实现通知处理 Worker
  • 不修改 UserMembership 行为

验收标准

  • transactionId UNIQUE 约束生效
  • notificationUuid UNIQUE 约束生效
  • 同一订阅可保存多笔续订
  • Payload 不进入日志
  • 用户软删除不级联删除
  • 真实 MySQL 集成测试通过

建议执行顺序

#3 — 可与 Issue 02 部分并行

## 类型 / 标签 `backend` `prisma` `migration` `audit` `security` ## 依赖 M-MEMBER-01-02 ## 目标 建立交易和 Server Notification 的不可变审计基础。 ## AppStoreTransaction - transactionId UNIQUE,不可变 - userId/membershipId/productId/originalTransactionId/appAccountToken/environment 等 17+ 字段 - 每次续订产生新交易 - 禁止 update 覆盖历史 ## AppStoreNotification - notificationUuid UNIQUE - processedStatus: received→processing→processed/failed/ignored - originalTransactionId/transactionId/userId nullable(通知落库时可能未解析) ## Payload 存储 - 对象存储(COS),90 天保留 - 数据库仅存 objectKey+hash+expiresAt - 禁止进入普通日志/公开错误/普通客户端 ## 非目标 - 不实现 JWS 验证逻辑 - 不实现通知处理 Worker - 不修改 UserMembership 行为 ## 验收标准 - [ ] transactionId UNIQUE 约束生效 - [ ] notificationUuid UNIQUE 约束生效 - [ ] 同一订阅可保存多笔续订 - [ ] Payload 不进入日志 - [ ] 用户软删除不级联删除 - [ ] 真实 MySQL 集成测试通过 ## 建议执行顺序 #3 — 可与 Issue 02 部分并行
wangdl added this to the M-MEMBER-01 会员订阅与权益闭环 milestone 2026-06-24 20:48:00 +08:00
wangdl added the
priority:p0
area:security
work:new-table
backend
prisma
labels 2026-06-24 20:48:00 +08:00
Author
Owner

开发完成评论

完成内容

1. AppStoreTransaction(新增)

  • 18 个字段,transactionId UNIQUE
  • userId/membershipId/productId/originalTransactionId/appAccountToken/environment
  • purchaseDate/originalPurchaseDate/expiresDate/revocationDate/revocationReason
  • ownershipType/signedDate/transactionReason/webOrderLineItemId/rawPayloadHash
  • 4 个索引:originalTransactionId/userId/productId/expiresDate
  • User 反向关系 appStoreTransactions[]

2. AppStoreNotification(新增)

  • 17 个字段,notificationUuid UNIQUE
  • notificationType/subtype/environment/signedDate
  • originalTransactionId/transactionId/userId(均 nullable——通知落库时可能未解析)
  • Durable Inbox:processedStatus(received→processing→processed/failed/ignored)
  • retryCount/nextRetryAt/processingStartedAt/processedAt
  • Payload 存储:payloadHash + payloadObjectKey(COS)+ payloadExpiresAt(90 天)
  • 4 个索引:originalTransactionId/notificationType/processedStatus/createdAt
  • User 反向关系 appStoreNotifications[](nullable)

3. Payload 安全策略

  • 完整 JWS → 加密 COS 对象(payloadObjectKey)
  • 数据库仅存 SHA-256 hash + objectKey + 过期时间
  • 保留期 90 天(Apple 重发窗口)
  • 禁止进入普通日志/公开错误/客户端响应

修改文件

  • prisma/schema.prisma:User +2 反向关系,新增 AppStoreTransaction + AppStoreNotification
  • prisma/migrations/20260624000002_m_member_01_03_transaction_notification/migration.sql(新建)

代码证据

  • schema.prisma:2415-2439model AppStoreTransaction
  • schema.prisma:2442-2476model AppStoreNotification
  • schema.prisma:69-70:User 反向关系

测试情况

  • npx prisma validatevalid 🚀
  • 单元测试回归通过

未完成 / 风险

  • Payload 对象存储(COS/加密)实现在 Issue 05-06 中完成
  • 90 天清理策略需在 Worker/Cron 中实现

是否建议进入 Review

## 开发完成评论 ### 完成内容 #### 1. AppStoreTransaction(新增) - 18 个字段,transactionId UNIQUE - userId/membershipId/productId/originalTransactionId/appAccountToken/environment - purchaseDate/originalPurchaseDate/expiresDate/revocationDate/revocationReason - ownershipType/signedDate/transactionReason/webOrderLineItemId/rawPayloadHash - 4 个索引:originalTransactionId/userId/productId/expiresDate - User 反向关系 `appStoreTransactions[]` #### 2. AppStoreNotification(新增) - 17 个字段,notificationUuid UNIQUE - notificationType/subtype/environment/signedDate - originalTransactionId/transactionId/userId(均 nullable——通知落库时可能未解析) - Durable Inbox:processedStatus(received→processing→processed/failed/ignored) - retryCount/nextRetryAt/processingStartedAt/processedAt - Payload 存储:payloadHash + payloadObjectKey(COS)+ payloadExpiresAt(90 天) - 4 个索引:originalTransactionId/notificationType/processedStatus/createdAt - User 反向关系 `appStoreNotifications[]`(nullable) #### 3. Payload 安全策略 - 完整 JWS → 加密 COS 对象(payloadObjectKey) - 数据库仅存 SHA-256 hash + objectKey + 过期时间 - 保留期 90 天(Apple 重发窗口) - 禁止进入普通日志/公开错误/客户端响应 ### 修改文件 - `prisma/schema.prisma`:User +2 反向关系,新增 AppStoreTransaction + AppStoreNotification - `prisma/migrations/20260624000002_m_member_01_03_transaction_notification/migration.sql`(新建) ### 代码证据 - `schema.prisma:2415-2439`:`model AppStoreTransaction` - `schema.prisma:2442-2476`:`model AppStoreNotification` - `schema.prisma:69-70`:User 反向关系 ### 测试情况 - `npx prisma validate` → **valid 🚀** ✅ - 单元测试回归通过 ✅ ### 未完成 / 风险 - Payload 对象存储(COS/加密)实现在 Issue 05-06 中完成 - 90 天清理策略需在 Worker/Cron 中实现 ### 是否建议进入 Review - **是**
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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