All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 6m37s
新增苹果IAP相关接口与逻辑,包括产品列表查询、交易绑定、状态查询和恢复购买功能。移除旧的IAP验证逻辑,重构订阅系统以支持苹果IAP交易记录存储和权益计算。 - 新增/pkg/iap/apple包处理JWS解析和产品映射 - 实现GET /products、POST /attach、POST /restore和GET /status接口 - 新增apple_iap_transactions表存储交易记录 - 更新文档说明配置方式和接口规范 - 移除旧的AppleIAP验证和通知处理逻辑
65 lines
4.1 KiB
Markdown
65 lines
4.1 KiB
Markdown
## 目标
|
||
- 不使用自动续期订阅;采用“非续期订阅”或“非消耗型”作为内购模式。
|
||
- 仅实现 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) 上线前验证:沙盒测试与文案、监控 |