12 KiB
12 KiB
iOS Apple Pay 落实计划
当前状态
已完成
- 协议文档更新(
user-points-chat-data-protocol.md、http-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,移除GRANTPointsBizType新增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_ledgercheck 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_shape和ck_points_ledger_metadata_refund_shape - 更新
ck_points_ledger_metadata_adjust_shape(ticket_id->reason)
- 更新
points_audit_ledgercheck constraints 同步变更
模型文件:
backend/src/models/points_ledger.py- 同步更新 SQLAlchemy CheckConstraintbackend/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- 新增AppleIapSettings和apple_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/VerifyTransactionResponsebackend/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-PaymentRepositoryget_or_create_user_points_for_updateget_user_points_for_update(for refund, no auto-create)get_transaction_by_transaction_idinsert_transactionget_register_bonus_claimupsert_register_bonus_claim_for_starter_pack
2.5 支付服务 ✅
backend/src/v1/payments/service.py-PaymentServiceverify_and_grant: productCode / appStoreProductId 校验, Apple JWS 验签, transaction_id 幂等, 新手包资格检查, 积分入账 + points_ledger + register_bonus_claimsprocess_refund: 退款扣回积分, 余额不足时扣到 0 并标记refunded_insufficient, 幂等处理handle_server_notification: 解析 Apple Server Notifications V2, REFUND/REVOKE 触发退款, DID_RENEW 记录日志
2.6 API 路由 + 依赖注入 ✅
backend/src/v1/payments/router.pyPOST /api/v1/payments/apple/transactions/verifyPOST /api/v1/payments/apple/notifications
backend/src/v1/payments/dependencies.py- DI wiringbackend/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.pyApple 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-
- 验证 productCode 存在且启用
-
- 验证 appStoreProductId 与映射匹配
-
- 调用 Apple verifier 验签
-
- 检查 transaction 幂等(已发放返回
already_granted)
- 检查 transaction 幂等(已发放返回
-
- 检查新手包资格
-
- 事务:创建/更新
apple_iap_transactions+ 更新user_points+ 写入points_ledger+ 更新register_bonus_claims
- 事务:创建/更新
-
-
process_refund(transaction_id) -> None-
- 查询
apple_iap_transactions
- 查询
-
- 事务:扣减积分 + 写入
refund流水 + 更新状态
- 事务:扣减积分 + 写入
-
- 余额不足时设置
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.pybackend/tests/unit/payments/test_payment_service.py
集成测试:
backend/tests/integration/payments/test_verify_flow.py
Phase 3: iOS / Flutter 接入 ✅ 已完成
3.1 依赖添加 ✅
apps/pubspec.yaml—in_app_purchase: ^3.2.3+in_app_purchase_storekit: ^0.4.8+crypto: ^3.0.7
3.2 后端配合变更 ✅
backend/src/v1/points/schemas.py—PackageInfo新增appStoreProductId字段backend/src/v1/points/service.py—PackageInfoResult新增app_store_product_id,get_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/verifyapple_iap_service.dart— AppleIapService (ChangeNotifier):- 初始化 purchaseStream 监听
- queryProductDetails 查询 StoreKit 商品
buyConsumable传递applicationUserName(appAccountToken = userId MD5 hash)- 购买成功 → 发送 JWS + appAccountToken 到后端验证 → completePurchase
- 可重试错误(5xx/网络)不 complete,下次启动自动重试
- 不可重试错误(4xx)complete 并暴露 ApiProblem 供 UI 映射 l10n
- 暴露
lastApiProblem供错误码映射
package_info.dart— 新增appStoreProductId字段settings_section_widgets.dart— CoinPackageCard 新增onPurchase/isPurchasing/isAvailable/unavailableMessagecoin_center_screen.dart— 集成 AppleIapService:- 接收
userId/onBalanceChanged参数 - StoreKit 价格覆盖后端参考价格
- 商品不可用时禁用卡片并显示提示
- pending 状态显示 "Apple 正在处理中"
- 购买成功后调用
_refreshBalance并回调onBalanceChanged - 使用
mapApiProblemToMessage映射错误码到 l10n
- 接收
3.5 调用链更新 ✅
app.dart— HomeScreen 传递userId+onBalanceChangedhome_screen.dart—_ProfileTab传递userId+onBalanceChangedsettings_screen.dart— 传递userId+onBalanceChanged给 CoinCenterScreen
3.6 错误处理与本地化 ✅
api_problem_mapper.dart— 6 个支付错误码映射- 3 个 ARB 文件新增 7 个 l10n key:
paymentSuccess/paymentVerifyFailed/paymentProductNotFound/paymentStarterPackIneligiblepaymentProductUnavailable/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 analyze0 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.storekitXcode 本地测试配置文件
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,前端保留交易 |