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.
- package/.claude/agents/nccgs-accessibility-specialist.md +26 -0
- package/.claude/agents/nccgs-adversarial-reviewer.md +26 -0
- package/.claude/agents/nccgs-ai-programmer.md +26 -0
- package/.claude/agents/nccgs-analytics-engineer.md +26 -0
- package/.claude/agents/nccgs-art-direction-lead.md +26 -0
- package/.claude/agents/nccgs-audio-direction-lead.md +26 -0
- package/.claude/agents/nccgs-creative-director.md +26 -0
- package/.claude/agents/nccgs-documentation-manager.md +26 -0
- package/.claude/agents/nccgs-economy-designer.md +26 -0
- package/.claude/agents/nccgs-engine-programmer.md +26 -0
- package/.claude/agents/nccgs-game-design-lead.md +26 -0
- package/.claude/agents/nccgs-game-designer.md +26 -0
- package/.claude/agents/nccgs-gameplay-programmer.md +26 -0
- package/.claude/agents/nccgs-level-designer.md +26 -0
- package/.claude/agents/nccgs-live-ops-designer.md +26 -0
- package/.claude/agents/nccgs-localization-lead.md +26 -0
- package/.claude/agents/nccgs-narrative-lead.md +26 -0
- package/.claude/agents/nccgs-network-programmer.md +26 -0
- package/.claude/agents/nccgs-performance-analyst.md +26 -0
- package/.claude/agents/nccgs-producer.md +26 -0
- package/.claude/agents/nccgs-production-coordinator.md +23 -0
- package/.claude/agents/nccgs-programming-lead.md +26 -0
- package/.claude/agents/nccgs-prototyper.md +26 -0
- package/.claude/agents/nccgs-qa-engineer.md +26 -0
- package/.claude/agents/nccgs-qa-lead.md +26 -0
- package/.claude/agents/nccgs-release-engineer.md +26 -0
- package/.claude/agents/nccgs-release-lead.md +26 -0
- package/.claude/agents/nccgs-security-engineer.md +26 -0
- package/.claude/agents/nccgs-sound-designer.md +26 -0
- package/.claude/agents/nccgs-systems-designer.md +26 -0
- package/.claude/agents/nccgs-technical-architect.md +26 -0
- package/.claude/agents/nccgs-technical-artist.md +26 -0
- package/.claude/agents/nccgs-technical-director.md +26 -0
- package/.claude/agents/nccgs-tools-programmer.md +26 -0
- package/.claude/agents/nccgs-ui-programmer.md +26 -0
- package/.claude/agents/nccgs-unity-build-specialist.md +26 -0
- package/.claude/agents/nccgs-unity-content-specialist.md +26 -0
- package/.claude/agents/nccgs-unity-implementer.md +26 -0
- package/.claude/agents/nccgs-unity-rendering-specialist.md +26 -0
- package/.claude/agents/nccgs-unity-systems-specialist.md +26 -0
- package/.claude/agents/nccgs-unity-ui-specialist.md +26 -0
- package/.claude/agents/nccgs-ux-designer.md +26 -0
- package/.claude/agents/nccgs-verification-engineer.md +26 -0
- package/.claude/agents/nccgs-world-builder.md +26 -0
- package/.claude/agents/nccgs-writer.md +26 -0
- package/.claude/nccgs/THIRD_PARTY_NOTICES.md +13 -0
- package/.claude/nccgs/VERSION +1 -0
- package/.claude/nccgs/constitution.md +131 -0
- package/.claude/nccgs/hooks/agent-audit.mjs +10 -0
- package/.claude/nccgs/hooks/common.mjs +41 -0
- package/.claude/nccgs/hooks/post-compact.mjs +1 -0
- package/.claude/nccgs/hooks/pre-compact.mjs +8 -0
- package/.claude/nccgs/hooks/protect-git.mjs +22 -0
- package/.claude/nccgs/hooks/protect-write.mjs +17 -0
- package/.claude/nccgs/hooks/session-start.mjs +15 -0
- package/.claude/nccgs/hooks/session-stop.mjs +7 -0
- package/.claude/nccgs/protocols/agent-contract.md +34 -0
- package/.claude/nccgs/protocols/context-packets.md +13 -0
- package/.claude/nccgs/protocols/evidence.md +15 -0
- package/.claude/nccgs/protocols/model-routing.md +22 -0
- package/.claude/nccgs/protocols/orchestration.md +26 -0
- package/.claude/nccgs/protocols/unity-boundary.md +11 -0
- package/.claude/nccgs/settings.fragment.json +77 -0
- package/.claude/nccgs/studio.json +347 -0
- package/.claude/nccgs/tools/configure-models.mjs +39 -0
- package/.claude/nccgs/unity-skills-manifest.json +727 -0
- package/.claude/nccgs/workflow-catalog.json +317 -0
- package/.claude/rules/nccgs-canonical-docs.md +19 -0
- package/.claude/rules/nccgs-editor-tools.md +12 -0
- package/.claude/rules/nccgs-localization.md +12 -0
- package/.claude/rules/nccgs-networking.md +13 -0
- package/.claude/rules/nccgs-performance.md +14 -0
- package/.claude/rules/nccgs-rendering.md +16 -0
- package/.claude/rules/nccgs-security.md +13 -0
- package/.claude/rules/nccgs-tests.md +18 -0
- package/.claude/rules/nccgs-ui.md +14 -0
- package/.claude/rules/nccgs-unity-assets.md +21 -0
- package/.claude/rules/nccgs-unity-code.md +22 -0
- package/.claude/skills/accessibility-review/SKILL.md +14 -0
- package/.claude/skills/architecture-decision/SKILL.md +14 -0
- package/.claude/skills/asset-audit/SKILL.md +14 -0
- package/.claude/skills/audit/SKILL.md +16 -0
- package/.claude/skills/audit/references/dimensions.md +46 -0
- package/.claude/skills/balance-review/SKILL.md +14 -0
- package/.claude/skills/bug-triage/SKILL.md +14 -0
- package/.claude/skills/build-live-game/SKILL.md +317 -0
- package/.claude/skills/build-live-game/references/achievements.md +779 -0
- package/.claude/skills/build-live-game/references/apis.md +280 -0
- package/.claude/skills/build-live-game/references/authentication.md +437 -0
- package/.claude/skills/build-live-game/references/battlepass.md +860 -0
- package/.claude/skills/build-live-game/references/cloud-code.md +563 -0
- package/.claude/skills/build-live-game/references/cloud-save.md +474 -0
- package/.claude/skills/build-live-game/references/deployment.md +216 -0
- package/.claude/skills/build-live-game/references/player-account.md +813 -0
- package/.claude/skills/build-live-game/references/remote-config.md +96 -0
- package/.claude/skills/build-live-game/references/tooling.md +431 -0
- package/.claude/skills/closure/SKILL.md +14 -0
- package/.claude/skills/code-review/SKILL.md +14 -0
- package/.claude/skills/compatibility-review/SKILL.md +14 -0
- package/.claude/skills/context-pack/SKILL.md +14 -0
- package/.claude/skills/dependency-review/SKILL.md +14 -0
- package/.claude/skills/design/SKILL.md +20 -0
- package/.claude/skills/design-review/SKILL.md +14 -0
- package/.claude/skills/evidence-review/SKILL.md +14 -0
- package/.claude/skills/hotfix/SKILL.md +14 -0
- package/.claude/skills/implement-in-app-purchases/README.md +233 -0
- package/.claude/skills/implement-in-app-purchases/SKILL.md +158 -0
- package/.claude/skills/implement-in-app-purchases/references/api-notes.md +562 -0
- package/.claude/skills/implement-in-app-purchases/references/codeless-catalog.md +331 -0
- package/.claude/skills/implement-in-app-purchases/references/convert-adapty.md +268 -0
- package/.claude/skills/implement-in-app-purchases/references/convert-essentialkit.md +239 -0
- package/.claude/skills/implement-in-app-purchases/references/convert-revenuecat.md +275 -0
- package/.claude/skills/implement-in-app-purchases/references/convert-unipay.md +145 -0
- package/.claude/skills/implement-in-app-purchases/references/migration-v4-to-v5.md +204 -0
- package/.claude/skills/implement-in-app-purchases/references/path-add-iap-to-new-project.md +198 -0
- package/.claude/skills/implement-in-app-purchases/references/path-convert-native-google-billing.md +356 -0
- package/.claude/skills/implement-in-app-purchases/references/path-convert-native-storekit.md +446 -0
- package/.claude/skills/implement-in-app-purchases/references/path-implement-iap-d2c.md +680 -0
- package/.claude/skills/implement-in-app-purchases/references/platform-notes.md +204 -0
- package/.claude/skills/implement-in-app-purchases/references/pre-check.md +205 -0
- package/.claude/skills/incident-recovery/SKILL.md +14 -0
- package/.claude/skills/initialize-ai-navigation/SKILL.md +95 -0
- package/.claude/skills/initialize-ai-navigation/references/navigation-system.md +794 -0
- package/.claude/skills/levelplay-unity-integration/CHANGELOG.md +57 -0
- package/.claude/skills/levelplay-unity-integration/README.md +74 -0
- package/.claude/skills/levelplay-unity-integration/SKILL.md +1126 -0
- package/.claude/skills/levelplay-unity-integration/references/banner-api.md +920 -0
- package/.claude/skills/levelplay-unity-integration/references/best-practices.md +536 -0
- package/.claude/skills/levelplay-unity-integration/references/ilrd-api.md +337 -0
- package/.claude/skills/levelplay-unity-integration/references/initialization-api.md +630 -0
- package/.claude/skills/levelplay-unity-integration/references/interstitial-api.md +899 -0
- package/.claude/skills/levelplay-unity-integration/references/ios-setup.md +491 -0
- package/.claude/skills/levelplay-unity-integration/references/migration-sdk-9.md +666 -0
- package/.claude/skills/levelplay-unity-integration/references/privacy-settings.md +608 -0
- package/.claude/skills/levelplay-unity-integration/references/rewarded-api.md +902 -0
- package/.claude/skills/localization/SKILL.md +135 -0
- package/.claude/skills/localization/references/api-notes.md +76 -0
- package/.claude/skills/localization/resources/L10nBatchProcessor.cs +69 -0
- package/.claude/skills/localization/resources/LocalizedFontAsset.cs +18 -0
- package/.claude/skills/localize-game/SKILL.md +14 -0
- package/.claude/skills/migrate-project/SKILL.md +21 -0
- package/.claude/skills/migrate-project/references/procedure.md +63 -0
- package/.claude/skills/milestone-review/SKILL.md +14 -0
- package/.claude/skills/new-unity-project/SKILL.md +179 -0
- package/.claude/skills/optimize-audio/SKILL.md +199 -0
- package/.claude/skills/optimize-audio/resources/audio-import-api.md +146 -0
- package/.claude/skills/optimize-audio/resources/platform-settings.md +48 -0
- package/.claude/skills/optimize-text-mesh-pro/SKILL.md +182 -0
- package/.claude/skills/optimize-web/SKILL.md +393 -0
- package/.claude/skills/optimize-web/resources/WebOptimizer.cs +21 -0
- package/.claude/skills/optimize-web/resources/toktx-examples.sh +11 -0
- package/.claude/skills/performance-audit/SKILL.md +14 -0
- package/.claude/skills/physics-3d-collision/SKILL.md +442 -0
- package/.claude/skills/physics-3d-collision/references/troubleshooting.md +41 -0
- package/.claude/skills/physics-3d-collision/resources/CollisionDebugger.cs +33 -0
- package/.claude/skills/plan-feature/SKILL.md +14 -0
- package/.claude/skills/playtest/SKILL.md +14 -0
- package/.claude/skills/project-stage/SKILL.md +14 -0
- package/.claude/skills/prototype-feature/SKILL.md +14 -0
- package/.claude/skills/qa-plan/SKILL.md +14 -0
- package/.claude/skills/release/SKILL.md +18 -0
- package/.claude/skills/release-readiness/SKILL.md +14 -0
- package/.claude/skills/retrospective/SKILL.md +14 -0
- package/.claude/skills/review/SKILL.md +16 -0
- package/.claude/skills/security-audit/SKILL.md +14 -0
- package/.claude/skills/setup-multiplayer-services/SKILL.md +39 -0
- package/.claude/skills/setup-multiplayer-services/references/dgs-entrypoint.md +79 -0
- package/.claude/skills/setup-multiplayer-services/references/entrypoints.md +213 -0
- package/.claude/skills/setup-multiplayer-services/references/examples.md +33 -0
- package/.claude/skills/setup-multiplayer-services/references/implementation-fit.md +30 -0
- package/.claude/skills/setup-multiplayer-services/references/underlying-services.md +11 -0
- package/.claude/skills/setup-multiplayer-services/references/workflows-prerequisites.md +17 -0
- package/.claude/skills/setup-vivox-voice-chat/SKILL.md +118 -0
- package/.claude/skills/setup-vivox-voice-chat/evals/.env.example +6 -0
- package/.claude/skills/setup-vivox-voice-chat/evals/README.md +101 -0
- package/.claude/skills/setup-vivox-voice-chat/evals/promptfooconfig.yaml +32 -0
- package/.claude/skills/setup-vivox-voice-chat/evals/tests/init-and-login.yaml +76 -0
- package/.claude/skills/setup-vivox-voice-chat/evals/tests/text-chat.yaml +65 -0
- package/.claude/skills/setup-vivox-voice-chat/evals/tests/voice-channels.yaml +74 -0
- package/.claude/skills/setup-vivox-voice-chat/references/events-and-participants.md +79 -0
- package/.claude/skills/setup-vivox-voice-chat/references/init-and-login.md +92 -0
- package/.claude/skills/setup-vivox-voice-chat/references/text-chat.md +93 -0
- package/.claude/skills/setup-vivox-voice-chat/references/troubleshooting.md +47 -0
- package/.claude/skills/setup-vivox-voice-chat/references/voice-channels.md +90 -0
- package/.claude/skills/shader-graph-create-custom-node/SKILL.md +25 -0
- package/.claude/skills/shader-graph-create-custom-node/resources/all_hints.hlsl +182 -0
- package/.claude/skills/sprint-plan/SKILL.md +14 -0
- package/.claude/skills/sprite-editor/SKILL.md +66 -0
- package/.claude/skills/sprite-editor/references/api_reference.md +151 -0
- package/.claude/skills/sprite-editor/references/background.md +112 -0
- package/.claude/skills/sprite-editor/references/templates.md +72 -0
- package/.claude/skills/sprite-editor/scripts/AutomaticSliceTexture.cs +40 -0
- package/.claude/skills/sprite-editor/scripts/GenerateNewSpriteRects.cs +200 -0
- package/.claude/skills/sprite-editor/scripts/GetTextureSourceImageSize.cs +32 -0
- package/.claude/skills/sprite-editor/scripts/GetTextureToSlice.cs +55 -0
- package/.claude/skills/sprite-editor/scripts/GridSliceTexture.cs +40 -0
- package/.claude/skills/sprite-editor/scripts/IsometricSliceTexture.cs +141 -0
- package/.claude/skills/sprite-editor/scripts/README.md +134 -0
- package/.claude/skills/sprite-editor/scripts/SetPivotExample.cs +58 -0
- package/.claude/skills/sprite-editor/scripts/SpriteToPng.cs +88 -0
- package/.claude/skills/status/SKILL.md +16 -0
- package/.claude/skills/story-readiness/SKILL.md +14 -0
- package/.claude/skills/test/SKILL.md +16 -0
- package/.claude/skills/ui/SKILL.md +142 -0
- package/.claude/skills/ui-imgui/SKILL.md +186 -0
- package/.claude/skills/ui-imgui/references/gui-elements.md +156 -0
- package/.claude/skills/ui-imgui/references/templates.md +141 -0
- package/.claude/skills/ui-review/SKILL.md +14 -0
- package/.claude/skills/ui-ugui/SKILL.md +282 -0
- package/.claude/skills/ui-ugui/references/scrollview-setup.md +29 -0
- package/.claude/skills/ui-uitk/SKILL.md +235 -0
- package/.claude/skills/ui-uitk/references/common-issues.md +74 -0
- package/.claude/skills/ui-uitk/references/custom-elements.md +241 -0
- package/.claude/skills/ui-uitk/references/painter2d.md +282 -0
- package/.claude/skills/ui-uitk/references/pointermanipulator-guide.md +94 -0
- package/.claude/skills/ui-uitk/references/svg-icons.md +136 -0
- package/.claude/skills/ui-uitk/references/ui-runtime-binding.md +234 -0
- package/.claude/skills/ui-uitk/references/uss-guide.md +138 -0
- package/.claude/skills/unity-cli/CHANGELOG.md +233 -0
- package/.claude/skills/unity-cli/SECURITY.md +22 -0
- package/.claude/skills/unity-cli/SKILL.md +414 -0
- package/.claude/skills/unity-cli/references/auth-license-cloud.md +146 -0
- package/.claude/skills/unity-cli/references/build-run-test.md +349 -0
- package/.claude/skills/unity-cli/references/collaboration.md +472 -0
- package/.claude/skills/unity-cli/references/config-hub.md +103 -0
- package/.claude/skills/unity-cli/references/diagnostics-maintenance.md +326 -0
- package/.claude/skills/unity-cli/references/editors-install.md +327 -0
- package/.claude/skills/unity-cli/references/integration-advanced.md +472 -0
- package/.claude/skills/unity-cli/references/projects-templates.md +574 -0
- package/.claude/skills/unity-package-management/SKILL.md +304 -0
- package/.claude/skills/unity-package-management/references/select-packages.md +108 -0
- package/.claude/skills/urp-postprocessing/SKILL.md +188 -0
- package/.claude/skills/urp-postprocessing/references/code-templates.md +119 -0
- package/.claude/skills/urp-postprocessing/references/effect-reference.md +86 -0
- package/.claude/skills/validate-urp-render-graph-renderer-feature/SKILL.md +269 -0
- package/.claude/skills/work/SKILL.md +36 -0
- package/.claude/skills/work/references/classification.md +41 -0
- package/.claude/skills/work/references/closure.md +50 -0
- package/.claude/skills/work/references/feature-contracts.md +32 -0
- package/.claude/skills/work/references/verification.md +26 -0
- package/CLAUDE.md +6 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/UPGRADING.md +32 -0
- package/VERSION +1 -0
- package/docs/ARCHITECTURE.md +73 -0
- package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +324 -0
- package/docs/MIGRATION-MATRIX.md +23 -0
- package/docs/PROJECT-POLICY.md +67 -0
- package/docs/WORKFLOWS.md +61 -0
- package/package-assets/setup-vivox-voice-chat-evals.gitignore +7 -0
- package/package.json +41 -0
- package/scaffold/.nccgs/bugs/.gitkeep +1 -0
- package/scaffold/.nccgs/closures/.gitkeep +1 -0
- package/scaffold/.nccgs/context/.gitkeep +1 -0
- package/scaffold/.nccgs/decisions/.gitkeep +1 -0
- package/scaffold/.nccgs/evidence/.gitkeep +1 -0
- package/scaffold/.nccgs/features/.gitkeep +1 -0
- package/scaffold/.nccgs/migrations/.gitkeep +1 -0
- package/scaffold/.nccgs/playtests/.gitkeep +1 -0
- package/scaffold/.nccgs/project.yaml +103 -0
- package/scaffold/.nccgs/requirements.yaml +10 -0
- package/scaffold/.nccgs/reviews/.gitkeep +1 -0
- package/scaffold/.nccgs/state.md +40 -0
- package/scaffold/.nccgs/templates/agent-handoff.md +25 -0
- package/scaffold/.nccgs/templates/architecture-decision.md +27 -0
- package/scaffold/.nccgs/templates/closure-record.md +51 -0
- package/scaffold/.nccgs/templates/context-packet.yaml +17 -0
- package/scaffold/.nccgs/templates/evidence-record.md +23 -0
- package/scaffold/.nccgs/templates/feature-contract.md +35 -0
- package/scaffold/.nccgs/templates/migration-plan.md +40 -0
- package/scaffold/.nccgs/templates/waiver.md +11 -0
- package/scripts/cli.mjs +56 -0
- package/scripts/install.mjs +267 -0
- package/scripts/sync-unity-skills.mjs +126 -0
- package/scripts/validate.mjs +205 -0
- package/tests/framework.test.mjs +121 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Unity IAP v4 to v5 Migration Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Trigger Phrases](#trigger-phrases)
|
|
6
|
+
- [Overview](#overview)
|
|
7
|
+
- [Migration Mapping Table](#migration-mapping-table)
|
|
8
|
+
- [Key Breaking Changes](#key-breaking-changes)
|
|
9
|
+
- [Migration Anti-Patterns](#migration-anti-patterns)
|
|
10
|
+
- [Minimal v5 Example](#minimal-v5-example)
|
|
11
|
+
|
|
12
|
+
## Trigger Phrases
|
|
13
|
+
|
|
14
|
+
- "Migrate from Unity IAP v4 to v5"
|
|
15
|
+
- "Upgrade Unity IAP to v5"
|
|
16
|
+
- "Update IAP from v4"
|
|
17
|
+
- "Migrate IStoreListener to StoreController"
|
|
18
|
+
- "Replace ConfigurationBuilder with v5 IAP"
|
|
19
|
+
- "Update UnityPurchasing.Initialize to v5"
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
Unity IAP v5 replaces the listener-based pattern (`IStoreListener`) with an event-driven pattern (`StoreController` events). This is a breaking change that requires updating all IAP code.
|
|
24
|
+
|
|
25
|
+
## Migration Mapping Table
|
|
26
|
+
|
|
27
|
+
### Initialization
|
|
28
|
+
|
|
29
|
+
| v4 (Legacy) | v5 (Current) |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `UnityPurchasing.Initialize(listener, builder)` | `UnityIAPServices.StoreController()` then `await store.Connect()` |
|
|
32
|
+
| `ConfigurationBuilder.Instance(StandardPurchasingModule.Instance())` | Use `CatalogProvider` for store-specific IDs/payouts, or pass `List<ProductDefinition>` directly to `FetchProducts()` |
|
|
33
|
+
| `builder.AddProduct("id", ProductType.Consumable)` | `new ProductDefinition("id", ProductType.Consumable)` in a list |
|
|
34
|
+
| `IStoreListener.OnInitialized(controller, extensions)` | `store.OnStoreConnected` event |
|
|
35
|
+
| `IStoreListener.OnInitializeFailed(error)` | `store.OnStoreDisconnected` event |
|
|
36
|
+
|
|
37
|
+
### Purchase Flow
|
|
38
|
+
|
|
39
|
+
| v4 (Legacy) | v5 (Current) |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `controller.InitiatePurchase(product)` | `store.PurchaseProduct(product)` — note: `Purchase()` no longer accepts a developer payload argument (removed in Google Billing v3) |
|
|
42
|
+
| `IStoreListener.ProcessPurchase(args)` for **new** purchases | `store.OnPurchasePending` + `store.ConfirmPurchase(pendingOrder)` |
|
|
43
|
+
| `IStoreListener.ProcessPurchase(args)` for **restored** purchases | `store.OnPurchasesFetched` — use existing `ProcessPurchase` logic in both `OnPurchasePending` and `OnPurchasesFetched` |
|
|
44
|
+
| `IStoreListener.ProcessPurchase(args)` returning `PurchaseProcessingResult.Pending` | Just don't call `ConfirmPurchase` until ready — store the `PendingOrder` reference |
|
|
45
|
+
| `IStoreListener.OnPurchaseFailed(product, reason)` | `store.OnPurchaseFailed` event with `FailedOrder` (has `FailureReason` and `Details`) |
|
|
46
|
+
| `controller.ConfirmPendingPurchase(product)` | `store.ConfirmPurchase(pendingOrder)` — requires the `PendingOrder` object, not the `Product` |
|
|
47
|
+
| `product.hasReceipt` | **Preferred:** call `store.CheckEntitlement(product)` and handle `store.OnCheckEntitlement` — check `entitlement.Status == EntitlementStatus.FullyEntitled`. **Alternative:** track ownership with a `bool` flag updated in `OnPurchasePending` (new) and `OnPurchasesFetched` (restored). `CheckEntitlement` is the recommended v5 pattern for non-consumables and subscriptions. |
|
|
48
|
+
|
|
49
|
+
### Configuration / Products
|
|
50
|
+
|
|
51
|
+
| v4 (Legacy) | v5 (Current) |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `ConfigurationBuilder` with `AddProduct()` | `CatalogProvider` with `AddProduct()`, or a `List<ProductDefinition>` passed to `store.FetchProducts()` |
|
|
54
|
+
| `builder.AddProduct("id", type, new IDs { { "store_id", store } })` | `catalogProvider.AddProduct("id", type, new StoreSpecificIds { { "store_id", store } })` — or `new ProductDefinition("id", "store_id", type)` for a single store ID |
|
|
55
|
+
| `controller.products.WithID("id")` | `store.GetProductById("id")` |
|
|
56
|
+
| `controller.products.all` | `store.GetProducts()` |
|
|
57
|
+
|
|
58
|
+
### Restore Transactions
|
|
59
|
+
|
|
60
|
+
| v4 (Legacy) | v5 (Current) |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `extensions.GetExtension<IAppleExtensions>().RestoreTransactions(callback)` | `store.RestoreTransactions(callback)` |
|
|
63
|
+
| Manual restore call required | Confirmed purchases are **automatically restored** when you call `store.FetchPurchases()` or `store.CheckEntitlement()` — `RestoreTransactions` is only needed for explicit user-triggered restore buttons (required on iOS) |
|
|
64
|
+
|
|
65
|
+
### Receipt Validation
|
|
66
|
+
|
|
67
|
+
| v4 (Legacy) | v5 (Current) |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `CrossPlatformValidator` with Apple + Google | `CrossPlatformValidator` still works, but under StoreKit 2 Apple validation returns an empty array (no-op). **Recommended:** Use the Google-only constructor: `CrossPlatformValidator(GooglePlayTangle.Data(), Application.identifier)`. The legacy 4-arg constructor still works. Single-bundle-ID constructors are `[Obsolete]` |
|
|
70
|
+
| `AppleTangle.Data()` for Apple validation | Under StoreKit 2, local Apple receipt validation is a no-op. Use `order.Info.Apple?.jwsRepresentation` for server-side Apple validation instead |
|
|
71
|
+
| Manual receipt parsing | Use `order.Info.Apple?.jwsRepresentation` for server-side validation |
|
|
72
|
+
| `product.receipt` | `order.Info.Receipt` — receipt is now accessed from the `Order`/`PendingOrder`, not the `Product` |
|
|
73
|
+
|
|
74
|
+
### Platform Extensions
|
|
75
|
+
|
|
76
|
+
| v4 (Legacy) | v5 (Current) |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `extensions.GetExtension<IAppleExtensions>()` | `store.AppleStoreExtendedService` / `store.AppleStoreExtendedPurchaseService` |
|
|
79
|
+
| `extensions.GetExtension<IGooglePlayStoreExtensions>()` | `store.GooglePlayStoreExtendedService` / `store.GooglePlayStoreExtendedPurchaseService` |
|
|
80
|
+
| `builder.Configure<IAppleConfiguration>().SetApplePromotionalPurchaseInterceptorCallback(cb)` | `store.AppleStoreExtendedPurchaseService.OnPromotionalPurchaseIntercepted += cb` (event on `IAppleStoreExtendedPurchaseService`, null-check required; callback signature: `Action<Product>`) |
|
|
81
|
+
| `appleExtensions.ContinuePromotionalPurchases()` | `store.AppleStoreExtendedPurchaseService?.ContinuePromotionalPurchases()` |
|
|
82
|
+
| `appleExtensions.RegisterPurchaseDeferredListener(cb)` | `store.OnPurchaseDeferred += cb` (event on `StoreController`) |
|
|
83
|
+
| `appleExtensions.simulateAskToBuy` | `store.AppleStoreExtendedPurchaseService?.simulateAskToBuy` (null-check required) |
|
|
84
|
+
| `appleExtensions.PresentCodeRedemptionSheet()` | `store.AppleStoreExtendedPurchaseService?.PresentCodeRedemptionSheet()` |
|
|
85
|
+
| `appleExtensions.RestoreTransactions(cb)` | `store.RestoreTransactions(cb)` |
|
|
86
|
+
| `appleExtensions.SetApplicationUsername(hashedString)` | `store.AppleStoreExtendedService?.SetAppAccountToken(Guid)` — must be called **after** `Connect()`; accepts a `Guid` (not a hash) identifying the user account in your system |
|
|
87
|
+
| `builder.Configure<IAppleConfiguration>().SetEntitlementsRevokedListener(cb)` | `store.AppleStoreExtendedPurchaseService.OnEntitlementRevoked += cb` (event on `IAppleStoreExtendedPurchaseService`, null-check required; callback signature: `Action<string>` — receives a single product ID, NOT `List<Product>`) |
|
|
88
|
+
| `appleExtensions.GetTransactionReceiptForProduct(product)` | `order.Info.Receipt` inside `OnPurchasePending` — per-transaction receipt is now on the order |
|
|
89
|
+
| `builder.Configure<IAppleConfiguration>().canMakePayments` | `store.AppleStoreExtendedService?.canMakePayments` — must be checked **after** `Connect()`, not before initialization |
|
|
90
|
+
| `appleExtensions.GetIntroductoryPriceDictionary()` | `store.AppleStoreExtendedProductService?.GetIntroductoryPriceDictionary()` — returns `Dictionary<string, string>` mapping product store-specific IDs to JSON with intro offer details. **NOT removed**, just moved to the product extension service. `AppleProductMetadata` does NOT expose `introductoryPrice`, `introductoryPriceLocale`, `introductoryNumberOfPeriods`, or `subscriptionPeriod` — those fields do not exist on that class. For introductory price data, use `SubscriptionInfo` methods (`GetIntroductoryPrice()`, `GetIntroductoryPricePeriod()`, `GetIntroductoryPricePeriodCycles()`). |
|
|
91
|
+
| `appleExtensions.GetProductDetails()` | `store.AppleStoreExtendedProductService?.GetProductDetails()` — returns `Dictionary<string, string>`. **NOT removed**, just moved to the product extension service. Basic metadata (title, description, price) is on `product.metadata` directly. |
|
|
92
|
+
| `appleExtensions.SetStorePromotionOrder(products)` | `store.AppleStoreExtendedProductService?.SetStorePromotionOrder(products)` — moved to `IAppleStoreExtendedProductService` (null-check required) |
|
|
93
|
+
| `appleExtensions.SetStorePromotionVisibility(product, visibility)` | `store.AppleStoreExtendedProductService?.SetStorePromotionVisibility(product, visibility)` — moved to `IAppleStoreExtendedProductService` (null-check required) |
|
|
94
|
+
| `appleExtensions.FetchStorePromotionOrder(success, failure)` | `store.AppleStoreExtendedProductService?.FetchStorePromotionOrder(successCb, errorCb)` — moved to `IAppleStoreExtendedProductService` (null-check required; callback signatures: `Action<List<Product>>`, `Action<string>`) |
|
|
95
|
+
| `appleExtensions.FetchStorePromotionVisibility(product, success, failure)` | `store.AppleStoreExtendedProductService?.FetchStorePromotionVisibility(product, successCb, errorCb)` — moved to `IAppleStoreExtendedProductService` (null-check required; callback signatures: `Action<string, AppleStorePromotionVisibility>`, `Action<string>`) |
|
|
96
|
+
| `googlePlayConfig.SetDeferredPurchaseListener(cb)` | `store.OnPurchaseDeferred += cb` (event on `StoreController`) |
|
|
97
|
+
| `googlePlayConfig.SetDeferredProrationUpgradeDowngradeSubscriptionListener(cb)` | `store.GooglePlayStoreExtendedPurchaseService.OnDeferredPaymentUntilRenewalDate += cb` (event on `IGooglePlayStoreExtendedPurchaseService`, null-check required; callback signature: `Action<DeferredPaymentUntilRenewalDateOrder>` — NOT `Action<Product>`. Access the product via `deferredOrder.SubscriptionOrdered`) |
|
|
98
|
+
| `googlePlayExtensions.UpgradeDowngradeSubscription(currentId, newId, mode)` | `store.GooglePlayStoreExtendedPurchaseService?.UpgradeDowngradeSubscription(currentOrder, newProduct, desiredReplacementMode)` — takes `Order` and `Product` objects instead of string IDs; third parameter is `GooglePlayReplacementMode` (not the deprecated `GooglePlayProrationMode`); call after `Connect()` |
|
|
99
|
+
| `googlePlayExtensions.IsPurchasedProductDeferred(product)` | `store.GooglePlayStoreExtendedPurchaseService?.IsOrderDeferred(order)` — renamed and takes `Order` instead of `Product`; marked `[Obsolete]`. Prefer tracking deferred state via `store.OnPurchaseDeferred` / `store.OnPurchasePending` events instead |
|
|
100
|
+
| `googlePlayExtensions.RestoreTransactions(cb)` | `store.RestoreTransactions(cb)` — moved to `StoreController` directly |
|
|
101
|
+
| `googlePlayConfig.SetObfuscatedAccountId(id)` | `store.GooglePlayStoreExtendedService?.SetObfuscatedAccountId(id)` — moved from config-time to **post-`Connect()`** |
|
|
102
|
+
| `googlePlayConfig.SetObfuscatedProfileId(id)` | `store.GooglePlayStoreExtendedService?.SetObfuscatedProfileId(id)` — moved from config-time to **post-`Connect()`** |
|
|
103
|
+
|
|
104
|
+
### Subscription Info
|
|
105
|
+
|
|
106
|
+
| v4 (Legacy) | v5 (Current) |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `new SubscriptionManager(product, introJson).getSubscriptionInfo()` | `order.Info.PurchasedProductInfo.FirstOrDefault(p => p.productId == productId)?.subscriptionInfo` — accessed via `IPurchasedProductInfo` on the order's info, NOT on `CartItem`. `CartItem` only has `Product` and `Quantity`. |
|
|
109
|
+
| `subscriptionInfo.isSubscribed() == Result.True` | `purchasedProductInfo?.subscriptionInfo?.IsSubscribed() == Result.True` — method is now PascalCase but still returns `Result` enum (True/False/Unsupported), NOT `bool`. Use `== Result.True` for null-safe comparison (returns `false` if the chain is null). Do NOT use `?? false` — `Result?` and `bool` are incompatible types. |
|
|
110
|
+
| `product.receipt == null` to check ownership | **Preferred:** use `store.CheckEntitlement(product)` + `store.OnCheckEntitlement` — the recommended v5 ownership check. **Alternative:** track ownership with a `bool` flag updated in `OnPurchasePending` (new purchase) and `OnPurchasesFetched` (restored purchases). |
|
|
111
|
+
| `product.metadata.GetAppleProductMetadata()?.isFamilyShareable` | Still valid — `GetAppleProductMetadata()` extension method on `ProductMetadata` is unchanged |
|
|
112
|
+
|
|
113
|
+
### Codeless IAP
|
|
114
|
+
|
|
115
|
+
| v4 (Legacy) | v5 (Current) |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `CodelessIAPStoreListener.Instance` | Codeless still works but uses v5 under the hood |
|
|
118
|
+
| `CodelessIAPStoreListener.initializationComplete` | `CodelessIAPStoreListener.IsInitialized()` |
|
|
119
|
+
|
|
120
|
+
## Key Breaking Changes
|
|
121
|
+
|
|
122
|
+
1. **`IStoreListener` is deprecated**: Replace the interface implementation with event subscriptions on `StoreController`. The interface still compiles but is `[Obsolete]`.
|
|
123
|
+
2. **Two-step purchase flow is mandatory**: v5 always uses pending → confirm. There is no equivalent of returning `PurchaseProcessingResult.Complete` from `ProcessPurchase`.
|
|
124
|
+
3. **`ConfigurationBuilder` is deprecated**: Use `CatalogProvider` for store-specific IDs and payouts, or pass `List<ProductDefinition>` directly to `FetchProducts()`. The class still compiles but is `[Obsolete]`.
|
|
125
|
+
4. **Apple local receipt validation is a no-op under StoreKit 2**: `CrossPlatformValidator` itself is not deprecated, but Apple validation silently returns an empty array under StoreKit 2. **Recommended:** use the Google-only constructor `CrossPlatformValidator(GooglePlayTangle.Data(), Application.identifier)`. Single-bundle-ID constructors are `[Obsolete]`. For server-side Apple validation, use `order.Info.Apple?.jwsRepresentation`.
|
|
126
|
+
5. **Extensions are direct properties**: No more `GetExtension<T>()` pattern. Access via `store.AppleStoreExtendedService`, `store.AppleStoreExtendedPurchaseService`, `store.GooglePlayStoreExtendedService`, `store.GooglePlayStoreExtendedPurchaseService` (null-check required — only non-null on the matching platform).
|
|
127
|
+
6. **`product.receipt` and `product.hasReceipt` are gone**: Use `order.Info.Receipt` from `PendingOrder` for the transaction receipt. For ownership checking, use `store.CheckEntitlement(product)` + `OnCheckEntitlement` (recommended) or track ownership with `bool` flags updated in `OnPurchasePending` and `OnPurchasesFetched`.
|
|
128
|
+
7. **`Purchase()` no longer has a payload**: Developer payload support was removed in Google Billing v3.
|
|
129
|
+
8. **`ProcessPurchase` logic belongs in two places**: Put it in both `OnPurchasePending` (new purchases) and `OnPurchasesFetched` (restored/existing purchases).
|
|
130
|
+
9. **Restore is implicit**: Calling `FetchPurchases()` automatically re-delivers any unconfirmed purchases via `OnPurchasePending`. Explicit `RestoreTransactions()` is still required for the iOS "Restore Purchases" button.
|
|
131
|
+
10. **`SubscriptionManager` is replaced**: No more `new SubscriptionManager(product, introJson).getSubscriptionInfo()`. In v5, subscription info is accessed via `order.Info.PurchasedProductInfo.FirstOrDefault(p => p.productId == id)?.subscriptionInfo` — it's on `IPurchasedProductInfo`, NOT on `CartItem`. `CartItem` only has `Product` and `Quantity`. Method names are now PascalCase: `IsSubscribed()` not `isSubscribed()`.
|
|
132
|
+
11. **Platform-specific config must happen post-`Connect()`**: `SetAppAccountToken`, `SetObfuscatedAccountId/ProfileId`, `canMakePayments` — all moved from pre-init `ConfigurationBuilder` to the corresponding extended service, only available after `await store.Connect()`.
|
|
133
|
+
12. **Apple promotional APIs moved to `IAppleStoreExtendedProductService`**: `SetStorePromotionOrder`, `SetStorePromotionVisibility`, `FetchStorePromotionOrder`, `FetchStorePromotionVisibility` are now on `store.AppleStoreExtendedProductService` (null-check required — only non-null on Apple platforms). No more `GetExtension<IAppleExtensions>()` pattern.
|
|
134
|
+
13. **`OnPurchaseConfirmed` receives `Order` base type**: You MUST pattern-match `ConfirmedOrder` (success) vs `FailedOrder` (confirmation failed). Do not assume confirmation always succeeds.
|
|
135
|
+
14. **Platform-specific events are on extended services, NOT `StoreController`**: `OnPromotionalPurchaseIntercepted` and `OnEntitlementRevoked` are on `AppleStoreExtendedPurchaseService`. `OnDeferredPaymentUntilRenewalDate` is on `GooglePlayStoreExtendedPurchaseService`. Only `OnPurchaseDeferred` (Ask-to-Buy) is directly on `StoreController`. Always null-check the extended service before subscribing to events (use `if (store.AppleStoreExtendedPurchaseService != null)` pattern — `?.` does not work with `+=`).
|
|
136
|
+
|
|
137
|
+
## Migration Anti-Patterns
|
|
138
|
+
|
|
139
|
+
**Always subscribe to BOTH success and failure events.** Not subscribing to failure events (e.g., `OnProductsFetchFailed`, `OnPurchasesFetchFailed`, `OnStoreDisconnected`) generates runtime warnings and leaves failures unhandled.
|
|
140
|
+
|
|
141
|
+
**Always subscribe to `OnPurchaseDeferred`.** This event fires for Ask-to-Buy (iOS parental approval) and Google Play deferred purchases. Not subscribing means deferred purchases are silently ignored.
|
|
142
|
+
|
|
143
|
+
**Use `CheckEntitlement` for ownership checks, not manual bool flags.** When migrating `product.hasReceipt` or `product.receipt == null` ownership checks, the recommended v5 pattern is `store.CheckEntitlement(product)` + `store.OnCheckEntitlement`. This works cross-platform and handles edge cases (refunds, subscription expiration) automatically.
|
|
144
|
+
|
|
145
|
+
**`StoreConnectionFailureDescription` has `.message`, NOT `.reason`.** When migrating `OnInitializeFailed(error, message)` to `store.OnStoreDisconnected`, the failure description property is `failureDescription.message` (lowercase), not `reason`.
|
|
146
|
+
|
|
147
|
+
**`ProductFetchFailed` has `.FailureReason`, NOT `.Message`.** When handling `store.OnProductsFetchFailed`, access the reason via `failure.FailureReason`.
|
|
148
|
+
|
|
149
|
+
**Subscribe to events BEFORE calling `Connect()`.** Pending purchases from a previous session may fire immediately on reconnect. If your `OnPurchasePending` handler is not yet registered, those purchases will be missed.
|
|
150
|
+
|
|
151
|
+
**`OnPurchaseConfirmed` can receive `FailedOrder`.** The `OnPurchaseConfirmed` event fires with `Order` (base type). Always pattern-match: `ConfirmedOrder` means success, `FailedOrder` means confirmation failed. Do not assume confirmation always succeeds.
|
|
152
|
+
|
|
153
|
+
**Use `Awake()` for initialization, not `Start()`.** The official v5 samples use `Awake()` for `StoreController` setup and event subscription. This ensures IAP is initialized before other `Start()` methods that may depend on it.
|
|
154
|
+
|
|
155
|
+
## Minimal v5 Example
|
|
156
|
+
|
|
157
|
+
```csharp
|
|
158
|
+
StoreController m_StoreController;
|
|
159
|
+
|
|
160
|
+
async void Awake()
|
|
161
|
+
{
|
|
162
|
+
m_StoreController = UnityIAPServices.StoreController();
|
|
163
|
+
|
|
164
|
+
// Subscribe to ALL events BEFORE Connect — pending purchases may fire on reconnect
|
|
165
|
+
m_StoreController.OnPurchasePending += (order) =>
|
|
166
|
+
{
|
|
167
|
+
var product = order.CartOrdered.Items().FirstOrDefault()?.Product;
|
|
168
|
+
GrantContent(product);
|
|
169
|
+
m_StoreController.ConfirmPurchase(order);
|
|
170
|
+
};
|
|
171
|
+
m_StoreController.OnPurchaseConfirmed += (order) =>
|
|
172
|
+
{
|
|
173
|
+
switch (order)
|
|
174
|
+
{
|
|
175
|
+
case ConfirmedOrder: Debug.Log("Purchase confirmed"); break;
|
|
176
|
+
case FailedOrder failed: Debug.LogError($"Confirmation failed: {failed.FailureReason}"); break;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
m_StoreController.OnPurchaseFailed += (failed) => Debug.LogError($"{failed.FailureReason} - {failed.Details}");
|
|
180
|
+
m_StoreController.OnPurchaseDeferred += (deferred) => Debug.Log("Purchase deferred (e.g., Ask-to-Buy)");
|
|
181
|
+
|
|
182
|
+
m_StoreController.OnStoreConnected += OnStoreConnected;
|
|
183
|
+
m_StoreController.OnStoreDisconnected += (failure) => Debug.LogError($"Store disconnected: {failure.Message}");
|
|
184
|
+
|
|
185
|
+
await m_StoreController.Connect();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
void OnStoreConnected()
|
|
189
|
+
{
|
|
190
|
+
var products = new List<ProductDefinition>
|
|
191
|
+
{
|
|
192
|
+
new ProductDefinition("com.mygame.coins100", ProductType.Consumable)
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
m_StoreController.OnProductsFetched += (fetched) => Debug.Log("Products ready");
|
|
196
|
+
m_StoreController.OnProductsFetchFailed += (failure) => Debug.LogError($"Product fetch failed: {failure.FailureReason}");
|
|
197
|
+
m_StoreController.FetchProducts(products);
|
|
198
|
+
|
|
199
|
+
// Restore any pending/unfinished purchases from the platform store
|
|
200
|
+
m_StoreController.OnPurchasesFetched += (orders) => Debug.Log($"Restored {orders.PendingOrders.Count} pending purchases");
|
|
201
|
+
m_StoreController.OnPurchasesFetchFailed += (failure) => Debug.LogError($"Purchase fetch failed: {failure.Message}");
|
|
202
|
+
m_StoreController.FetchPurchases();
|
|
203
|
+
}
|
|
204
|
+
```
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Add Unity IAP 5 to a Project With No IAP
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Step 1 — Package Installation](#step-1--package-installation)
|
|
6
|
+
- [Step 2 — Project Scan](#step-2--project-scan)
|
|
7
|
+
- [Step 3 — Product Discovery](#step-3--product-discovery)
|
|
8
|
+
- [Step 4 — IAPManager Architecture](#step-4--iapmanager-architecture)
|
|
9
|
+
- [Step 5 — Purchase Handling Contract](#step-5--purchase-handling-contract)
|
|
10
|
+
- [Step 6 — Product Type Behavior Rules](#step-6--product-type-behavior-rules)
|
|
11
|
+
- [Step 7 — Cloud Save Integration](#step-7--cloud-save-integration)
|
|
12
|
+
- [Step 8 — UI Integration](#step-8--ui-integration)
|
|
13
|
+
- [Step 9 — Verification Report](#step-9--verification-report)
|
|
14
|
+
|
|
15
|
+
Use this reference when the project has no existing in-app purchase implementation and needs Unity IAP 5 added from scratch.
|
|
16
|
+
|
|
17
|
+
## Step 1 — Package Installation
|
|
18
|
+
|
|
19
|
+
Check `Packages/manifest.json` for `com.unity.purchasing`:
|
|
20
|
+
|
|
21
|
+
- **If absent:** Install via **Window > Package Manager > Unity Registry > In App Purchasing**. Do not edit `manifest.json` directly unless the user explicitly allows it. Confirm installation before proceeding.
|
|
22
|
+
- **If present but below v5.0:** Instruct the user to upgrade via Package Manager to the latest stable v5 release. Do not proceed until the upgrade is confirmed.
|
|
23
|
+
- **If v5.0+ is already present:** Note the exact version and proceed.
|
|
24
|
+
|
|
25
|
+
Always use the latest stable v5 release unless the user specifies a version. Never downgrade an existing package.
|
|
26
|
+
|
|
27
|
+
## Step 2 — Project Scan
|
|
28
|
+
|
|
29
|
+
### 2a — Code scan
|
|
30
|
+
|
|
31
|
+
Search `Assets/**/*.cs` for any existing IAP signals:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
ProductType|ProductDefinition|StoreController|IAPButton|CodelessIAP
|
|
35
|
+
ProcessPurchase|PendingOrder|DeferredOrder|ConfirmPurchase|FetchPurchases
|
|
36
|
+
IStoreListener|UnityPurchasing\.Initialize|ConfigurationBuilder
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2b — Inventory and economy scan
|
|
40
|
+
|
|
41
|
+
Search `Assets/**/*.cs` for terms that indicate what needs to be credited after purchase:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
\bcoins\b|\bgems\b|\blives\b|\binventory\b|\bcurrency\b
|
|
45
|
+
PlayerData|SaveAsync|CloudSave|SaveDataAsync|SaveGame
|
|
46
|
+
Economy
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2c — Shop UI scan
|
|
50
|
+
|
|
51
|
+
Search `Assets/**/*.unity`, `*.prefab`, `*.asset` for shop-related GameObjects and scripts:
|
|
52
|
+
```
|
|
53
|
+
Shop|Store|Purchase|Buy|IAP|Product|Monetiz
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Collect: scene names, prefab paths, button component names, and any serialized product ID strings.
|
|
57
|
+
|
|
58
|
+
## Step 3 — Product Discovery
|
|
59
|
+
|
|
60
|
+
### If product definitions are already found (from Step 2a/2b/2c)
|
|
61
|
+
|
|
62
|
+
Use them. Confirm types with the user if ambiguous.
|
|
63
|
+
|
|
64
|
+
### If no product definitions exist
|
|
65
|
+
|
|
66
|
+
**Stop and ask:**
|
|
67
|
+
|
|
68
|
+
> "Please provide the first IAP product ID and type — for example `com.mygame.coins100` as Consumable — and tell me which inventory field, currency, or item should be credited after purchase."
|
|
69
|
+
|
|
70
|
+
- If the user provides only a product ID with no type, **default to Consumable** but state the assumption explicitly before proceeding.
|
|
71
|
+
- Collect all products before writing any code. For each product record: `productId`, `ProductType`, reward target (field name / method name / amount).
|
|
72
|
+
|
|
73
|
+
## Step 4 — IAPManager Architecture
|
|
74
|
+
|
|
75
|
+
### Match the project's existing patterns
|
|
76
|
+
|
|
77
|
+
Before generating code:
|
|
78
|
+
- Check whether the project uses MonoBehaviour singletons, ScriptableObject services, or dependency injection.
|
|
79
|
+
- Check the namespace convention used in `Assets/Scripts/`.
|
|
80
|
+
- Prefer the pattern already in use rather than introducing a new one.
|
|
81
|
+
|
|
82
|
+
### IAPManager responsibilities
|
|
83
|
+
|
|
84
|
+
Create a single `IAPManager` (MonoBehaviour singleton or service, matching project pattern) that:
|
|
85
|
+
|
|
86
|
+
- Holds the `StoreController` instance.
|
|
87
|
+
- Exposes `Buy(string productId)`.
|
|
88
|
+
- Exposes `RestorePurchases()` — **only** if the product list contains `NonConsumable` or `Subscription` types.
|
|
89
|
+
- Fires UI-friendly events or callbacks for: `OnInitialized`, `OnProductsLoaded`, `OnPurchaseSuccess`, `OnPurchaseFailed`, `OnPurchaseDeferred`.
|
|
90
|
+
- Initializes once in `Awake()` — see the **Initialization Flow** section in SKILL.md for the exact event subscription and `Connect()` ordering rules.
|
|
91
|
+
- Registers all products from a single authoritative product list (not scattered across UI handlers).
|
|
92
|
+
|
|
93
|
+
### Product catalog
|
|
94
|
+
|
|
95
|
+
Define products in one place — a `ScriptableObject`, a plain `List<ProductDefinition>`, or a constants class — not inside button click handlers. Wire button click handlers to `IAPManager.Buy(productId)` using the catalog, not hardcoded strings.
|
|
96
|
+
|
|
97
|
+
## Step 5 — Purchase Handling Contract
|
|
98
|
+
|
|
99
|
+
For API mechanics (two-step flow, event names, `ConfirmPurchase` signature) see the **Two-Step Purchase Flow** and **Required Event Subscriptions** sections in SKILL.md. This section covers only the grant-and-save contract that is specific to this path.
|
|
100
|
+
|
|
101
|
+
### PendingOrder — the save-before-confirm rule
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
OnPurchasePending fires
|
|
105
|
+
→ grant reward to inventory / currency / entitlement
|
|
106
|
+
→ save player data (see Cloud Save Integration below)
|
|
107
|
+
→ ONLY IF save succeeds: call ConfirmPurchase(pendingOrder)
|
|
108
|
+
→ IF save fails: do NOT confirm — store will re-deliver on next launch
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Never call `ConfirmPurchase` before the save completes. An unconfirmed purchase is safe — it re-delivers. A confirmed purchase that was never saved is a lost reward.
|
|
112
|
+
|
|
113
|
+
### Duplicate grant prevention
|
|
114
|
+
|
|
115
|
+
`OnPurchasePending` may fire more than once for the same purchase (app restart before confirmation). Track processed order IDs (e.g., in Cloud Save or a local ledger) and skip grant if the order ID was already processed.
|
|
116
|
+
|
|
117
|
+
### DeferredOrder
|
|
118
|
+
|
|
119
|
+
Do not grant anything. Fire `OnPurchaseDeferred` event to update UI ("Purchase pending approval"). Wait for `OnPurchasePending` when the purchase is approved.
|
|
120
|
+
|
|
121
|
+
### FailedOrder
|
|
122
|
+
|
|
123
|
+
Do not grant anything. Fire `OnPurchaseFailed` with the reason. See **Failure Description Property Names** in SKILL.md for the correct property names per type.
|
|
124
|
+
|
|
125
|
+
## Step 6 — Product Type Behavior Rules
|
|
126
|
+
|
|
127
|
+
### Consumable
|
|
128
|
+
|
|
129
|
+
- Credit inventory or currency after purchase.
|
|
130
|
+
- Persist the credited state in save data before confirming.
|
|
131
|
+
- **Do not restore** old consumable orders — confirmed consumables are not returned by `FetchPurchases` and must not be re-granted.
|
|
132
|
+
- Track consumable grants yourself (order ID ledger in Cloud Save or local save).
|
|
133
|
+
|
|
134
|
+
### NonConsumable
|
|
135
|
+
|
|
136
|
+
- Unlock a durable entitlement (feature flag, item ownership).
|
|
137
|
+
- Include `RestorePurchases()` — required on iOS, good practice on Android.
|
|
138
|
+
- Re-apply the entitlement on app startup: call `store.FetchPurchases()` and re-check ownership in `OnPurchasesFetched`, or use `store.CheckEntitlement(product)`. See **Entitlement Checking** and **Fetch Existing Purchases** in SKILL.md.
|
|
139
|
+
|
|
140
|
+
### Subscription
|
|
141
|
+
|
|
142
|
+
- Restore or check subscription state on app startup, not only at purchase time. Subscriptions can expire or be cancelled externally.
|
|
143
|
+
- Use `store.CheckEntitlement(product)` or inspect `OnPurchasesFetched` results to update active/expired/unknown state.
|
|
144
|
+
- See **Subscription Info** in SKILL.md for the correct access path (`order.Info.PurchasedProductInfo`, `IsSubscribed() == Result.True`).
|
|
145
|
+
|
|
146
|
+
## Step 7 — Cloud Save Integration
|
|
147
|
+
|
|
148
|
+
### Detect the existing save system first
|
|
149
|
+
|
|
150
|
+
Search for any of these patterns before writing save code:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
\.SaveAsync\(\)|CloudSaveService\.Instance
|
|
154
|
+
SaveGame\(|SaveDataAsync\(|PlayerPrefs\.SetInt|JsonUtility\.ToJson|JsonConvert\.Serialize
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Rules
|
|
158
|
+
|
|
159
|
+
- **Use the existing save abstraction** — do not create a new save system.
|
|
160
|
+
- Prefer methods already in use: `CloudSaveService.Instance.Data.Player.SaveAsync()`, `SaveGame()`, `SaveDataAsync()`, or equivalent.
|
|
161
|
+
- If no save system exists, use `PlayerPrefs` as a minimal fallback and document it as a TODO for the developer to upgrade.
|
|
162
|
+
- Save **must complete before `ConfirmPurchase`** is called (see Step 5).
|
|
163
|
+
|
|
164
|
+
## Step 8 — UI Integration
|
|
165
|
+
|
|
166
|
+
### Wiring
|
|
167
|
+
|
|
168
|
+
- Wire existing shop buttons to `IAPManager.Buy(productId)` — do not embed product IDs in button click handlers directly.
|
|
169
|
+
- Subscribe to `IAPManager` events in the shop UI script to drive state changes.
|
|
170
|
+
|
|
171
|
+
### States to handle in UI
|
|
172
|
+
|
|
173
|
+
| State | Trigger | UI action |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| Initializing | Before `OnInitialized` | Disable buy buttons or show spinner |
|
|
176
|
+
| Products loaded | `OnProductsLoaded` | Display localized price from `product.metadata.localizedPriceString` |
|
|
177
|
+
| Product unavailable | Product missing from `OnProductsFetched` result | Hide or grey out the button |
|
|
178
|
+
| Purchase pending | `PurchaseProduct()` called | Disable button, show loading state |
|
|
179
|
+
| Purchase deferred | `OnPurchaseDeferred` | Show "pending approval" message |
|
|
180
|
+
| Purchase success | `OnPurchaseSuccess` | Show confirmation, update inventory display |
|
|
181
|
+
| Purchase failed | `OnPurchaseFailed` | Show error message, re-enable button |
|
|
182
|
+
|
|
183
|
+
### Restore button
|
|
184
|
+
|
|
185
|
+
Add a "Restore Purchases" button **only** if the product list contains `NonConsumable` or `Subscription` products. Required on iOS. Wire to `IAPManager.RestorePurchases()`.
|
|
186
|
+
|
|
187
|
+
## Step 9 — Verification Report
|
|
188
|
+
|
|
189
|
+
After applying changes, produce a report with these sections:
|
|
190
|
+
|
|
191
|
+
1. **Files changed** — list with nature of each change
|
|
192
|
+
2. **Product IDs and types** — final catalog
|
|
193
|
+
3. **Reward mapping** — product ID → field/method credited
|
|
194
|
+
4. **Save behavior** — which save method is called, when
|
|
195
|
+
5. **Restore behavior** — which products are restorable, how
|
|
196
|
+
6. **Pending / deferred handling** — confirmation of save-before-confirm and deferred UI
|
|
197
|
+
7. **Duplicate grant prevention** — how order IDs are tracked
|
|
198
|
+
8. **Manual steps still required** — Unity Editor steps (Receipt Validation Obfuscator if using local Google Play validation), App Store Connect / Play Console product setup, sandbox testing accounts
|