关于 implement-in-app-purchases
用于使用 Unity Purchasing 包实现应用内购买的 Unity 技能,涵盖新项目和迁移。重点说明 v5 的事件驱动连接流程,以及从待处理到确认的购买步骤。
主要功能
- 01扫描项目现状并选择实现路径
- 02连接商店并获取商品与购买记录
- 03处理购买确认、恢复和权益检查
- 04涵盖收据验证及平台相关细节
- 05提供从其他购买系统迁移的路径
适用场景
- 01在没有现有购买功能的项目中添加 Unity IAP
- 02从 Unity IAP v4 或原生计费代码迁移
- 03实现商品目录、订阅或购买恢复
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ unity-technologies/ skills --skill implement-in-app-purchases安装后在 Claude Code 中输入/implement-in-app-purchases确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api-notes.mdcodeless-catalog.mdconvert-adapty.mdconvert-essentialkit.mdconvert-revenuecat.mdconvert-unipay.mdmigration-v4-to-v5.mdpath-add-iap-to-new-project.mdpath-convert-native-google-billing.mdpath-convert-native-storekit.mdpath-implement-iap-d2c.mdplatform-notes.mdpre-check.md详细介绍
Unity 应用内购买
命名空间:UnityEngine.Purchasing | 安全:UnityEngine.Purchasing.Security
包:com.unity.purchasing
Unity IAP 有独立的初始化流程,通过 UnityIAPServices.StoreController() → store.Connect() 完成。它不需要 UnityServices.InitializeAsync(),但如果项目使用了其他 UGS 服务,两者可以共存。如果项目中包含 Analytics 并调用了 InitializeAsync(),IAP 会自动发送交易事件。
开始之前
请务必先阅读 references/pre-check.md。 该文件会扫描项目中的第三方 IAP 包、原生 Google Billing 和现有 Unity IAP 版本,然后引导至正确的处理路径。在确定处理路径之前,请勿阅读任何其他参考文件或进行任何更改。
详细参考资料
- 项目扫描与路径选择(首先阅读): 请参阅 references/pre-check.md
- API 签名与代码示例: 请参阅 references/api-notes.md
- 平台扩展(Apple、Google): 请参阅 references/platform-notes.md
- 编辑
IAPProductCatalog.json(架构、十进制序列化、刷新): 请参阅 references/codeless-catalog.md - v4 → v5 迁移: 请参阅 references/migration-v4-to-v5.md
- 将原生 Google BillingClient 转换为 Unity IAP 5: 请参阅 references/path-convert-native-google-billing.md
- 将原生 iOS StoreKit 插件转换为 Unity IAP 5: 请参阅 references/path-convert-native-storekit.md
- 将 Essential Kit 计费转换为 Unity IAP 5: 请参阅 references/convert-essentialkit.md
- UniPay(FLOBUK)——评估与指导: 请参阅 references/convert-unipay.md
- RevenueCat——转换评估与指导: 请参阅 references/convert-revenuecat.md
- Adapty——转换评估与指导: 请参阅 references/convert-adapty.md
- 为没有现有 IAP 的项目添加 Unity IAP 5: 请参阅 references/path-add-iap-to-new-project.md
- 实现 IAP D2C 功能(第三方支付提供商——Stripe/Coda,要求 v5.4+): 请参阅 references/path-implement-iap-d2c.md
按需阅读参考文件——仅在需要特定 API 签名、平台扩展详情或迁移映射时阅读。
初始化流程
- 通过
UnityIAPServices.StoreController()获取StoreController(或通过DefaultStore()、DefaultProduct()、DefaultPurchase()获取单项服务) - 调用
Connect()之前订阅所有必需事件(见下文“必需的事件订阅”) - 使用
await store.Connect()连接到平台商店 - 在
OnStoreConnected中调用store.FetchProducts(List<ProductDefinition>)加载目录 - 在
OnProductsFetched后,商品即可用于展示和购买
使用 Awake() 进行初始化,确保在其他 Start() 方法运行前 IAP 已就绪。
商品类型:ProductType.Consumable、ProductType.NonConsumable、ProductType.Subscription。
获取商品
将商品定义为 List<ProductDefinition> 并传入 store.FetchProducts()。如果 Apple Store 和 Google Store 中的商品 ID 不同,请使用 StoreSpecificIds。对于复杂的商品目录,请使用 CatalogProvider 管理商品集和商店特定 ID。
| 方法 | 行为 |
|---|---|
GetProducts() | 返回缓存的商品列表(同步;如果未调用 FetchProducts,则数据可能过时) |
FetchProducts() | 向商店查询最新价格和可用情况,并更新缓存 |
GetProductById(id) | 按 ID 返回单个缓存商品 |
这些方法不可互换。依赖 GetProducts() 之前,请务必先调用 FetchProducts()。
两步购买流程
IAP v5 使用强制性的两步流程:待处理 → 确认。
store.PurchaseProduct(product)— 启动平台购买对话框- 触发
OnPurchasePending— 收到一个PendingOrder - 验证收据,并向玩家发放内容
store.ConfirmPurchase(pendingOrder)— 完成交易- 触发
OnPurchaseConfirmed— 收到Order基类;通过模式匹配区分ConfirmedOrder(成功)和FailedOrder(确认失败)
发放内容后,必须调用 ConfirmPurchase(pendingOrder)。 为防止购买丢失,未确认的购买会在下次启动应用时重新传递。
去重: 同一笔购买的 OnPurchasePending 可能触发多次(例如,应用在确认前重启)。请务必检查内容是否已发放。
消耗品: FetchPurchases 不会返回已确认的消耗品购买。请自行跟踪消耗品的发放情况(例如,使用 Cloud Save 或 Economy)。
延期购买: 对于“询问购买”(iOS)和 Google Play 延期购买,会触发 OnPurchaseDeferred。请勿发放内容——等待购买获批后触发 OnPurchasePending。
恢复交易
store.RestoreTransactions(callback) 会重新传递非消耗品和订阅购买。每笔恢复的购买都会触发 OnPurchasePending。
为符合 Apple App Store 的要求,iOS 上必须添加“恢复购买”按钮。
无法通过 RestoreTransactions 恢复 Apple 非自动续订订阅。请在服务器端跟踪这些订阅。
收据验证
| 平台 | 方式 |
|---|---|
| Google Play | 使用 GooglePlayTangle.Data() 的 CrossPlatformValidator — 支持本地验证 |
| Apple(StoreKit 2) | 本地验证为空操作。请使用 order.Info.Apple?.jwsRepresentation 进行服务器端验证 |
在 Unity Editor 中,通过 Services > In-App Purchasing > Receipt Validation Obfuscator 生成混淆数据。
权益检查
当你没有 Order,但想了解特定商品的状态时使用(替代 v4 的 product.hasReceipt)。如果已有 Order,请改为检查其类型:PendingOrder 对应 EntitledUntilConsumed(消耗品)或 EntitledButNotFinished(非消耗品/订阅),ConfirmedOrder 对应 FullyEntitled。
调用 store.CheckEntitlement(product) 并处理 store.OnCheckEntitlement。检查 entitlement.Status == EntitlementStatus.FullyEntitled。
EntitlementStatus 的值:FullyEntitled、EntitledUntilConsumed、EntitledButNotFinished、NotEntitled、Unknown。
获取现有购买
store.FetchPurchases() 会从商店获取所有当前购买记录,适用于应用启动时调用。
| 方法 | 行为 |
|---|---|
GetPurchases() | 返回缓存的购买列表 |
FetchPurchases() | 向商店查询当前购买记录,并覆盖缓存列表 |
每次调用 FetchPurchases() 都会替换整个缓存列表。只会重新获取非消耗品和订阅——已确认的消耗品不会返回(见上文“两步购买流程”)。
订阅信息
订阅信息位于 IPurchasedProductInfo 上,通过 order.Info.PurchasedProductInfo 访问——不在 CartItem 上(CartItem 只有 Product 和 Quantity)。
IsSubscribed() 返回的是 Result 枚举(True/False/Unsupported),不是 bool。使用 == Result.True 进行空值安全比较。
必需的事件订阅
始终同时订阅成功和失败事件。 未订阅失败事件会产生运行时警告。
| 调用 | 成功事件 | 失败事件(必需) |
|---|---|---|
FetchProducts() | OnProductsFetched | OnProductsFetchFailed |
FetchPurchases() | OnPurchasesFetched | OnPurchasesFetchFailed |
Connect() | OnStoreConnected | OnStoreDisconnected |
PurchaseProduct() | OnPurchasePending | OnPurchaseFailed |
CheckEntitlement() | OnCheckEntitlement | — |
始终订阅 OnPurchaseDeferred——Ask-to-Buy(iOS)和 Google Play 延迟购买都会触发此事件。未订阅会导致延迟购买被静默丢弃。
在调用 Connect() 之前订阅事件——上一会话中的待处理购买可能会立即触发事件。
失败描述属性名称
这些属性名称不可互换——使用错误的名称会导致 CS1061:
| 类型 | 字段 | 属性 | 错误写法 |
|---|---|---|---|
StoreConnectionFailureDescription | .message | .Message | .reason |
ProductFetchFailed | — | .FailureReason、.FailedFetchProducts | .Message |
FailedOrder | — | .FailureReason、.Details | — |
PurchasesFetchFailureDescription | .message、.failureReason | .Message、.FailureReason | — |
验证
使用此包编写代码后:
- 验证项目能否无错误地编译。
- 确认所有 API 调用都符合 api-notes.md 中的 v5 签名——不要使用 v4 旧版模式(
IStoreListener、UnityPurchasing.Initialize、ConfigurationBuilder)。 - 检查 api-notes.md 中的“防止幻觉:常见 v5 错误”表格——不要使用
OnStoreConnectionFailed(应使用OnStoreDisconnected),不要向FetchProducts/FetchPurchases传递回调(应使用事件),不要使用product.receipt(应使用order.Info.Receipt)。 - 检查是否在调用
Connect()之前订阅了所有必需事件(参见上方的“必需的事件订阅”表格)。 - 验证两步购买流程:
OnPurchasePending→ 授予内容 →ConfirmPurchase(pendingOrder)。 - 确认每个异步操作都订阅了成功和失败事件(
OnProductsFetched/OnProductsFetchFailed、OnStoreConnected/OnStoreDisconnected等)。 - 如果处理订阅,验证
IsSubscribed()是通过== Result.True比较,而不是转换为bool。 - 如果同一项目中同时存在更新后的文件和旧版文件,请使用唯一命名空间(例如添加
.Updated后缀),以避免 CS0101/CS0111 编译错误。 - 如果项目订阅了
OnAuthAccountChanged(v5.4+):验证处理程序会从头重新获取产品和购买记录——Unity IAP 会在触发此事件前清除这两项缓存。不要在处理程序中读取GetProducts()或GetPurchases()。