## 目标 - 不使用自动续期订阅;采用“非续期订阅”或“非消耗型”作为内购模式。 - 仅实现 Go 后端 API;客户端(iOS/StoreKit 2)按说明调用。 ## 产品模型 - 非续期订阅:固定时长通行证(如 30/90/365 天),产品ID:`com.airport.vpn.pass.30d|90d|365d`。 - 非消耗型(可选):一次性解锁某附加功能,产品ID:`com.airport.vpn.addon.xyz`。 - 服务器以 `productId→权益/时长` 进行配置映射。 ## 后端API设计(Go/Gin) - 路由注册:`internal/handler/routes.go` - `GET /api/iap/apple/products`:返回前端展示的产品清单(含总价/描述/时长映射) - `POST /api/iap/apple/transactions/attach`:绑定一次购买到用户账户(需登录)。入参:`signedTransactionJWS` - `POST /api/iap/apple/restore`:恢复购买(批量接收 JWS 列表并绑定) - `GET /api/iap/apple/status`:返回用户当前权益与到期时间(统一来源聚合) - 逻辑目录:`internal/logic/iap/apple/*` - `AttachTransactionLogic`:解析 JWS→校验 `bundleId/productId/purchaseDate`→根据 `productId` 映射权益与时长→更新订阅统一表 - `RestoreLogic`:对所有已购记录执行绑定去重(基于 `original_transaction_id`) - `QueryStatusLogic`:聚合各来源订阅,返回有效权益(取最近到期/最高等级) - 工具包:`pkg/iap/apple` - `ParseTransactionJWS`:解析 JWS,提取 `transactionId/originalTransactionId/productId/purchaseDate/revocationDate` - `VerifyBasic`:基础校验(`bundleId`、签名头部与证书链存在性);如客户端已 `transaction.verify()`,可采用“信任+服务器最小校验”的模式快速落地 - 配置:`doc/config-zh.md` - `IAP_PRODUCT_MAP`:`productId → tier/duration`(例如:`30d→+30天`、`addon→解锁功能X`) - `APPLE_IAP_BUNDLE_ID`:用于 JWS 内部校验 ## 数据模型 - 新表:`apple_iap_transactions` - `id`、`user_id`、`original_transaction_id`(唯一)、`transaction_id`、`product_id`、`purchase_at`、`revocation_at`、`jws_hash` - 统一订阅表增强(现有 `SubscribeModel`) - 新增来源:`source=apple_iap`、`external_id=original_transaction_id`、`tier`、`expires_at` - 索引:`original_transaction_id` 唯一、`user_id+source`、`expires_at` ## 与现有系统融合 - `internal/svc/serviceContext.go`:初始化 IAP 模块与模型 - `QueryPurchaseOrderLogic/SubscribeModel`:聚合苹果IAP来源;冲突策略:按最高权益与最晚到期。 - 不产生命令行支付订单,仅记录订阅流水与审计(避免与 Stripe 等混淆)。 ## 安全与合规 - 仅显示商店在可支付时;价格、描述清晰;使用系统确认表单。 - 服务器进行最小校验:`bundleId`、`productId`白名单、`purchaseDate`有效性;保存 `jws_hash` 做去重。 - 退款:在 App 内提供“请求退款”的帮助页并使用系统接口触发;后端无需额外API。 ## 客户端使用说明(StoreKit 2) - 产品拉取与展示: - 通过已知 `productId` 列表调用 `Product.products(for:)`;展示总价与描述,检查 `canMakePayments` - 购买: - 调用 `purchase()`,系统确认表单弹出→返回 `Transaction`;执行 `await transaction.verify()` - 成功后将 `transaction.signedData` POST 到 `/api/iap/apple/transactions/attach` - 恢复: - 调用 `Transaction.currentEntitlements`,遍历并验证每条 `Transaction`,将其 `signedData` 批量 POST 到 `/api/iap/apple/restore` - 状态显示: - 访问 `GET /api/iap/apple/status` 获取到期时间与权益用于 UI 展示 - 退款入口: - 在购买帮助页直接使用 `beginRefundRequest(for:in:)`;文案简洁,按钮直达 ## 测试与验收 - 单元测试:JWS 解析、`productId→权益/时长` 映射、去重策略。 - 集成测试:绑定/恢复接口鉴权与幂等、统一订阅查询结果。 - 沙盒:使用 iOS 沙盒购买与恢复;记录审计与日志。 ## 里程碑 1) 基础能力:`products/status` 与 `transactions/attach` 落地 2) 恢复与融合:`restore` + 统一订阅聚合 3) 上线前验证:沙盒测试与文案、监控