revenuecat-purchase-flow:购买套餐与恢复购买
该技能用于实现 RevenueCat 的购买与恢复流程。当用户想要完成 RevenueCat 的购买侧逻辑时使用:获取 offerings、调用 purchase、处理取消与错误、暴露“恢复”操作。它不覆盖付费墙 UI 渲染(由 revenuecat-paywall 负责),也不覆盖功能门控(由 revenuecat-entitlements-gate 负责)。
如果应用还没有付费墙,且用户未要求自定义付费墙,需先确定付费墙的构建方式(integrate-revenuecat 第 5c 节)。默认使用 RevenueCat Paywall,它自身处理购买。
1. 检测平台
检查工作目录并从顶部到底部选择第一个匹配项:
- React Native:package.json 含 react-native-purchases 条目,或依赖 react-native → 读取 platforms/react-native.md。若同时依赖 expo,标记为 Expo 项目。
- Flutter:项目根目录存在 pubspec.yaml → 读取 platforms/flutter.md。
- Kotlin Multiplatform:build.gradle.kts 含 kotlin { … } 多平台 source sets 块,或依赖 com.revenuecat.purchases:purchases-kmp* → 读取 platforms/kmp.md。
- Android(原生):build.gradle(.kts) 应用 com.android.application(且非 KMP)→ 读取 platforms/android.md。
- iOS(原生):项目根目录存在 Package.swift、.xcodeproj、.xcworkspace 或 Podfile → 读取 platforms/ios.md。
若多项匹配(例如 Flutter 项目中的 ios/ 文件夹),选择最外层、拥有构建的项目。若仍不明确,询问用户想配置哪个平台。
2. 共享概念(所有平台)
流程:调用 getOfferings(),从当前 offering 中选取一个 Package,调用 purchase(package)。成功完成后,返回的 customerInfo 已反映该购买。读取 customerInfo.entitlements.active["<id>"] 确认访问权限。
用户取消不是应用错误。各 SDK 的呈现方式不同:iOS 抛出 purchaseCancelledError 代码,Android 抛出带 PurchasesErrorCode.PurchaseCancelledError 的 PurchasesException,Flutter 呈现带相同代码的 PlatformException,React Native 设置 e.userCancelled === true。此情况下静默返回,不要显示弹窗。
值得提示的错误:支付被拒、网络错误、商店不可用、收据已被使用。其余错误应记录日志并让用户重试。购买实际失败时绝不要静默视为成功。
不要在购买回调内解锁内容。刷新 customer info,让你的权益监听器(见 revenuecat-entitlements-gate)切换被门控的 UI。这样可保持访问权限的单一事实来源,避免购买路径与恢复路径之间产生偏差。
restorePurchases() 是用户操作,不是自动步骤。它向商店请求当前收据并同步到 RevenueCat。应在付费墙和/或设置界面上提供可见的“Restore purchases”按钮。iOS 的法律要求强制提供此类按钮。
同一时刻只处理一次购买。购买进行中时禁用付费墙购买按钮,防止重复扣费。
3. 实现
读取与检测结果匹配的平台文件:
- platforms/ios.md
- platforms/android.md
- platforms/kmp.md
- platforms/flutter.md
- platforms/react-native.md
每个平台文件包含完整的购买函数与恢复函数。
4. 验证
在以下条件满足前,不要声称流程可用:
- 对当前 offering 的 package 进行沙盒购买,端到端成功,且用户权益切换为 active。
- 取消商店弹窗不显示错误弹窗,也不让 UI 停留在加载状态。
- 对同一活跃订阅再次购买能被干净处理(StoreKit / Play Billing 会呈现 productAlreadyPurchased / receiptAlreadyInUse 路径;流程不应崩溃)。
- 在全新安装并登录同一商店账号时,恢复按钮可恢复权益并更新 UI。