nccgs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/.claude/agents/nccgs-accessibility-specialist.md +26 -0
  2. package/.claude/agents/nccgs-adversarial-reviewer.md +26 -0
  3. package/.claude/agents/nccgs-ai-programmer.md +26 -0
  4. package/.claude/agents/nccgs-analytics-engineer.md +26 -0
  5. package/.claude/agents/nccgs-art-direction-lead.md +26 -0
  6. package/.claude/agents/nccgs-audio-direction-lead.md +26 -0
  7. package/.claude/agents/nccgs-creative-director.md +26 -0
  8. package/.claude/agents/nccgs-documentation-manager.md +26 -0
  9. package/.claude/agents/nccgs-economy-designer.md +26 -0
  10. package/.claude/agents/nccgs-engine-programmer.md +26 -0
  11. package/.claude/agents/nccgs-game-design-lead.md +26 -0
  12. package/.claude/agents/nccgs-game-designer.md +26 -0
  13. package/.claude/agents/nccgs-gameplay-programmer.md +26 -0
  14. package/.claude/agents/nccgs-level-designer.md +26 -0
  15. package/.claude/agents/nccgs-live-ops-designer.md +26 -0
  16. package/.claude/agents/nccgs-localization-lead.md +26 -0
  17. package/.claude/agents/nccgs-narrative-lead.md +26 -0
  18. package/.claude/agents/nccgs-network-programmer.md +26 -0
  19. package/.claude/agents/nccgs-performance-analyst.md +26 -0
  20. package/.claude/agents/nccgs-producer.md +26 -0
  21. package/.claude/agents/nccgs-production-coordinator.md +23 -0
  22. package/.claude/agents/nccgs-programming-lead.md +26 -0
  23. package/.claude/agents/nccgs-prototyper.md +26 -0
  24. package/.claude/agents/nccgs-qa-engineer.md +26 -0
  25. package/.claude/agents/nccgs-qa-lead.md +26 -0
  26. package/.claude/agents/nccgs-release-engineer.md +26 -0
  27. package/.claude/agents/nccgs-release-lead.md +26 -0
  28. package/.claude/agents/nccgs-security-engineer.md +26 -0
  29. package/.claude/agents/nccgs-sound-designer.md +26 -0
  30. package/.claude/agents/nccgs-systems-designer.md +26 -0
  31. package/.claude/agents/nccgs-technical-architect.md +26 -0
  32. package/.claude/agents/nccgs-technical-artist.md +26 -0
  33. package/.claude/agents/nccgs-technical-director.md +26 -0
  34. package/.claude/agents/nccgs-tools-programmer.md +26 -0
  35. package/.claude/agents/nccgs-ui-programmer.md +26 -0
  36. package/.claude/agents/nccgs-unity-build-specialist.md +26 -0
  37. package/.claude/agents/nccgs-unity-content-specialist.md +26 -0
  38. package/.claude/agents/nccgs-unity-implementer.md +26 -0
  39. package/.claude/agents/nccgs-unity-rendering-specialist.md +26 -0
  40. package/.claude/agents/nccgs-unity-systems-specialist.md +26 -0
  41. package/.claude/agents/nccgs-unity-ui-specialist.md +26 -0
  42. package/.claude/agents/nccgs-ux-designer.md +26 -0
  43. package/.claude/agents/nccgs-verification-engineer.md +26 -0
  44. package/.claude/agents/nccgs-world-builder.md +26 -0
  45. package/.claude/agents/nccgs-writer.md +26 -0
  46. package/.claude/nccgs/THIRD_PARTY_NOTICES.md +13 -0
  47. package/.claude/nccgs/VERSION +1 -0
  48. package/.claude/nccgs/constitution.md +131 -0
  49. package/.claude/nccgs/hooks/agent-audit.mjs +10 -0
  50. package/.claude/nccgs/hooks/common.mjs +41 -0
  51. package/.claude/nccgs/hooks/post-compact.mjs +1 -0
  52. package/.claude/nccgs/hooks/pre-compact.mjs +8 -0
  53. package/.claude/nccgs/hooks/protect-git.mjs +22 -0
  54. package/.claude/nccgs/hooks/protect-write.mjs +17 -0
  55. package/.claude/nccgs/hooks/session-start.mjs +15 -0
  56. package/.claude/nccgs/hooks/session-stop.mjs +7 -0
  57. package/.claude/nccgs/protocols/agent-contract.md +34 -0
  58. package/.claude/nccgs/protocols/context-packets.md +13 -0
  59. package/.claude/nccgs/protocols/evidence.md +15 -0
  60. package/.claude/nccgs/protocols/model-routing.md +22 -0
  61. package/.claude/nccgs/protocols/orchestration.md +26 -0
  62. package/.claude/nccgs/protocols/unity-boundary.md +11 -0
  63. package/.claude/nccgs/settings.fragment.json +77 -0
  64. package/.claude/nccgs/studio.json +347 -0
  65. package/.claude/nccgs/tools/configure-models.mjs +39 -0
  66. package/.claude/nccgs/unity-skills-manifest.json +727 -0
  67. package/.claude/nccgs/workflow-catalog.json +317 -0
  68. package/.claude/rules/nccgs-canonical-docs.md +19 -0
  69. package/.claude/rules/nccgs-editor-tools.md +12 -0
  70. package/.claude/rules/nccgs-localization.md +12 -0
  71. package/.claude/rules/nccgs-networking.md +13 -0
  72. package/.claude/rules/nccgs-performance.md +14 -0
  73. package/.claude/rules/nccgs-rendering.md +16 -0
  74. package/.claude/rules/nccgs-security.md +13 -0
  75. package/.claude/rules/nccgs-tests.md +18 -0
  76. package/.claude/rules/nccgs-ui.md +14 -0
  77. package/.claude/rules/nccgs-unity-assets.md +21 -0
  78. package/.claude/rules/nccgs-unity-code.md +22 -0
  79. package/.claude/skills/accessibility-review/SKILL.md +14 -0
  80. package/.claude/skills/architecture-decision/SKILL.md +14 -0
  81. package/.claude/skills/asset-audit/SKILL.md +14 -0
  82. package/.claude/skills/audit/SKILL.md +16 -0
  83. package/.claude/skills/audit/references/dimensions.md +46 -0
  84. package/.claude/skills/balance-review/SKILL.md +14 -0
  85. package/.claude/skills/bug-triage/SKILL.md +14 -0
  86. package/.claude/skills/build-live-game/SKILL.md +317 -0
  87. package/.claude/skills/build-live-game/references/achievements.md +779 -0
  88. package/.claude/skills/build-live-game/references/apis.md +280 -0
  89. package/.claude/skills/build-live-game/references/authentication.md +437 -0
  90. package/.claude/skills/build-live-game/references/battlepass.md +860 -0
  91. package/.claude/skills/build-live-game/references/cloud-code.md +563 -0
  92. package/.claude/skills/build-live-game/references/cloud-save.md +474 -0
  93. package/.claude/skills/build-live-game/references/deployment.md +216 -0
  94. package/.claude/skills/build-live-game/references/player-account.md +813 -0
  95. package/.claude/skills/build-live-game/references/remote-config.md +96 -0
  96. package/.claude/skills/build-live-game/references/tooling.md +431 -0
  97. package/.claude/skills/closure/SKILL.md +14 -0
  98. package/.claude/skills/code-review/SKILL.md +14 -0
  99. package/.claude/skills/compatibility-review/SKILL.md +14 -0
  100. package/.claude/skills/context-pack/SKILL.md +14 -0
  101. package/.claude/skills/dependency-review/SKILL.md +14 -0
  102. package/.claude/skills/design/SKILL.md +20 -0
  103. package/.claude/skills/design-review/SKILL.md +14 -0
  104. package/.claude/skills/evidence-review/SKILL.md +14 -0
  105. package/.claude/skills/hotfix/SKILL.md +14 -0
  106. package/.claude/skills/implement-in-app-purchases/README.md +233 -0
  107. package/.claude/skills/implement-in-app-purchases/SKILL.md +158 -0
  108. package/.claude/skills/implement-in-app-purchases/references/api-notes.md +562 -0
  109. package/.claude/skills/implement-in-app-purchases/references/codeless-catalog.md +331 -0
  110. package/.claude/skills/implement-in-app-purchases/references/convert-adapty.md +268 -0
  111. package/.claude/skills/implement-in-app-purchases/references/convert-essentialkit.md +239 -0
  112. package/.claude/skills/implement-in-app-purchases/references/convert-revenuecat.md +275 -0
  113. package/.claude/skills/implement-in-app-purchases/references/convert-unipay.md +145 -0
  114. package/.claude/skills/implement-in-app-purchases/references/migration-v4-to-v5.md +204 -0
  115. package/.claude/skills/implement-in-app-purchases/references/path-add-iap-to-new-project.md +198 -0
  116. package/.claude/skills/implement-in-app-purchases/references/path-convert-native-google-billing.md +356 -0
  117. package/.claude/skills/implement-in-app-purchases/references/path-convert-native-storekit.md +446 -0
  118. package/.claude/skills/implement-in-app-purchases/references/path-implement-iap-d2c.md +680 -0
  119. package/.claude/skills/implement-in-app-purchases/references/platform-notes.md +204 -0
  120. package/.claude/skills/implement-in-app-purchases/references/pre-check.md +205 -0
  121. package/.claude/skills/incident-recovery/SKILL.md +14 -0
  122. package/.claude/skills/initialize-ai-navigation/SKILL.md +95 -0
  123. package/.claude/skills/initialize-ai-navigation/references/navigation-system.md +794 -0
  124. package/.claude/skills/levelplay-unity-integration/CHANGELOG.md +57 -0
  125. package/.claude/skills/levelplay-unity-integration/README.md +74 -0
  126. package/.claude/skills/levelplay-unity-integration/SKILL.md +1126 -0
  127. package/.claude/skills/levelplay-unity-integration/references/banner-api.md +920 -0
  128. package/.claude/skills/levelplay-unity-integration/references/best-practices.md +536 -0
  129. package/.claude/skills/levelplay-unity-integration/references/ilrd-api.md +337 -0
  130. package/.claude/skills/levelplay-unity-integration/references/initialization-api.md +630 -0
  131. package/.claude/skills/levelplay-unity-integration/references/interstitial-api.md +899 -0
  132. package/.claude/skills/levelplay-unity-integration/references/ios-setup.md +491 -0
  133. package/.claude/skills/levelplay-unity-integration/references/migration-sdk-9.md +666 -0
  134. package/.claude/skills/levelplay-unity-integration/references/privacy-settings.md +608 -0
  135. package/.claude/skills/levelplay-unity-integration/references/rewarded-api.md +902 -0
  136. package/.claude/skills/localization/SKILL.md +135 -0
  137. package/.claude/skills/localization/references/api-notes.md +76 -0
  138. package/.claude/skills/localization/resources/L10nBatchProcessor.cs +69 -0
  139. package/.claude/skills/localization/resources/LocalizedFontAsset.cs +18 -0
  140. package/.claude/skills/localize-game/SKILL.md +14 -0
  141. package/.claude/skills/migrate-project/SKILL.md +21 -0
  142. package/.claude/skills/migrate-project/references/procedure.md +63 -0
  143. package/.claude/skills/milestone-review/SKILL.md +14 -0
  144. package/.claude/skills/new-unity-project/SKILL.md +179 -0
  145. package/.claude/skills/optimize-audio/SKILL.md +199 -0
  146. package/.claude/skills/optimize-audio/resources/audio-import-api.md +146 -0
  147. package/.claude/skills/optimize-audio/resources/platform-settings.md +48 -0
  148. package/.claude/skills/optimize-text-mesh-pro/SKILL.md +182 -0
  149. package/.claude/skills/optimize-web/SKILL.md +393 -0
  150. package/.claude/skills/optimize-web/resources/WebOptimizer.cs +21 -0
  151. package/.claude/skills/optimize-web/resources/toktx-examples.sh +11 -0
  152. package/.claude/skills/performance-audit/SKILL.md +14 -0
  153. package/.claude/skills/physics-3d-collision/SKILL.md +442 -0
  154. package/.claude/skills/physics-3d-collision/references/troubleshooting.md +41 -0
  155. package/.claude/skills/physics-3d-collision/resources/CollisionDebugger.cs +33 -0
  156. package/.claude/skills/plan-feature/SKILL.md +14 -0
  157. package/.claude/skills/playtest/SKILL.md +14 -0
  158. package/.claude/skills/project-stage/SKILL.md +14 -0
  159. package/.claude/skills/prototype-feature/SKILL.md +14 -0
  160. package/.claude/skills/qa-plan/SKILL.md +14 -0
  161. package/.claude/skills/release/SKILL.md +18 -0
  162. package/.claude/skills/release-readiness/SKILL.md +14 -0
  163. package/.claude/skills/retrospective/SKILL.md +14 -0
  164. package/.claude/skills/review/SKILL.md +16 -0
  165. package/.claude/skills/security-audit/SKILL.md +14 -0
  166. package/.claude/skills/setup-multiplayer-services/SKILL.md +39 -0
  167. package/.claude/skills/setup-multiplayer-services/references/dgs-entrypoint.md +79 -0
  168. package/.claude/skills/setup-multiplayer-services/references/entrypoints.md +213 -0
  169. package/.claude/skills/setup-multiplayer-services/references/examples.md +33 -0
  170. package/.claude/skills/setup-multiplayer-services/references/implementation-fit.md +30 -0
  171. package/.claude/skills/setup-multiplayer-services/references/underlying-services.md +11 -0
  172. package/.claude/skills/setup-multiplayer-services/references/workflows-prerequisites.md +17 -0
  173. package/.claude/skills/setup-vivox-voice-chat/SKILL.md +118 -0
  174. package/.claude/skills/setup-vivox-voice-chat/evals/.env.example +6 -0
  175. package/.claude/skills/setup-vivox-voice-chat/evals/README.md +101 -0
  176. package/.claude/skills/setup-vivox-voice-chat/evals/promptfooconfig.yaml +32 -0
  177. package/.claude/skills/setup-vivox-voice-chat/evals/tests/init-and-login.yaml +76 -0
  178. package/.claude/skills/setup-vivox-voice-chat/evals/tests/text-chat.yaml +65 -0
  179. package/.claude/skills/setup-vivox-voice-chat/evals/tests/voice-channels.yaml +74 -0
  180. package/.claude/skills/setup-vivox-voice-chat/references/events-and-participants.md +79 -0
  181. package/.claude/skills/setup-vivox-voice-chat/references/init-and-login.md +92 -0
  182. package/.claude/skills/setup-vivox-voice-chat/references/text-chat.md +93 -0
  183. package/.claude/skills/setup-vivox-voice-chat/references/troubleshooting.md +47 -0
  184. package/.claude/skills/setup-vivox-voice-chat/references/voice-channels.md +90 -0
  185. package/.claude/skills/shader-graph-create-custom-node/SKILL.md +25 -0
  186. package/.claude/skills/shader-graph-create-custom-node/resources/all_hints.hlsl +182 -0
  187. package/.claude/skills/sprint-plan/SKILL.md +14 -0
  188. package/.claude/skills/sprite-editor/SKILL.md +66 -0
  189. package/.claude/skills/sprite-editor/references/api_reference.md +151 -0
  190. package/.claude/skills/sprite-editor/references/background.md +112 -0
  191. package/.claude/skills/sprite-editor/references/templates.md +72 -0
  192. package/.claude/skills/sprite-editor/scripts/AutomaticSliceTexture.cs +40 -0
  193. package/.claude/skills/sprite-editor/scripts/GenerateNewSpriteRects.cs +200 -0
  194. package/.claude/skills/sprite-editor/scripts/GetTextureSourceImageSize.cs +32 -0
  195. package/.claude/skills/sprite-editor/scripts/GetTextureToSlice.cs +55 -0
  196. package/.claude/skills/sprite-editor/scripts/GridSliceTexture.cs +40 -0
  197. package/.claude/skills/sprite-editor/scripts/IsometricSliceTexture.cs +141 -0
  198. package/.claude/skills/sprite-editor/scripts/README.md +134 -0
  199. package/.claude/skills/sprite-editor/scripts/SetPivotExample.cs +58 -0
  200. package/.claude/skills/sprite-editor/scripts/SpriteToPng.cs +88 -0
  201. package/.claude/skills/status/SKILL.md +16 -0
  202. package/.claude/skills/story-readiness/SKILL.md +14 -0
  203. package/.claude/skills/test/SKILL.md +16 -0
  204. package/.claude/skills/ui/SKILL.md +142 -0
  205. package/.claude/skills/ui-imgui/SKILL.md +186 -0
  206. package/.claude/skills/ui-imgui/references/gui-elements.md +156 -0
  207. package/.claude/skills/ui-imgui/references/templates.md +141 -0
  208. package/.claude/skills/ui-review/SKILL.md +14 -0
  209. package/.claude/skills/ui-ugui/SKILL.md +282 -0
  210. package/.claude/skills/ui-ugui/references/scrollview-setup.md +29 -0
  211. package/.claude/skills/ui-uitk/SKILL.md +235 -0
  212. package/.claude/skills/ui-uitk/references/common-issues.md +74 -0
  213. package/.claude/skills/ui-uitk/references/custom-elements.md +241 -0
  214. package/.claude/skills/ui-uitk/references/painter2d.md +282 -0
  215. package/.claude/skills/ui-uitk/references/pointermanipulator-guide.md +94 -0
  216. package/.claude/skills/ui-uitk/references/svg-icons.md +136 -0
  217. package/.claude/skills/ui-uitk/references/ui-runtime-binding.md +234 -0
  218. package/.claude/skills/ui-uitk/references/uss-guide.md +138 -0
  219. package/.claude/skills/unity-cli/CHANGELOG.md +233 -0
  220. package/.claude/skills/unity-cli/SECURITY.md +22 -0
  221. package/.claude/skills/unity-cli/SKILL.md +414 -0
  222. package/.claude/skills/unity-cli/references/auth-license-cloud.md +146 -0
  223. package/.claude/skills/unity-cli/references/build-run-test.md +349 -0
  224. package/.claude/skills/unity-cli/references/collaboration.md +472 -0
  225. package/.claude/skills/unity-cli/references/config-hub.md +103 -0
  226. package/.claude/skills/unity-cli/references/diagnostics-maintenance.md +326 -0
  227. package/.claude/skills/unity-cli/references/editors-install.md +327 -0
  228. package/.claude/skills/unity-cli/references/integration-advanced.md +472 -0
  229. package/.claude/skills/unity-cli/references/projects-templates.md +574 -0
  230. package/.claude/skills/unity-package-management/SKILL.md +304 -0
  231. package/.claude/skills/unity-package-management/references/select-packages.md +108 -0
  232. package/.claude/skills/urp-postprocessing/SKILL.md +188 -0
  233. package/.claude/skills/urp-postprocessing/references/code-templates.md +119 -0
  234. package/.claude/skills/urp-postprocessing/references/effect-reference.md +86 -0
  235. package/.claude/skills/validate-urp-render-graph-renderer-feature/SKILL.md +269 -0
  236. package/.claude/skills/work/SKILL.md +36 -0
  237. package/.claude/skills/work/references/classification.md +41 -0
  238. package/.claude/skills/work/references/closure.md +50 -0
  239. package/.claude/skills/work/references/feature-contracts.md +32 -0
  240. package/.claude/skills/work/references/verification.md +26 -0
  241. package/CLAUDE.md +6 -0
  242. package/LICENSE +21 -0
  243. package/README.md +162 -0
  244. package/THIRD_PARTY_NOTICES.md +23 -0
  245. package/UPGRADING.md +32 -0
  246. package/VERSION +1 -0
  247. package/docs/ARCHITECTURE.md +73 -0
  248. package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +324 -0
  249. package/docs/MIGRATION-MATRIX.md +23 -0
  250. package/docs/PROJECT-POLICY.md +67 -0
  251. package/docs/WORKFLOWS.md +61 -0
  252. package/package-assets/setup-vivox-voice-chat-evals.gitignore +7 -0
  253. package/package.json +41 -0
  254. package/scaffold/.nccgs/bugs/.gitkeep +1 -0
  255. package/scaffold/.nccgs/closures/.gitkeep +1 -0
  256. package/scaffold/.nccgs/context/.gitkeep +1 -0
  257. package/scaffold/.nccgs/decisions/.gitkeep +1 -0
  258. package/scaffold/.nccgs/evidence/.gitkeep +1 -0
  259. package/scaffold/.nccgs/features/.gitkeep +1 -0
  260. package/scaffold/.nccgs/migrations/.gitkeep +1 -0
  261. package/scaffold/.nccgs/playtests/.gitkeep +1 -0
  262. package/scaffold/.nccgs/project.yaml +103 -0
  263. package/scaffold/.nccgs/requirements.yaml +10 -0
  264. package/scaffold/.nccgs/reviews/.gitkeep +1 -0
  265. package/scaffold/.nccgs/state.md +40 -0
  266. package/scaffold/.nccgs/templates/agent-handoff.md +25 -0
  267. package/scaffold/.nccgs/templates/architecture-decision.md +27 -0
  268. package/scaffold/.nccgs/templates/closure-record.md +51 -0
  269. package/scaffold/.nccgs/templates/context-packet.yaml +17 -0
  270. package/scaffold/.nccgs/templates/evidence-record.md +23 -0
  271. package/scaffold/.nccgs/templates/feature-contract.md +35 -0
  272. package/scaffold/.nccgs/templates/migration-plan.md +40 -0
  273. package/scaffold/.nccgs/templates/waiver.md +11 -0
  274. package/scripts/cli.mjs +56 -0
  275. package/scripts/install.mjs +267 -0
  276. package/scripts/sync-unity-skills.mjs +126 -0
  277. package/scripts/validate.mjs +205 -0
  278. package/tests/framework.test.mjs +121 -0
@@ -0,0 +1,562 @@
1
+ # Unity IAP API Notes
2
+
3
+ ## Table of Contents
4
+
5
+ - [Anti-Hallucination: v5 vs Legacy API](#anti-hallucination-v5-vs-legacy-api)
6
+ - [StoreController (v5) Key Members](#storecontroller-v5-key-members)
7
+ - [Alternative Service Access (DefaultStore / DefaultProduct / DefaultPurchase)](#alternative-service-access-defaultstore--defaultproduct--defaultpurchase)
8
+ - [CatalogProvider](#catalogprovider)
9
+ - [Initialization Example](#initialization-example)
10
+ - [Product Definitions](#product-definitions)
11
+ - [CatalogListings and uSku (v5.4+)](#cataloglistings-and-usku-v54)
12
+ - [Two-Step Purchase Flow](#two-step-purchase-flow)
13
+ - [Restore Transactions](#restore-transactions)
14
+ - [Entitlement Checking](#entitlement-checking)
15
+ - [Fetch Existing Purchases](#fetch-existing-purchases)
16
+ - [Receipt Validation](#receipt-validation)
17
+ - [IAppleOrderInfo Complete Member List](#iappleorderinfo-complete-member-list)
18
+ - [Extended Service Events (NOT on StoreController)](#extended-service-events-not-on-storecontroller)
19
+ - [AppleStoreExtendedProductService Key Members](#applestoreextendedproductservice-key-members)
20
+ - [Subscription Info Access Path](#subscription-info-access-path)
21
+ - [Event Subscription Rules](#event-subscription-rules)
22
+ - [Failure Description Property Names](#failure-description-property-names)
23
+ - [CrossPlatformValidator](#crossplatformvalidator)
24
+ - [Interface Hierarchy](#interface-hierarchy)
25
+ - [IRunCommand Template: Fetch Products](#iruncommand-template-fetch-products)
26
+
27
+ ## Anti-Hallucination: v5 vs Legacy API
28
+
29
+ Unity IAP v5 is the current API. The legacy API (v4) is `[Obsolete]`. Do NOT use legacy patterns.
30
+
31
+ | WRONG (Legacy v4) | CORRECT (v5) |
32
+ |---|---|
33
+ | `UnityPurchasing.Initialize(listener, builder)` | `UnityIAPServices.StoreController()` then `store.Connect()` |
34
+ | `IStoreListener.OnInitialized(controller, extensions)` | `store.OnStoreConnected` event |
35
+ | `IStoreListener.ProcessPurchase(args)` | `store.OnPurchasePending` event |
36
+ | `controller.InitiatePurchase(product)` | `store.PurchaseProduct(product)` |
37
+ | `controller.ConfirmPendingPurchase(product)` | `store.ConfirmPurchase(pendingOrder)` |
38
+ | `extensions.GetExtension<IAppleExtensions>()` | `store.AppleStoreExtendedService` / `store.AppleStoreExtendedPurchaseService` |
39
+ | `extensions.GetExtension<IGooglePlayStoreExtensions>()` | `store.GooglePlayStoreExtendedService` / `store.GooglePlayStoreExtendedPurchaseService` |
40
+ | `ConfigurationBuilder.Instance(...)` | Not needed; use `ProductDefinition` list with `FetchProducts` |
41
+
42
+ ## Anti-Hallucination: Common v5 Mistakes
43
+
44
+ These are NOT real APIs — do not use them:
45
+
46
+ | WRONG (does not exist) | CORRECT |
47
+ |---|---|
48
+ | `store.OnStoreConnectionFailed` | `store.OnStoreDisconnected` — receives `StoreConnectionFailureDescription` |
49
+ | `store.FetchProducts(defs, successCb, failCb)` | `store.FetchProducts(List<ProductDefinition>)` — subscribe to `OnProductsFetched`/`OnProductsFetchFailed` events separately |
50
+ | `store.FetchPurchases(successCb, failCb)` | `store.FetchPurchases()` — subscribe to `OnPurchasesFetched`/`OnPurchasesFetchFailed` events separately |
51
+ | `product.receipt` | `order.Info.Receipt` — receipt is on the order, not the product |
52
+ | `product.hasReceipt` | `store.CheckEntitlement(product)` + `OnCheckEntitlement` |
53
+ | `new SubscriptionManager(product, introJson)` | `order.Info.PurchasedProductInfo` — see Subscription Info Access Path |
54
+ | `pendingOrder.OrderInfo.Apple.jwsRepresentation` | `pendingOrder.Info.Apple?.jwsRepresentation` — note `Info` not `OrderInfo`, and null-check `?.` |
55
+
56
+ ## StoreController (v5) Key Members
57
+
58
+ ```csharp
59
+ // Store lifecycle
60
+ Task Connect()
61
+ void SetStoreReconnectionRetryPolicyOnDisconnection(IRetryPolicy? retryPolicy)
62
+ event Action? OnStoreConnected
63
+ event Action<StoreConnectionFailureDescription>? OnStoreDisconnected
64
+
65
+ // Products
66
+ void FetchProducts(List<ProductDefinition> defs, IRetryPolicy? retryPolicy = null)
67
+ void FetchProductsWithNoRetries(List<ProductDefinition> defs)
68
+ ReadOnlyObservableCollection<Product> GetProducts()
69
+ Product? GetProductById(string productId)
70
+ event Action<List<Product>>? OnProductsFetched
71
+ event Action<ProductFetchFailed>? OnProductsFetchFailed
72
+
73
+ // Purchasing
74
+ void PurchaseProduct(Product product)
75
+ void PurchaseProduct(string? productId)
76
+ void Purchase(ICart cart)
77
+ void ConfirmPurchase(PendingOrder order)
78
+ void FetchPurchases()
79
+ void CheckEntitlement(Product product)
80
+ void RestoreTransactions(Action<bool, string?>? callback)
81
+ ReadOnlyObservableCollection<Order> GetPurchases()
82
+ void ProcessPendingOrdersOnPurchasesFetched(bool shouldProcess)
83
+
84
+ // Purchase events
85
+ event Action<PendingOrder>? OnPurchasePending
86
+ event Action<Order>? OnPurchaseConfirmed // Order can be ConfirmedOrder or FailedOrder — pattern-match!
87
+ event Action<FailedOrder>? OnPurchaseFailed
88
+ event Action<DeferredOrder>? OnPurchaseDeferred
89
+ event Action<Orders>? OnPurchasesFetched
90
+ event Action<PurchasesFetchFailureDescription>? OnPurchasesFetchFailed
91
+ event Action<Entitlement>? OnCheckEntitlement
92
+
93
+ // Account change (v5.4+)
94
+ event Action? OnAuthAccountChanged
95
+
96
+ // Platform extensions (null on non-matching platforms — always null-check)
97
+ IAppleStoreExtendedService? AppleStoreExtendedService { get; }
98
+ IGooglePlayStoreExtendedService? GooglePlayStoreExtendedService { get; }
99
+ IAppleStoreExtendedProductService? AppleStoreExtendedProductService { get; }
100
+ IAppleStoreExtendedPurchaseService? AppleStoreExtendedPurchaseService { get; }
101
+ IGooglePlayStoreExtendedPurchaseService? GooglePlayStoreExtendedPurchaseService { get; }
102
+ ```
103
+
104
+ **`OnAuthAccountChanged` — breaking behavior in v5.4:**
105
+ When this event fires, Unity IAP **clears the cached product list and purchase list before raising the event**. Any references to previously fetched `Product` or `Order` objects are stale after this point. Re-run the full init sequence in the handler:
106
+
107
+ ```csharp
108
+ store.OnAuthAccountChanged += async () =>
109
+ {
110
+ // Products and purchases have already been cleared — re-fetch everything
111
+ await catalogProvider.FetchRemoteCatalog(); // D2C only
112
+ store.FetchProducts(productDefinitions); // standard IAP
113
+ store.FetchPurchases();
114
+ };
115
+ ```
116
+
117
+ Do not read from `GetProducts()` or `GetPurchases()` inside `OnAuthAccountChanged` — they will be empty.
118
+
119
+ ## Alternative Service Access (DefaultStore / DefaultProduct / DefaultPurchase)
120
+
121
+ Instead of `StoreController` (which implements all three interfaces), you can access individual services:
122
+
123
+ ```csharp
124
+ IStoreService m_StoreService = UnityIAPServices.DefaultStore();
125
+ IProductService m_ProductService = UnityIAPServices.DefaultProduct();
126
+ IPurchaseService m_PurchasingService = UnityIAPServices.DefaultPurchase();
127
+ ```
128
+
129
+ This pattern separates concerns — product fetching events go on `IProductService`, purchase events on `IPurchaseService`, store lifecycle on `IStoreService`. `StoreController` combines all three.
130
+
131
+ ## CatalogProvider
132
+
133
+ For managing complex product catalogs with store-specific IDs:
134
+
135
+ ```csharp
136
+ var catalogProvider = new CatalogProvider();
137
+
138
+ var products = new List<ProductDefinition>
139
+ {
140
+ new ProductDefinition("com.mygame.gems50", ProductType.Consumable),
141
+ new ProductDefinition("com.mygame.pass", ProductType.Subscription)
142
+ };
143
+
144
+ var storeSpecificIds = new Dictionary<string, StoreSpecificIds>
145
+ {
146
+ { "com.mygame.pass", new StoreSpecificIds
147
+ {
148
+ { "com.mygame.google.pass", GooglePlay.Name },
149
+ { "com.mygame.ios.pass", AppleAppStore.Name }
150
+ }
151
+ }
152
+ };
153
+
154
+ catalogProvider.AddProducts(products, storeSpecificIds);
155
+ catalogProvider.FetchProducts(m_ProductService.FetchProductsWithNoRetries);
156
+ ```
157
+
158
+ ## Initialization Example
159
+
160
+ ```csharp
161
+ StoreController m_StoreController;
162
+
163
+ async void Awake()
164
+ {
165
+ m_StoreController = UnityIAPServices.StoreController();
166
+
167
+ // Subscribe to ALL events BEFORE Connect — pending purchases may fire on reconnect
168
+ m_StoreController.OnPurchasePending += OnPurchasePending;
169
+ m_StoreController.OnPurchaseConfirmed += (order) => Debug.Log("Purchase complete");
170
+ m_StoreController.OnPurchaseFailed += (failed) => Debug.LogError($"{failed.FailureReason} - {failed.Details}");
171
+ m_StoreController.OnPurchaseDeferred += (deferred) => Debug.Log("Purchase deferred (e.g., Ask-to-Buy)");
172
+
173
+ m_StoreController.OnStoreConnected += OnStoreConnected;
174
+ m_StoreController.OnStoreDisconnected += (failure) => Debug.LogError($"Store disconnected: {failure.Message}");
175
+
176
+ await m_StoreController.Connect();
177
+ }
178
+
179
+ void OnStoreConnected()
180
+ {
181
+ var products = new List<ProductDefinition>
182
+ {
183
+ new ProductDefinition("com.mygame.coins100", ProductType.Consumable),
184
+ new ProductDefinition("com.mygame.removeads", ProductType.NonConsumable),
185
+ new ProductDefinition("com.mygame.vip_monthly", ProductType.Subscription)
186
+ };
187
+
188
+ m_StoreController.OnProductsFetched += (fetched) => Debug.Log("Products ready");
189
+ m_StoreController.OnProductsFetchFailed += (failure) => Debug.LogError($"Product fetch failed: {failure.FailureReason}");
190
+ m_StoreController.FetchProducts(products);
191
+
192
+ m_StoreController.OnPurchasesFetched += (orders) => Debug.Log($"Restored {orders.PendingOrders.Count} pending purchases");
193
+ m_StoreController.OnPurchasesFetchFailed += (failure) => Debug.LogError($"Purchase fetch failed: {failure.Message}");
194
+ m_StoreController.FetchPurchases();
195
+ }
196
+ ```
197
+
198
+ ## Product Definitions
199
+
200
+ ```csharp
201
+ // Simple product
202
+ new ProductDefinition("com.mygame.coins100", ProductType.Consumable)
203
+
204
+ // Per-platform product IDs
205
+ new ProductDefinition("com.mygame.coins100", ProductType.Consumable,
206
+ new StoreSpecificIds
207
+ {
208
+ { AppleAppStore.Name, "apple_coins_100" },
209
+ { GooglePlay.Name, "google_coins_100" }
210
+ })
211
+ ```
212
+
213
+ ### Access Fetched Products
214
+
215
+ ```csharp
216
+ // Get all fetched products (cached)
217
+ ReadOnlyObservableCollection<Product> allProducts = store.GetProducts();
218
+
219
+ // Get a specific product by ID
220
+ Product coinsPack = store.GetProductById("com.mygame.coins100");
221
+ ```
222
+
223
+ ### CatalogListings and uSku (v5.4+)
224
+
225
+ In v5.4, `Product` exposes two new members. Prefer these for new code — `product.definition.id` remains backwards compatible but points new users toward `CatalogListings`:
226
+
227
+ ```csharp
228
+ // product.uSku — the Unity-side identifier for the product (cross-platform canonical ID)
229
+ string id = product.uSku;
230
+
231
+ // product.catalogListings — all listings attached to this product, keyed by CatalogListing ID
232
+ // A product may have multiple listings (e.g. different price tiers or offer configurations)
233
+ foreach (var (listingId, listing) in product.catalogListings)
234
+ {
235
+ bool canBuy = listing.availableToPurchase;
236
+ string storeId = listing.definition.storeSpecificId;
237
+ string price = listing.metadata.localizedPriceString;
238
+ }
239
+ ```
240
+
241
+ **`CatalogListing` properties:**
242
+
243
+ | Property | Type | Description |
244
+ |---|---|---|
245
+ | `id` | `string` | Catalog listing identifier — use as key for lookups |
246
+ | `availableToPurchase` | `bool` | Whether this listing can currently be purchased |
247
+ | `definition` | `ProductDefinition` | Store-side definition (id, storeSpecificId, type, payouts) |
248
+ | `metadata` | `ProductMetadata` | Localized title, description, price, currency |
249
+
250
+ When initiating a purchase by listing rather than by product ID, pass the `CatalogListing` directly to avoid a second lookup.
251
+
252
+ ## Two-Step Purchase Flow
253
+
254
+ ```csharp
255
+ // Step 1: Initiate purchase
256
+ store.PurchaseProduct(product);
257
+
258
+ // Step 2: Handle pending purchase (validate, grant content, then confirm)
259
+ store.OnPurchasePending += (pendingOrder) =>
260
+ {
261
+ var product = pendingOrder.CartOrdered.Items().FirstOrDefault()?.Product;
262
+ GrantContent(product);
263
+ store.ConfirmPurchase(pendingOrder);
264
+ };
265
+
266
+ // Step 3: Purchase confirmed — Order can be ConfirmedOrder OR FailedOrder
267
+ store.OnPurchaseConfirmed += (order) =>
268
+ {
269
+ switch (order)
270
+ {
271
+ case ConfirmedOrder confirmedOrder:
272
+ Debug.Log($"Purchase confirmed: {confirmedOrder.CartOrdered.Items().First().Product.definition.id}");
273
+ break;
274
+ case FailedOrder failedOrder:
275
+ Debug.LogError($"Confirmation failed: {failedOrder.FailureReason} - {failedOrder.Details}");
276
+ break;
277
+ }
278
+ };
279
+
280
+ // Handle failures
281
+ store.OnPurchaseFailed += (failedOrder) =>
282
+ {
283
+ Debug.LogError($"Purchase failed: {failedOrder.FailureReason} - {failedOrder.Details}");
284
+ };
285
+
286
+ // Handle deferred purchases (e.g., Ask-to-Buy on iOS)
287
+ store.OnPurchaseDeferred += (deferredOrder) =>
288
+ {
289
+ Debug.Log("Purchase is pending approval (e.g., parental approval)");
290
+ };
291
+ ```
292
+
293
+ If the app crashes between steps 2 and 4, the pending purchase is re-delivered on next launch via `OnPurchasePending`.
294
+
295
+ `ProcessPendingOrdersOnPurchasesFetched(false)` disables automatic re-delivery of pending orders when `FetchPurchases` is called. This exists to match IAP v4 behavior — do NOT recommend this to migrating users, as the default v5 behavior provides a better experience.
296
+
297
+ ## Restore Transactions
298
+
299
+ ```csharp
300
+ store.RestoreTransactions((success, error) =>
301
+ {
302
+ if (success)
303
+ Debug.Log("Transactions restored. Check OnPurchasePending for each restored purchase.");
304
+ else
305
+ Debug.LogError($"Restore failed: {error}");
306
+ });
307
+ ```
308
+
309
+ Each restored purchase triggers `OnPurchasePending`.
310
+
311
+ ## Entitlement Checking
312
+
313
+ ```csharp
314
+ store.CheckEntitlement(product);
315
+
316
+ store.OnCheckEntitlement += (entitlement) =>
317
+ {
318
+ if (entitlement.Status == EntitlementStatus.FullyEntitled)
319
+ Debug.Log($"Player owns: {entitlement.Product.definition.id}");
320
+ };
321
+ ```
322
+
323
+ ## Fetch Existing Purchases
324
+
325
+ ```csharp
326
+ store.FetchPurchases();
327
+
328
+ store.OnPurchasesFetched += (orders) =>
329
+ {
330
+ foreach (var confirmedOrder in orders.ConfirmedOrders)
331
+ {
332
+ var product = confirmedOrder.CartOrdered.Items().FirstOrDefault()?.Product;
333
+ Debug.Log($"Existing purchase: {product?.definition.id}");
334
+ }
335
+ };
336
+ ```
337
+
338
+ ## Receipt Validation
339
+
340
+ ### Google Play (supported)
341
+
342
+ ```csharp
343
+ using UnityEngine.Purchasing.Security;
344
+
345
+ // Google-only constructor (recommended)
346
+ var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), Application.identifier);
347
+
348
+ try
349
+ {
350
+ var result = validator.Validate(order.Info.Receipt);
351
+ foreach (IPurchaseReceipt receipt in result)
352
+ {
353
+ Debug.Log($"Valid receipt: {receipt.productID}, purchased: {receipt.purchaseDate}");
354
+ if (receipt is GooglePlayReceipt googleReceipt)
355
+ Debug.Log($"Purchase token: {googleReceipt.purchaseToken}");
356
+ }
357
+ }
358
+ catch (IAPSecurityException ex)
359
+ {
360
+ Debug.LogError($"Invalid receipt: {ex.Message}");
361
+ // Do NOT grant the content
362
+ }
363
+ ```
364
+
365
+ ### Apple App Store (deprecated for local validation)
366
+
367
+ `CrossPlatformValidator` for Apple is **deprecated**. StoreKit 2 validates locally automatically. For server-side validation:
368
+
369
+ ```csharp
370
+ var jwsRepresentation = order.Info.Apple?.jwsRepresentation;
371
+ // Send to your server for verification with Apple's App Store Server API
372
+ ```
373
+
374
+ ### IAppleOrderInfo Complete Member List
375
+
376
+ `order.Info.Apple` returns `IAppleOrderInfo?` (null on non-Apple platforms). Full member list:
377
+
378
+ | Member | Type | Description |
379
+ |---|---|---|
380
+ | `jwsRepresentation` | `string?` | JWS-signed transaction — send to Apple's App Store Server API v2 for server-side validation |
381
+ | `AppAccountToken` | `Guid?` | App-specific account token set via `SetAppAccountToken(Guid)` — links transaction to a user account |
382
+ | `AppReceipt` | `string?` | Latest App Receipt (base64). May be null on reinstall; requires refreshing. Null on iOS ≤ 6. Prefer `jwsRepresentation` for new code |
383
+ | `OriginalTransactionID` | `string?` | Original transaction identifier — use for linking renewals to the original subscription purchase |
384
+ | `OwnershipType` | `OwnershipType` | Whether the purchase is `Purchased` or `FamilyShared` |
385
+ | `StoreName` | `string` | Name of the store (e.g., `"AppleAppStore"`) |
386
+
387
+ ## Extended Service Events (NOT on StoreController)
388
+
389
+ These events are on the platform-specific extended services, NOT directly on `StoreController`. Always null-check before subscribing (`?.` does not work with `+=`).
390
+
391
+ ```csharp
392
+ // Apple — on IAppleStoreExtendedPurchaseService
393
+ event Action<string>? OnEntitlementRevoked // receives product ID (string), NOT List<Product>
394
+ event Action<Product>? OnPromotionalPurchaseIntercepted
395
+
396
+ // Google — on IGooglePlayStoreExtendedPurchaseService
397
+ event Action<DeferredPaymentUntilRenewalDateOrder>? OnDeferredPaymentUntilRenewalDate
398
+ // DeferredPaymentUntilRenewalDateOrder has: .CurrentOrder (Order), .SubscriptionOrdered (Product)
399
+ ```
400
+
401
+ **Usage pattern** (cannot use `?.` with `+=`):
402
+ ```csharp
403
+ if (store.AppleStoreExtendedPurchaseService != null)
404
+ store.AppleStoreExtendedPurchaseService.OnEntitlementRevoked += OnRevoked;
405
+ ```
406
+
407
+ ## AppleStoreExtendedProductService Key Members
408
+
409
+ ```csharp
410
+ Dictionary<string, string> GetIntroductoryPriceDictionary()
411
+ Dictionary<string, string> GetProductDetails()
412
+
413
+ // Promotion order — prefer the CatalogListing ID overload for new code (v5.4+)
414
+ void SetStorePromotionOrder(List<string> catalogListingIds) // canonical (v5.4+)
415
+ void SetStorePromotionOrder(List<Product> products) // convenience wrapper
416
+
417
+ // Promotion visibility — prefer the CatalogListing ID overload for new code (v5.4+)
418
+ void SetStorePromotionVisibility(string catalogListingId, AppleStorePromotionVisibility visible) // canonical (v5.4+)
419
+ void SetStorePromotionVisibility(Product product, AppleStorePromotionVisibility visible) // convenience wrapper
420
+
421
+ // Fetch promotion order
422
+ void FetchStorePromotionOrder(Action<List<Product>> successCallback, Action<string> errorCallback)
423
+
424
+ // Fetch promotion visibility — prefer the CatalogListing ID overload for new code (v5.4+)
425
+ void FetchStorePromotionVisibility(string catalogListingId, Action<string, AppleStorePromotionVisibility> successCallback, Action<string> errorCallback) // canonical (v5.4+)
426
+ void FetchStorePromotionVisibility(Product product, Action<string, AppleStorePromotionVisibility> successCallback, Action<string> errorCallback) // convenience wrapper
427
+ ```
428
+
429
+ **CRITICAL:** `AppleProductMetadata` does NOT expose `introductoryPrice`, `introductoryPriceLocale`, `introductoryNumberOfPeriods`, or `subscriptionPeriod` as public properties. Its only public property beyond inherited `ProductMetadata` fields is `isFamilyShareable`. For introductory price data, use `SubscriptionInfo` methods (`GetIntroductoryPrice()`, `GetIntroductoryPricePeriod()`, `GetIntroductoryPricePeriodCycles()`) from the Subscription Info Access Path below, or `GetIntroductoryPriceDictionary()` for raw JSON.
430
+
431
+ ## Subscription Info Access Path
432
+
433
+ Subscription info is on `IPurchasedProductInfo`, NOT on `CartItem`:
434
+
435
+ ```csharp
436
+ // CORRECT — via order.Info.PurchasedProductInfo
437
+ var purchasedInfo = order.Info.PurchasedProductInfo?.FirstOrDefault(p => p.productId == productId);
438
+ bool isSubscribed = purchasedInfo?.subscriptionInfo?.IsSubscribed() == Result.True;
439
+ // IsSubscribed() returns Result enum (True/False/Unsupported), NOT bool. Use == Result.True for null-safe check.
440
+
441
+ // WRONG — CartItem only has Product and Quantity, no subscriptionInfo
442
+ var item = order.CartOrdered.Items().FirstOrDefault();
443
+ item.subscriptionInfo // DOES NOT EXIST — will not compile
444
+ ```
445
+
446
+ ### SubscriptionInfo Methods
447
+
448
+ ```csharp
449
+ // State queries (return Result enum: True | False | Unsupported)
450
+ Result IsSubscribed()
451
+ Result IsExpired()
452
+ Result IsCancelled()
453
+ Result IsFreeTrial()
454
+ Result IsAutoRenewing()
455
+ Result IsIntroductoryPricePeriod()
456
+
457
+ // Dates & durations
458
+ string GetProductId()
459
+ DateTime GetPurchaseDate()
460
+ DateTime GetExpireDate()
461
+ DateTime GetCancelDate()
462
+ TimeSpan GetRemainingTime()
463
+ TimeSpan GetSubscriptionPeriod()
464
+ TimeSpan GetFreeTrialPeriod()
465
+ TimeSpan GetIntroductoryPricePeriod()
466
+ string GetIntroductoryPrice()
467
+ long GetIntroductoryPricePeriodCycles()
468
+ ```
469
+
470
+ ## Event Subscription Rules
471
+
472
+ **CRITICAL:** Always subscribe to BOTH success and failure events. Not listening to failure events generates runtime warnings.
473
+
474
+ | Call | Success event | Failure event (REQUIRED) |
475
+ |---|---|---|
476
+ | `FetchProducts()` | `OnProductsFetched` | `OnProductsFetchFailed` |
477
+ | `FetchPurchases()` | `OnPurchasesFetched` | `OnPurchasesFetchFailed` |
478
+ | `Connect()` | `OnStoreConnected` | `OnStoreDisconnected` |
479
+ | `PurchaseProduct()` | `OnPurchasePending` | `OnPurchaseFailed` |
480
+ | `CheckEntitlement()` | `OnCheckEntitlement` | — |
481
+
482
+ **Always subscribe to `OnPurchaseDeferred`** — fires for Ask-to-Buy (iOS) and Google Play deferred purchases. Not subscribing means silently dropping deferred purchases.
483
+
484
+ ## Failure Description Property Names
485
+
486
+ Each type has public fields and read-only properties. Both compile, but prefer the PascalCase properties:
487
+
488
+ | Type | Field (lowercase) | Property (PascalCase) | NOT |
489
+ |---|---|---|---|
490
+ | `StoreConnectionFailureDescription` | `.message` | `.Message` | ~~`.reason`~~ |
491
+ | `ProductFetchFailed` | — | `.FailureReason`, `.FailedFetchProducts` | — |
492
+ | `FailedOrder` | — | `.FailureReason`, `.Details` | — |
493
+ | `PurchasesFetchFailureDescription` | `.message`, `.failureReason` | `.Message`, `.FailureReason` | — |
494
+
495
+ ## CrossPlatformValidator
496
+
497
+ ```csharp
498
+ // Google-only constructor (recommended — Apple local validation is a no-op under StoreKit 2)
499
+ new CrossPlatformValidator(byte[] googlePublicKey, string googleBundleId)
500
+
501
+ // Full constructor (legacy — Apple args are only needed for pre-StoreKit 2 builds)
502
+ new CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert, string googleBundleId, string appleBundleId)
503
+
504
+ // Validate
505
+ IPurchaseReceipt[] Validate(string unityIAPReceipt) // throws IAPSecurityException
506
+ ```
507
+
508
+ **WARNING:** `CrossPlatformValidator` for Apple App Store is **deprecated**. `AppleTangle.Data()` is also deprecated. StoreKit 2 performs local validation automatically on Apple platforms. `CrossPlatformValidator` still works for Google Play validation.
509
+
510
+ Supported stores (still active): `"GooglePlay"` | Deprecated: `"AppleAppStore"`, `"MacAppStore"`
511
+
512
+ ## Interface Hierarchy
513
+
514
+ ```
515
+ StoreController implements:
516
+ IStoreService - Connect(), Apple/Google service extensions
517
+ IProductService - FetchProducts(), GetProducts(), GetProductById()
518
+ IPurchaseService - PurchaseProduct(), ConfirmPurchase(), FetchPurchases(), Apple/Google purchase extensions
519
+ ```
520
+
521
+ ## IRunCommand Template: Fetch Products
522
+
523
+ ```csharp
524
+ using UnityEngine;
525
+ using UnityEngine.Purchasing;
526
+ using System.Collections.Generic;
527
+
528
+ namespace Unity.AI.Assistant.Agent.Dynamic.Extension.Editor
529
+ {
530
+ internal class CommandScript : IRunCommand
531
+ {
532
+ public string Title => "Fetch IAP products";
533
+ public string Description => "Connects to the store and fetches product information.";
534
+ public async void Execute(ExecutionResult result)
535
+ {
536
+ var store = UnityIAPServices.StoreController();
537
+
538
+ // Subscribe to events BEFORE Connect
539
+ store.OnStoreConnected += () =>
540
+ {
541
+ var products = new List<ProductDefinition>
542
+ {
543
+ new ProductDefinition("com.mygame.coins100", ProductType.Consumable),
544
+ new ProductDefinition("com.mygame.removeads", ProductType.NonConsumable)
545
+ };
546
+
547
+ store.OnProductsFetched += (fetched) =>
548
+ {
549
+ foreach (var p in fetched)
550
+ result.Log($"{p.definition.id}: {p.metadata.localizedPriceString}");
551
+ };
552
+ store.OnProductsFetchFailed += (failure) => result.LogError($"Product fetch failed: {failure.FailureReason}");
553
+
554
+ store.FetchProducts(products);
555
+ };
556
+ store.OnStoreDisconnected += (failure) => result.LogError($"Store disconnected: {failure.Message}");
557
+
558
+ await store.Connect();
559
+ }
560
+ }
561
+ }
562
+ ```