Files
eryao/.trellis/tasks/archive/2026-04/04-27-feat-ios-apple-pay/IMPLEMENTATION_PLAN.md
T

12 KiB
Raw Blame History

iOS Apple Pay 落实计划

当前状态

已完成

  • 协议文档更新(user-points-chat-data-protocol.mdhttp-error-codes.md
  • PRD 退款扣回策略已明确
  • 套餐配置 YAML 已正确命名(new_user_pack, starter_pack 等)
  • Phase 1: 数据库与枚举2026-04-27 完成)
  • Phase 2: 后端支付服务2026-04-27 完成)
  • Phase 3: iOS / Flutter IAP 接入2026-04-27 完成)

待实现

  • 联调与发布准备(Phase 4

Phase 1: 数据库与枚举(后端基础) 已完成

1.1 枚举扩展

文件: backend/src/schemas/enums.py

  • PointsChangeType 新增 PURCHASE, REFUND,移除 GRANT
  • PointsBizType 新增 PAYMENT

文件: backend/src/schemas/domain/points.py

  • 更新 ApplyPointsChangeCommand 验证逻辑,支持 purchase/refund
  • 移除 GrantLedgerMetadata

1.2 数据库迁移

迁移文件: backend/alembic/versions/20260427_0001_apple_iap_transactions.py

  • 创建 apple_iap_transactions 表(按 PRD 5.3 定义)
  • 更新 points_ledger check constraints
    • change_type in ('register', 'consume', 'adjust', 'purchase', 'refund')
    • biz_type is null or biz_type in ('chat', 'payment')
    • 更新 ck_points_ledger_biz_binding 支持 purchase/refund
    • 更新 ck_points_ledger_direction_by_change_type 支持 purchase(direction=1)refund(direction=-1)
    • 新增 ck_points_ledger_metadata_payment_shapeck_points_ledger_metadata_refund_shape
    • 更新 ck_points_ledger_metadata_adjust_shapeticket_id -> reason
  • 更新 points_audit_ledger check constraints 同步变更

模型文件:

  • backend/src/models/points_ledger.py - 同步更新 SQLAlchemy CheckConstraint
  • backend/src/models/apple_iap_transaction.py - 新建模型
  • backend/src/models/__init__.py - 导出新模型

1.3 验证

  • 迁移已应用到数据库
  • apple_iap_transactions 表已创建
  • points_ledger 约束已更新

Phase 2: 后端支付服务 已完成

2.1 配置扩展

  • backend/src/core/config/settings.py - 新增 AppleIapSettingsapple_iap 配置项
  • backend/src/core/config/static/packages/mapping.yaml - productCode -> App Store Product ID 映射

2.2 API Schemas

  • backend/src/v1/payments/schemas.py - VerifyTransactionRequest / VerifyTransactionResponse
  • backend/src/schemas/domain/points.py - 新增 PurchaseLedgerMetadata

2.3 Apple JWS 验签器

  • backend/src/v1/payments/apple_verifier.py
    • JWS x5c 证书链验证(root fingerprint + issuer/subject chain
    • bundleId / productId / environment / revocationDate 验证
    • 返回 VerifiedTransaction | VerificationError

2.4 支付数据仓库

  • backend/src/v1/payments/repository.py - PaymentRepository
    • get_or_create_user_points_for_update
    • get_user_points_for_update (for refund, no auto-create)
    • get_transaction_by_transaction_id
    • insert_transaction
    • get_register_bonus_claim
    • upsert_register_bonus_claim_for_starter_pack

2.5 支付服务

  • backend/src/v1/payments/service.py - PaymentService
    • verify_and_grant: productCode / appStoreProductId 校验, Apple JWS 验签, transaction_id 幂等, 新手包资格检查, 积分入账 + points_ledger + register_bonus_claims
    • process_refund: 退款扣回积分, 余额不足时扣到 0 并标记 refunded_insufficient, 幂等处理
    • handle_server_notification: 解析 Apple Server Notifications V2, REFUND/REVOKE 触发退款, DID_RENEW 记录日志

2.6 API 路由 + 依赖注入

  • backend/src/v1/payments/router.py
    • POST /api/v1/payments/apple/transactions/verify
    • POST /api/v1/payments/apple/notifications
  • backend/src/v1/payments/dependencies.py - DI wiring
  • backend/src/v1/router.py - 注册 payments_router

2.7 测试

  • backend/tests/unit/payments/test_payment_service.py - 16 个测试全部通过
    • 验证流程: product_not_found / product_mismatch / verification_failed / already_granted / transaction_conflict / successful_grant / starter_pack_ineligible / starter_pack_success
    • 退款流程: refund_unknown / refund_not_granted / refund_sufficient_balance / refund_insufficient_balance / refund_idempotency
    • 通知处理: notification_refund / notification_empty / notification_non_refund
  • backend/tests/unit/payments/__init__.py - verifier 基础测试
  • backend/tests/integration/payments/test_verify_flow.py - 集成测试骨架
  • basedpyright 类型检查通过(0 errors
  • 所有模块 import 正常

未实现(后续迭代)

  • GET /api/v1/payments/apple/transactions/{transactionId} 查询接口
  • apple_client.py Apple Server API 主动查询客户端(可选)

2.2 核心实现

2.2.1 Apple JWS 验签器

文件: backend/src/v1/payments/apple_verifier.py

职责:

  • 下载/缓存 Apple 根证书链
  • 验证 JWS 签名
  • 解析 payload 并验证字段:bundleId, productId, transactionId, environment, revocationDate
  • 返回结构化验证结果

2.2.2 支付服务

文件: backend/src/v1/payments/service.py

核心方法:

  • verify_and_grant(user_id, request) -> VerifyResponse

      1. 验证 productCode 存在且启用
      1. 验证 appStoreProductId 与映射匹配
      1. 调用 Apple verifier 验签
      1. 检查 transaction 幂等(已发放返回 already_granted
      1. 检查新手包资格
      1. 事务:创建/更新 apple_iap_transactions + 更新 user_points + 写入 points_ledger + 更新 register_bonus_claims
  • process_refund(transaction_id) -> None

      1. 查询 apple_iap_transactions
      1. 事务:扣减积分 + 写入 refund 流水 + 更新状态
      1. 余额不足时设置 refunded_insufficient 并告警

2.2.3 API 接口

文件: backend/src/v1/payments/router.py

POST /api/v1/payments/apple/transactions/verify
POST /api/v1/payments/apple/notifications  # App Store Server Notifications V2
GET  /api/v1/payments/apple/transactions/{transactionId}  # 可选

2.3 配置扩展

文件: backend/src/core/config/settings.py

新增配置项:

apple_iap: AppleIapSettings

class AppleIapSettings:
    bundle_id: str
    root_cert_url: str = "https://www.apple.com/certificateauthority/AppleIncRootCertificate.cer"
    jws_issuer_id: str | None = None  # Server API (可选)
    jws_key_id: str | None = None
    jws_private_key: str | None = None

文件: backend/src/core/config/static/packages/mapping.yaml (新建)

product_mappings:
  new_user_pack:
    app_store_product_id: com.meeyao.qianwen.new_user_pack
    credits: 60
    type: starter
  starter_pack:
    app_store_product_id: com.meeyao.qianwen.starter_pack
    credits: 100
    type: regular
  popular_pack:
    app_store_product_id: com.meeyao.qianwen.popular_pack
    credits: 210
    type: regular
  premium_pack:
    app_store_product_id: com.meeyao.qianwen.premium_pack
    credits: 415
    type: regular

2.4 测试

单元测试:

  • backend/tests/unit/payments/test_apple_verifier.py
  • backend/tests/unit/payments/test_payment_service.py

集成测试:

  • backend/tests/integration/payments/test_verify_flow.py

Phase 3: iOS / Flutter 接入 已完成

3.1 依赖添加

  • apps/pubspec.yamlin_app_purchase: ^3.2.3 + in_app_purchase_storekit: ^0.4.8 + crypto: ^3.0.7

3.2 后端配合变更

  • backend/src/v1/points/schemas.pyPackageInfo 新增 appStoreProductId 字段
  • backend/src/v1/points/service.pyPackageInfoResult 新增 app_store_product_idget_available_packages 从 mapping.yaml 加载映射
  • backend/src/v1/points/router.py — 响应中包含 appStoreProductId

3.3 前端目录结构

apps/lib/features/payments/
├── data/
│   ├── apis/
│   │   └── apple_payment_api.dart    # 后端 verify 接口
│   ├── models/
│   │   └── apple_purchase_models.dart # VerifyTransactionRequest/Response
│   └── services/
│       └── apple_iap_service.dart    # StoreKit 集成服务

3.4 核心实现

  • apple_purchase_models.dart — VerifyTransactionRequest(含 appAccountToken/ VerifyTransactionResponse(含 newBalance/ledgerEventId
  • apple_payment_api.dart — POST /api/v1/payments/apple/transactions/verify
  • apple_iap_service.dart — AppleIapService (ChangeNotifier):
    • 初始化 purchaseStream 监听
    • queryProductDetails 查询 StoreKit 商品
    • buyConsumable 传递 applicationUserNameappAccountToken = userId MD5 hash
    • 购买成功 → 发送 JWS + appAccountToken 到后端验证 → completePurchase
    • 可重试错误(5xx/网络)不 complete,下次启动自动重试
    • 不可重试错误(4xxcomplete 并暴露 ApiProblem 供 UI 映射 l10n
    • 暴露 lastApiProblem 供错误码映射
  • package_info.dart — 新增 appStoreProductId 字段
  • settings_section_widgets.dart — CoinPackageCard 新增 onPurchase/isPurchasing/isAvailable/unavailableMessage
  • coin_center_screen.dart — 集成 AppleIapService
    • 接收 userId/onBalanceChanged 参数
    • StoreKit 价格覆盖后端参考价格
    • 商品不可用时禁用卡片并显示提示
    • pending 状态显示 "Apple 正在处理中"
    • 购买成功后调用 _refreshBalance 并回调 onBalanceChanged
    • 使用 mapApiProblemToMessage 映射错误码到 l10n

3.5 调用链更新

  • app.dart — HomeScreen 传递 userId + onBalanceChanged
  • home_screen.dart_ProfileTab 传递 userId + onBalanceChanged
  • settings_screen.dart — 传递 userId + onBalanceChanged 给 CoinCenterScreen

3.6 错误处理与本地化

  • api_problem_mapper.dart — 6 个支付错误码映射
  • 3 个 ARB 文件新增 7 个 l10n key
    • paymentSuccess / paymentVerifyFailed / paymentProductNotFound / paymentStarterPackIneligible
    • paymentProductUnavailable / paymentPending
  • flutter gen-l10n 生成通过

3.7 前端测试

  • apps/test/features/payments/data/models/apple_purchase_models_test.dart
    • VerifyTransactionRequest.toJson 含/不含 appAccountToken
    • VerifyTransactionResponse 解析 granted / already_granted
  • 4 个测试全部通过

3.8 验证

  • flutter analyze 0 issues
  • 后端 basedpyright 0 errors
  • 后端 16 个单元测试全部通过

Phase 4: 联调与发布准备

4.1 App Store Connect 配置

  • 创建 4 个消耗型 IAP 商品(Product ID 已确认与映射表一致)
  • Product ID 与映射表一致
    • com.meeyao.qianwen.new_user_pack — 新手包
    • com.meeyao.qianwen.starter_pack — 入门包
    • com.meeyao.qianwen.popular_pack — 热门包
    • com.meeyao.qianwen.premium_pack — 高级包
  • 配置价格和描述
  • 创建沙盒测试账号:qiuzhiliang@xunmee.com
  • 配置 Server Notifications V2 URL(生产环境公网 URL

4.2 后端配置

  • 环境变量:APPLE_IAP_BUNDLE_ID=com.meeyao.qianwen
  • Server API 密钥配置(T6M7J28MAQ / 862a2cd0-ad6e-47c8-ac5a-bef5676c470b
  • 日志检查:不打印完整 JWS
  • 环境判断:根据 ERYAO_RUNTIME__ENVIRONMENT 自动切换 Sandbox/Production

4.3 开发环境准备

  • .env.example 更新 Apple IAP 配置模板
  • .env 写入实际配置值
  • AppleIapSettings 支持所有配置字段
  • EryaoProducts.storekit Xcode 本地测试配置文件

4.4 测试清单

  • Xcode StoreKit Configuration 本地测试
  • Sandbox 购买成功验证
  • Sandbox 退款测试
  • 网络中断后重启恢复
  • 新手包重复购买阻断
  • TestFlight 环境验证

预估工时

Phase 工作项 预估时间
1 枚举扩展 + 数据库迁移 2-3h
2 后端支付服务 + 测试 1-2 天
3 Flutter IAP 接入 1 天
4 联调与发布准备 0.5-1 天

总计: 3-4 天


风险与依赖

风险/依赖 缓解措施
App Store Connect 配置权限 提前确认账号权限,Phase 1 同步申请
in_app_purchase 插件无法暴露 signedData 预研后确定是否需要 platform channel
Apple 服务不可用 返回 PAYMENT_APPLE_UNAVAILABLE,前端保留交易