7.6 KiB
7.6 KiB
iOS 新人包支付接入与一次性权益计划
1. 背景与目标
当前前端充值页为静态套餐展示,购买按钮未接入真实支付链路。现需新增 iOS 新人包:
- 价格:
$0.99 - 积分:
60 - 资格:同邮箱只能购买一次
- 删除账号后同邮箱重新注册,不刷新新人包资格
同时补齐后端真实支付路由与订单审计能力,前端不再硬编码套餐。
2. 本次范围
2.1 In Scope
- 后端新增 iOS 支付相关路由(下单/验单/查询/回调)。
- 新建支付订单主表与支付事件审计表。
- 改造
register_bonus_claims为可承载“权益唯一占用”能力。 - 前端套餐由后端接口驱动,不再硬编码三档固定套餐。
- 新人包资格前后端联动(展示、购买、验单、入账)。
2.2 Out of Scope
- Android 支付渠道接入。
- Apple 开发者账号正式联调(当前账号未就绪)。
- 财务对账后台页面。
3. 数据模型设计
3.1 新建表:payment_orders
用途:订单当前态,支持幂等验单与退款状态跟踪。
建议字段:
idUUID PKorder_noVARCHAR(64) UNIQUEuser_idUUID NOT NULL (auth.users.id)channelVARCHAR(16) NOT NULL (ios_iap)product_codeVARCHAR(64) NOT NULL(例:new_user_pack_099_60)price_usdNUMERIC(12,6) NOT NULLcreditsBIGINT NOT NULLcurrencyVARCHAR(8) NOT NULL DEFAULTUSDstatusVARCHAR(24) NOT NULLcreated|receipt_submitted|verified|credited|refund_pending|refunded|revoked|failed
apple_transaction_idVARCHAR(128) NULL UNIQUEapple_original_transaction_idVARCHAR(128) NULLapp_account_tokenUUID NULLidempotency_keyVARCHAR(128) NULL UNIQUEerror_codeVARCHAR(64) NULLerror_messageTEXT NULLcreated_at/updated_at
关键约束:
credits > 0price_usd >= 0statuscheckchannel='ios_iap'(本期)
3.2 新建表:payment_order_events
用途:支付事件不可变审计流水(验单结果、回调、退款、冲正)。
建议字段:
idUUID PKorder_idUUID NOT NULL FKpayment_orders.idevent_typeVARCHAR(32) NOT NULLorder_created|receipt_submitted|verify_success|verify_failed|credited|refund_notified|refunded|revoke_notified|reversed
event_sourceVARCHAR(24) NOT NULLapi|apple_server_notification|job
event_idempotency_keyVARCHAR(128) NULL UNIQUEpayloadJSONB NOT NULLoperator_idUUID NULLcreated_at
3.3 改造表:register_bonus_claims
目标:从“注册送分去重”升级为“权益唯一占用”。
新增字段建议:
offer_codeVARCHAR(64) NOT NULL(例:register_bonus_20、new_user_pack_099_60)claim_sourceVARCHAR(24) NOT NULL(register_bonus|ios_purchase)claim_order_idUUID NULL FKpayment_orders.id
新增唯一约束:
UNIQUE(offer_code, email_hash)
保留行为:
first_user_id允许ON DELETE SET NULL,保证删号后资格仍占用。
4. 路由与服务边界
4.1 后端新增路由(v1)
GET /api/v1/payments/packages- 返回可购买套餐列表与用户资格(是否可买新人包)。
POST /api/v1/payments/orders- 创建订单,返回
orderNo与客户端支付所需参数。
- 创建订单,返回
POST /api/v1/payments/orders/{orderNo}/verify-ios-receipt- 提交 iOS 收据,后端调用 Apple 校验。
GET /api/v1/payments/orders/{orderNo}- 查询订单状态与入账结果。
POST /api/v1/payments/webhooks/apple- 接收 App Store Server Notifications V2,处理退款/撤销。
4.2 分层职责
- Router:鉴权、请求校验、RFC7807 错误映射。
- Service:
- 资格判断(新人包是否可买)
- 下单与验单业务编排
- 入账积分与冲正
- 幂等控制
- Repository:
payment_orders/payment_order_events/register_bonus_claims读写- 订单状态流转条件更新
5. 核心流程
5.1 下单与资格检查
客户端请求套餐 -> GET /payments/packages
-> 后端按 email_hash 检查 offer_code='new_user_pack_099_60' 是否已占用
-> 返回 eligible=true/false
客户端创建订单 -> POST /payments/orders
-> 再次做资格校验(防并发)
-> 创建 payment_orders(status=created)
-> 写 payment_order_events(order_created)
5.2 iOS 验单与积分入账
客户端支付后提交 receipt -> POST /orders/{orderNo}/verify-ios-receipt
-> 后端调用 Apple 验单(可切 sandbox)
-> 验证 transaction_id 幂等
-> 状态 verified
-> 原子事务:
1) 占用权益 register_bonus_claims(offer_code,email_hash)
2) 写 points_ledger(grant)
3) 写 points_audit_ledger(direction=1,billed_to='user')
4) 订单置 credited
5) 写 payment_order_events(credited)
5.3 退款与冲正
Apple 回调退款 -> POST /payments/webhooks/apple
-> 定位 order(transaction_id / original_transaction_id)
-> 幂等处理通知
-> 状态 refunded/revoked
-> 原子事务:
1) 写 points_ledger(adjust/consume reverse)
2) 写 points_audit_ledger(direction=-1,billed_to='platform',metadata.reason='refund')
3) 写 payment_order_events(refunded/reversed)
6. 信任边界与风控
- 客户端价格、积分、product_code 全部不可信,按后端配置为准。
- 不信任客户端“支付成功”标记,必须后端验单通过才入账。
- Apple 回调需验签(JWS)并做
notificationUUID幂等。 - 订单与入账使用数据库事务,失败不允许半成功。
offer_code + email_hash唯一约束是最终防线。
7. 前端改造
当前 CoinCenterScreen 中套餐硬编码,需改为 API 驱动:
- 页面加载调用
GET /api/v1/payments/packages - 渲染返回的套餐列表
- 新人包
eligible=false时展示“已购买/不可购买”态 - 点击购买后走真实支付流(创建订单 -> 拉起 IAP -> 提交 receipt)
8. 无 Apple 账号阶段的交付策略
在无开发者账号前,先做可替换的验单适配层:
IOSReceiptVerifier接口(生产实现 + mock 实现)- 通过配置开关使用 mock 结果跑通后端链路与前端状态
- 后续只替换 verifier 实现,不改订单主流程
9. 测试计划
9.1 后端单元测试
- 新人包资格判定(首次可买、重复不可买、删号重注册不可买)
- 验单幂等(同 transaction_id 不重复入账)
- 退款冲正幂等(同通知不重复冲正)
9.2 后端集成测试
- 首次注册 -> 下单 -> 验单 -> 入账 60
- 删除账号 -> 同邮箱重注册 -> 新人包不可买
- 退款通知 -> 积分冲正 -> 订单状态更新
9.3 前端集成测试
- 套餐接口渲染(替代硬编码)
- 新人包可买/不可买状态切换
- 支付中/成功/失败/退款状态展示
10. 里程碑拆分
PR1(数据层)
- 迁移:新建
payment_orders、payment_order_events - 迁移:改造
register_bonus_claims - 模型与 repository
PR2(后端业务)
- 支付路由 + service
- iOS 验单适配层(先 mock)
- 订单与积分入账/冲正
PR3(前端)
- 套餐改 API 驱动
- 新人包购买态与禁用态
- 下单/验单交互链路
PR4(联调与验证)
- 使用集成测试回归全流程
- Apple 账号就绪后切换真实 verifier
11. 变更类型判定
这是 新 Feature,不是现有功能的小修补。
理由:
- 引入了新的支付域模型和事件审计。
- 引入了新的后端支付路由与验单流程。
- 前端从静态展示升级为可交易流程。
- 增加了退款冲正与 iOS 回调处理能力。