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,331 @@
|
|
|
1
|
+
# Editing `IAPProductCatalog.json`
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Quick reference — minimal safe edit](#quick-reference--minimal-safe-edit)
|
|
6
|
+
- [File location](#file-location)
|
|
7
|
+
- [Schema](#schema)
|
|
8
|
+
- [Serialization rules](#serialization-rules)
|
|
9
|
+
- [JsonUtility round-trip](#jsonutility-round-trip)
|
|
10
|
+
- [The `Price.data` decimal array](#the-pricedata-decimal-array)
|
|
11
|
+
- [Enum integers](#enum-integers)
|
|
12
|
+
- [Canonical store keys](#canonical-store-keys)
|
|
13
|
+
- [Non-ASCII characters in descriptions](#non-ascii-characters-in-descriptions)
|
|
14
|
+
- [Validation — run before saving](#validation--run-before-saving)
|
|
15
|
+
- [Post-edit refresh](#post-edit-refresh)
|
|
16
|
+
- [When to escalate to the Editor window](#when-to-escalate-to-the-editor-window)
|
|
17
|
+
- [Catalog as part of Codeless IAP](#catalog-as-part-of-codeless-iap)
|
|
18
|
+
- [Codeless vs scripted — which to use](#codeless-vs-scripted--which-to-use)
|
|
19
|
+
- [Auto-init race condition](#auto-init-race-condition)
|
|
20
|
+
- [Pushing the catalog to the storefronts](#pushing-the-catalog-to-the-storefronts)
|
|
21
|
+
- [Detection checklist](#detection-checklist)
|
|
22
|
+
|
|
23
|
+
`Assets/Resources/IAPProductCatalog.json` is a Unity-managed JSON asset deserialized by `JsonUtility.FromJson<ProductCatalog>` (`Runtime/Purchasing/Extension/ProductCatalog.cs`). It can be edited safely outside the Editor as long as the serialization rules below are respected.
|
|
24
|
+
|
|
25
|
+
## Quick reference — minimal safe edit
|
|
26
|
+
|
|
27
|
+
1. **Read** the file as raw text. It is canonical JSON, single-line.
|
|
28
|
+
2. **Parse** it into a dict/object.
|
|
29
|
+
3. **Modify** fields using the schema and serialization rules below.
|
|
30
|
+
4. **Write back** as JSON. Field name spelling and casing must match exactly; preserving order is not required.
|
|
31
|
+
5. **Refresh** so the Editor / runtime sees the change (see [Post-edit refresh](#post-edit-refresh)).
|
|
32
|
+
|
|
33
|
+
Adding a new $1.99 consumable `gems_50` in one operation:
|
|
34
|
+
|
|
35
|
+
```jsonc
|
|
36
|
+
{
|
|
37
|
+
"id": "gems_50",
|
|
38
|
+
"type": 0,
|
|
39
|
+
"storeIDs": [],
|
|
40
|
+
"defaultDescription": { "googleLocale": 21, "title": "50 Gems", "description": "A small pouch of gems." },
|
|
41
|
+
"screenshotPath": "",
|
|
42
|
+
"applePriceTier": 0,
|
|
43
|
+
"googlePrice": { "data": [199, 0, 0, 131072], "num": 1.99 },
|
|
44
|
+
"pricingTemplateID": "",
|
|
45
|
+
"descriptions": [],
|
|
46
|
+
"payouts": []
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Append to the `products` array, save, then trigger a refresh.
|
|
51
|
+
|
|
52
|
+
## File location
|
|
53
|
+
|
|
54
|
+
- **Current:** `Assets/Resources/IAPProductCatalog.json` (constant `ProductCatalog.kCatalogPath`)
|
|
55
|
+
- **Legacy:** `Assets/Plugins/UnityPurchasing/Resources/IAPProductCatalog.json` (constant `ProductCatalog.kPrevCatalogPath`) — auto-migrated on Editor load via `ProductCatalogEditor.MigrateProductCatalog()`. If both exist, the current path wins.
|
|
56
|
+
|
|
57
|
+
`Resources.Load("IAPProductCatalog")` deserializes it at runtime via `ProductCatalogImpl.LoadDefaultCatalog()`. The asset must remain under a `Resources/` directory for runtime load to succeed.
|
|
58
|
+
|
|
59
|
+
## Schema
|
|
60
|
+
|
|
61
|
+
```jsonc
|
|
62
|
+
{
|
|
63
|
+
"appleSKU": "", // app-level Apple SKU (Apple XML exporter)
|
|
64
|
+
"appleTeamID": "", // Apple team ID (Apple XML exporter)
|
|
65
|
+
"enableCodelessAutoInitialization": true, // auto-init Unity IAP at runtime
|
|
66
|
+
"enableUnityGamingServicesAutoInitialization": false,
|
|
67
|
+
"products": [
|
|
68
|
+
{
|
|
69
|
+
"id": "gold_100", // canonical Unity SKU — required, non-empty, unique
|
|
70
|
+
"type": 0, // ProductType int (see Enum integers)
|
|
71
|
+
"storeIDs": [ // per-store override SKUs
|
|
72
|
+
{ "store": "AppleAppStore", "id": "com.example.gold100" }
|
|
73
|
+
],
|
|
74
|
+
"defaultDescription": { // required for export
|
|
75
|
+
"googleLocale": 21, // TranslationLocale int (en_US = 21)
|
|
76
|
+
"title": "",
|
|
77
|
+
"description": ""
|
|
78
|
+
},
|
|
79
|
+
"screenshotPath": "", // Apple screenshot path (optional)
|
|
80
|
+
"applePriceTier": 0, // Apple price tier (Apple XML exporter)
|
|
81
|
+
"googlePrice": { "data": [99,0,0,131072], "num": 0.99 }, // see Price.data
|
|
82
|
+
"pricingTemplateID": "", // Google Play pricing template
|
|
83
|
+
"descriptions": [ // additional locale variants
|
|
84
|
+
{ "googleLocale": 30, "title": "...", "description": "..." }
|
|
85
|
+
],
|
|
86
|
+
"payouts": [ // optional grant metadata
|
|
87
|
+
{ "t": "Currency", "st": "Gold", "q": 100, "d": "" }
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Per-product required fields for the runtime to accept the product: `id` (non-empty, trimmed). Everything else is optional at runtime; exporters and the Editor window enforce stricter rules (see [Validation](#validation--run-before-saving)).
|
|
95
|
+
|
|
96
|
+
## Serialization rules
|
|
97
|
+
|
|
98
|
+
### JsonUtility round-trip
|
|
99
|
+
|
|
100
|
+
- Field names are **case-sensitive** and must match the `[SerializeField]` field names in `ProductCatalog.cs` exactly. The visible field names you see in the JSON (e.g. `title`, `description` inside `LocalizedProductDescription`) are the **backing field** names, not the public C# property names (`Title`, `Description`).
|
|
101
|
+
- Unknown fields are silently dropped on the next save.
|
|
102
|
+
- Missing fields deserialize to default values (`0`, `""`, empty list, etc.) — safe to omit defaults, but the Editor window writes them out explicitly.
|
|
103
|
+
- The file is normally a single line — pretty-printing is fine, but JsonUtility re-emits as one line on the next Editor save.
|
|
104
|
+
- Do not change the top-level key set. `appleSKU`, `appleTeamID`, `enableCodelessAutoInitialization`, `enableUnityGamingServicesAutoInitialization`, and `products` are all `[SerializeField]`-bound — removing keys is fine (they default), but renaming silently loses data.
|
|
105
|
+
|
|
106
|
+
### The `Price.data` decimal array
|
|
107
|
+
|
|
108
|
+
`Price` is the trickiest field. It serializes a `decimal` via two mirror fields and **`data` is authoritative on read**:
|
|
109
|
+
|
|
110
|
+
```csharp
|
|
111
|
+
public void OnBeforeSerialize() {
|
|
112
|
+
data = decimal.GetBits(value); // int[4]: [low, mid, high, flags]
|
|
113
|
+
num = decimal.ToDouble(value);
|
|
114
|
+
}
|
|
115
|
+
public void OnAfterDeserialize() {
|
|
116
|
+
if (data != null && data.Length == 4)
|
|
117
|
+
value = new decimal(data); // num is ignored on read
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`data` is the `decimal.GetBits` representation: `[low32 mantissa, mid32 mantissa, high32 mantissa, flags]`.
|
|
122
|
+
|
|
123
|
+
The `flags` int encodes scale (decimal places) and sign:
|
|
124
|
+
- Bits 16–23: scale (0–28)
|
|
125
|
+
- Bit 31: sign (0 = positive, 1 = negative; negative prices are never valid here)
|
|
126
|
+
- All other bits: unused, must be 0
|
|
127
|
+
|
|
128
|
+
For scale 2 (cents-style prices): `flags = 0x00020000 = 131072`
|
|
129
|
+
For scale 0 (yen / whole-unit prices): `flags = 0`
|
|
130
|
+
|
|
131
|
+
| Price (display) | Mantissa | Scale | `data` |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| 0.99 | 99 | 2 | `[99, 0, 0, 131072]` |
|
|
134
|
+
| 1.99 | 199 | 2 | `[199, 0, 0, 131072]` |
|
|
135
|
+
| 2.99 | 299 | 2 | `[299, 0, 0, 131072]` |
|
|
136
|
+
| 4.99 | 499 | 2 | `[499, 0, 0, 131072]` |
|
|
137
|
+
| 9.99 | 999 | 2 | `[999, 0, 0, 131072]` |
|
|
138
|
+
| 19.99 | 1999 | 2 | `[1999, 0, 0, 131072]` |
|
|
139
|
+
| 99.99 | 9999 | 2 | `[9999, 0, 0, 131072]` |
|
|
140
|
+
| 1000 (¥, ₩) | 1000 | 0 | `[1000, 0, 0, 0]` |
|
|
141
|
+
|
|
142
|
+
Formula for arbitrary positive prices with up to 2³¹−1 unscaled units:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
unscaled = round(price * 10^scale) // e.g. 2.99 with scale=2 → 299
|
|
146
|
+
data = [unscaled, 0, 0, scale << 16]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Keep `num` in sync with the display price even though it's not read — the Editor reads it diagnostically and the file diffs more readably.
|
|
150
|
+
|
|
151
|
+
For prices larger than ~$21M (unscaled > 2³¹−1), the high two ints carry the overflow — at that point invoke Unity in batchmode and let `Price.OnBeforeSerialize` do the conversion, rather than constructing the bits by hand.
|
|
152
|
+
|
|
153
|
+
### Enum integers
|
|
154
|
+
|
|
155
|
+
`ProductType` (`type` field):
|
|
156
|
+
|
|
157
|
+
| Int | Enum | Use for |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| 0 | `Consumable` | Coins, gems, lives, ammo — granted then consumed |
|
|
160
|
+
| 1 | `NonConsumable` | One-time unlocks, remove-ads, character packs |
|
|
161
|
+
| 2 | `Subscription` | Recurring entitlements |
|
|
162
|
+
|
|
163
|
+
`TranslationLocale` (`googleLocale` field): the int is the zero-based index into the enum declared in `Runtime/Purchasing/Extension/ProductCatalog.cs`. Common values:
|
|
164
|
+
|
|
165
|
+
| Int | Locale |
|
|
166
|
+
|---|---|
|
|
167
|
+
| 13 | `zh_CN` |
|
|
168
|
+
| 14 | `zh_TW` |
|
|
169
|
+
| 17 | `da_DK` |
|
|
170
|
+
| 18 | `nl_NL` |
|
|
171
|
+
| 21 | `en_US` (default for new descriptions) |
|
|
172
|
+
| 22 | `en_GB` |
|
|
173
|
+
| 30 | `fr_FR` |
|
|
174
|
+
| 33 | `de_DE` |
|
|
175
|
+
| 41 | `it_IT` |
|
|
176
|
+
| 42 | `ja_JP` |
|
|
177
|
+
| 46 | `ko_KR` |
|
|
178
|
+
| 63 | `pl_PL` |
|
|
179
|
+
| 64 | `pt_BR` |
|
|
180
|
+
| 69 | `ru_RU` |
|
|
181
|
+
| 75 | `es_ES` |
|
|
182
|
+
|
|
183
|
+
For anything outside this list, count from the top of the `TranslationLocale` enum in `ProductCatalog.cs` — the order is the index.
|
|
184
|
+
|
|
185
|
+
`ProductCatalogPayoutType` (`payouts[].t` field): **serialized as a string**, not an int. Valid values: `"Other"`, `"Currency"`, `"Item"`, `"Resource"`.
|
|
186
|
+
|
|
187
|
+
### Canonical store keys
|
|
188
|
+
|
|
189
|
+
`storeIDs[].store` must be one of the keys validated by `ProductCatalogEditor.kStoreKeys`:
|
|
190
|
+
|
|
191
|
+
- `AppleAppStore`
|
|
192
|
+
- `GooglePlay`
|
|
193
|
+
- `MacAppStore`
|
|
194
|
+
|
|
195
|
+
Using `"Apple"`, `"apple"`, `"google"`, etc. won't crash but the override will not be picked up by the runtime store routing. An empty `storeIDs` array means the product's `id` is used as the platform SKU on every store.
|
|
196
|
+
|
|
197
|
+
### Non-ASCII characters in descriptions
|
|
198
|
+
|
|
199
|
+
`LocalizedProductDescription.Title` / `Description` setters encode any character with code point > 127 as `\uXXXX` (`EncodeNonLatinCharacters` in `ProductCatalog.cs`). The getter regex-decodes both forms. So both representations work on read:
|
|
200
|
+
|
|
201
|
+
```jsonc
|
|
202
|
+
{ "title": "Caf\\u00e9 Pack" } // Editor-written form
|
|
203
|
+
{ "title": "Café Pack" } // raw UTF-8 — also accepted on read
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Prefer the `\uXXXX` form** when writing programmatically, to match what the Editor will round-trip the file into on the next save. Mixed forms in one file are fine but the next Editor save normalizes everything to escaped form.
|
|
207
|
+
|
|
208
|
+
Subtype `payouts[].st` has a 64-char max (`ProductCatalogPayout.MaxSubtypeLength`). Subtype `payouts[].d` (data) has a 1024-char max (`MaxDataLength`).
|
|
209
|
+
|
|
210
|
+
## Validation — run before saving
|
|
211
|
+
|
|
212
|
+
These mirror the Editor window's validation. Verify each before writing:
|
|
213
|
+
|
|
214
|
+
**Per product:**
|
|
215
|
+
- `id` is non-empty after trim. (`ProductCatalog.allValidProducts` filters on this.)
|
|
216
|
+
- `id` is unique across all products in the array (the Editor flags duplicates).
|
|
217
|
+
- `type` ∈ `{0, 1, 2}`.
|
|
218
|
+
- Every `defaultDescription.googleLocale` and `descriptions[].googleLocale` is a valid int index into `TranslationLocale`.
|
|
219
|
+
- `storeIDs[].store` ∈ canonical store keys.
|
|
220
|
+
- If `googlePrice.data` is present, it is a 4-element int array; sign bit (bit 31 of `data[3]`) is 0.
|
|
221
|
+
|
|
222
|
+
**Per-exporter (only if export is intended):**
|
|
223
|
+
|
|
224
|
+
- **Apple XML** (`AppleXMLProductCatalogExporter.Validate`):
|
|
225
|
+
- Catalog-level: `appleSKU` non-empty, `appleTeamID` non-empty, no duplicate product IDs, no duplicate Apple store IDs, no duplicate runtime IDs.
|
|
226
|
+
- Per item: `id` non-empty, `defaultDescription.Title` non-empty, `defaultDescription.Description` non-empty, **`screenshotPath` non-empty (required, not optional)**.
|
|
227
|
+
- `applePriceTier` is written to the XML (`<wholesale_price_tier>`) but not validated — any int is accepted.
|
|
228
|
+
- **Google CSV** (`GooglePlayProductCatalogExporter.Validate`):
|
|
229
|
+
- Catalog-level: no duplicate product IDs, no duplicate Google store IDs, no duplicate runtime IDs.
|
|
230
|
+
- Per item: `id` non-empty AND must start with a lowercase letter or digit AND contain only `a-z`, `0-9`, `_`, `.` (same rule applies to `storeIDs[].id` for the `GooglePlay` override).
|
|
231
|
+
- Description rules (apply to `defaultDescription` and every entry in `descriptions`):
|
|
232
|
+
- `Title` non-empty; ≤ 55 chars (error if longer); warning if > 25 chars.
|
|
233
|
+
- `Description` non-empty; ≤ 80 chars (error if longer).
|
|
234
|
+
- Price: either `googlePrice.value` ≠ 0 (i.e. non-zero `data` mantissa) **or** `pricingTemplateID` non-empty.
|
|
235
|
+
|
|
236
|
+
**Top-level:**
|
|
237
|
+
- `enableCodelessAutoInitialization` is a bool, not 0/1.
|
|
238
|
+
- If the catalog ends up empty (no products with non-empty `id`), `CodelessIAPStoreListener.InitializeCodelessPurchasingOnLoad` short-circuits — flipping the auto-init flag in that state has no effect until a product is added.
|
|
239
|
+
|
|
240
|
+
## Post-edit refresh
|
|
241
|
+
|
|
242
|
+
The Unity Editor caches `IAPProductCatalog` as a `TextAsset` keyed by Resources path. After writing the JSON externally:
|
|
243
|
+
|
|
244
|
+
- **Editor open, not in Play Mode:** Unity detects external changes on next focus and re-imports the asset automatically (`AssetDatabase`'s file watcher). If you want to force it from the Editor side, run **Assets → Refresh** (Ctrl/Cmd+R). The next `Resources.Load("IAPProductCatalog")` call will see the new contents.
|
|
245
|
+
- **Editor open, in Play Mode:** the runtime `TextAsset` was loaded at play-start and is cached. Changes won't be picked up until play mode is restarted (or you re-call `Resources.Load` after `AssetDatabase.ImportAsset` to bypass the cache).
|
|
246
|
+
- **Editor closed:** edits are picked up next time the Editor opens.
|
|
247
|
+
- **Player build:** the catalog is baked into the build at build-time. Edits after build do nothing — rebuild required.
|
|
248
|
+
|
|
249
|
+
For batchmode-driven workflows that need a guaranteed refresh:
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
"<unity>/Unity.exe" -batchmode -quit -projectPath "<path>" -logFile -
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Plain batchmode without `-executeMethod` is enough to trigger asset import on startup. The next Editor session will see the change.
|
|
256
|
+
|
|
257
|
+
## When to escalate to the Editor window
|
|
258
|
+
|
|
259
|
+
Hand the user back to the IAP Catalog window (Services → In-App Purchasing → IAP Catalog…) when:
|
|
260
|
+
|
|
261
|
+
- The decimal price is larger than ~$21M (would need the high mantissa ints — let `Price.OnBeforeSerialize` do it).
|
|
262
|
+
- You need to set up many `payouts` with custom subtypes — the Editor has inline validation on lengths.
|
|
263
|
+
- The user wants to use **App Store Export** (Apple XML / Google CSV) — that flow is a Unity dialog with its own validation results panel.
|
|
264
|
+
- You hit a JsonUtility round-trip error you can't diagnose from the schema rules above.
|
|
265
|
+
|
|
266
|
+
For everything else — adding/removing products, changing prices in normal ranges, toggling auto-init flags, updating descriptions, adding store ID overrides — direct file edits are safe and faster than driving the Editor UI.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Catalog as part of Codeless IAP
|
|
271
|
+
|
|
272
|
+
The catalog file plays two distinct roles in the package. Knowing which one applies in the current project changes how edits are made and what side-effects to expect.
|
|
273
|
+
|
|
274
|
+
### Codeless vs scripted — which to use
|
|
275
|
+
|
|
276
|
+
| Use case | Path |
|
|
277
|
+
|---|---|
|
|
278
|
+
| Ship a buy button with zero C# | **Codeless** — catalog + `CodelessIAPButton` |
|
|
279
|
+
| Custom UI states, server validation, granular control of the purchase flow | **Scripted** — `StoreController` + an `IAPManager` (see [path-add-iap-to-new-project.md](path-add-iap-to-new-project.md)) |
|
|
280
|
+
| Catalog-managed product list + scripted purchase flow | **Mixed** — catalog drives `CodelessIAPStoreListener` for product registration, scripted code subscribes to its events |
|
|
281
|
+
| Export products to Apple Application Loader / Play Console | Codeless catalog — the only built-in export path |
|
|
282
|
+
|
|
283
|
+
Default recommendation when starting a new project from scratch: scripted IAP. Codeless is convenient for prototypes but couples the project to a global singleton (`CodelessIAPStoreListener.Instance`) and hides the init/purchase flow most production games eventually need to customize.
|
|
284
|
+
|
|
285
|
+
### Auto-init race condition
|
|
286
|
+
|
|
287
|
+
If the project has **both** a non-empty `IAPProductCatalog.json` with `enableCodelessAutoInitialization: true` **and** a scripted `StoreController` initialized in user code, two init paths race for the same native store. Symptoms:
|
|
288
|
+
|
|
289
|
+
- Duplicate `OnPurchasePending` callbacks (one per init's listener set).
|
|
290
|
+
- "Store already connected" warnings on `Connect()`.
|
|
291
|
+
- Unpredictable which init's product list wins.
|
|
292
|
+
|
|
293
|
+
Mitigations (pick one):
|
|
294
|
+
|
|
295
|
+
1. **Keep scripted, disable codeless:** set `"enableCodelessAutoInitialization": false` in the catalog JSON. The catalog stays available for `ProductCatalog.LoadDefaultCatalog()` calls.
|
|
296
|
+
2. **Keep codeless, remove scripted init:** delete the user's `StoreController` setup; rely on `CodelessIAPStoreListener.Instance`.
|
|
297
|
+
3. **Empty the catalog:** clear `products` to `[]`. `CodelessIAPStoreListener.InitializeCodelessPurchasingOnLoad` short-circuits on empty catalogs even with the flag on.
|
|
298
|
+
|
|
299
|
+
Surface this before adding scripted IAP to a project that already has a non-empty catalog.
|
|
300
|
+
|
|
301
|
+
### Pushing the catalog to the storefronts
|
|
302
|
+
|
|
303
|
+
The IAP Catalog window's **App Store Export** button opens `ProductCatalogExportWindow`, which generates bulk-import files. Nothing in the package calls App Store Connect or Play Console APIs directly — devs upload the generated files manually.
|
|
304
|
+
|
|
305
|
+
| Exporter | Output | Upload destination |
|
|
306
|
+
|---|---|---|
|
|
307
|
+
| `AppleXMLProductCatalogExporter` | Application Loader XML | App Store Connect → Transporter / Application Loader |
|
|
308
|
+
| `GooglePlayProductCatalogExporter` | CSV | Play Console → In-app products → Import |
|
|
309
|
+
|
|
310
|
+
Editing the JSON directly populates the same fields the exporters read; the validation surface in the Editor window (`ExporterValidationResults`) is the user-visible signal that fields are missing.
|
|
311
|
+
|
|
312
|
+
### Detection checklist
|
|
313
|
+
|
|
314
|
+
When triaging an IAP issue or planning changes, check in order:
|
|
315
|
+
|
|
316
|
+
1. Does `Assets/Resources/IAPProductCatalog.json` exist?
|
|
317
|
+
2. Is `enableCodelessAutoInitialization` true? (`grep enableCodelessAutoInitialization Assets/Resources/IAPProductCatalog.json`)
|
|
318
|
+
3. Are there `CodelessIAPButton` or legacy `IAPButton` components in scenes/prefabs?
|
|
319
|
+
4. Does user code construct a `StoreController` directly (or call `UnityIAPServices.StoreController(...)`)?
|
|
320
|
+
5. Does user code reference `CodelessIAPStoreListener.Instance`?
|
|
321
|
+
|
|
322
|
+
Routing the result:
|
|
323
|
+
|
|
324
|
+
| Catalog | autoInit | Scripted `StoreController` | Action |
|
|
325
|
+
|---|---|---|---|
|
|
326
|
+
| present (non-empty) | true | absent | Pure codeless — edit the catalog freely. |
|
|
327
|
+
| present (non-empty) | true | present | **Race condition** — surface to the user, apply one of the three mitigations. |
|
|
328
|
+
| present | false | present | Catalog is dormant unless `ProductCatalog.LoadDefaultCatalog()` is called from user code. Edits safe. |
|
|
329
|
+
| present (empty) | true or false | present | Codeless auto-init short-circuits on empty catalog. Treat as scripted-only. |
|
|
330
|
+
| absent | n/a | present | Standard scripted IAP. See [path-add-iap-to-new-project.md](path-add-iap-to-new-project.md). |
|
|
331
|
+
| absent | n/a | absent | No IAP — see [pre-check.md](pre-check.md) routing. |
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Adapty — Conversion Assessment and Guidance
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Trigger Phrases](#trigger-phrases)
|
|
6
|
+
- [Overview](#overview)
|
|
7
|
+
- [Step 1 — Check If Already in Observer Mode](#step-1--check-if-already-in-observer-mode)
|
|
8
|
+
- [Step 2 — Feature Support Check](#step-2--feature-support-check)
|
|
9
|
+
- [Step 3 — Native Google BillingClient Check](#step-3--native-google-billingclient-check)
|
|
10
|
+
- [Step 4 — Route to Outcome](#step-4--route-to-outcome)
|
|
11
|
+
- [Case 1 — Already in Observer Mode with Unity IAP 5](#case-1--already-in-observer-mode-with-unity-iap-5)
|
|
12
|
+
- [Case 2 — Unsupported Features Detected](#case-2--unsupported-features-detected)
|
|
13
|
+
- [Case 3 — No Blockers, Conversion Is Viable](#case-3--no-blockers-conversion-is-viable)
|
|
14
|
+
|
|
15
|
+
Use this reference when the project has Adapty (`AdaptySDK-Unity`) installed and the user wants to **replace or remove Adapty** and switch to Unity IAP 5 (`com.unity.purchasing`). This path does not cover adding Unity IAP 5 alongside Adapty for non-IAP purposes.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Trigger Phrases
|
|
20
|
+
|
|
21
|
+
- "Replace Adapty SDK with Unity IAP"
|
|
22
|
+
- "Remove Adapty and use Unity IAP"
|
|
23
|
+
- "Migrate from Adapty to Unity IAP"
|
|
24
|
+
- "Use Unity IAP 5 to handle store purchases and work with Adapty"
|
|
25
|
+
- "Make Adapty work with Unity IAP 5"
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Overview
|
|
30
|
+
|
|
31
|
+
Adapty is architecturally independent from Unity IAP — it has its own native Swift (iOS) and Kotlin (Android) SDKs that call Apple StoreKit and Google BillingClient directly. There is no simple swap. Conversion requires assessing which Adapty features the project uses and whether Unity IAP 5 can replace them.
|
|
32
|
+
|
|
33
|
+
This path always produces one of three outcomes:
|
|
34
|
+
|
|
35
|
+
| Case | Condition | Outcome |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| **1** | Already in observer mode with Unity IAP 5 | Report — no action needed |
|
|
38
|
+
| **2** | Adapty-only features are in active use | Report limitations, present two choices |
|
|
39
|
+
| **3** | No blockers — only basic purchase flow used | Present two conversion choices |
|
|
40
|
+
|
|
41
|
+
Run Steps 1–3 in order. Collect all findings before routing.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Step 1 — Check If Already in Observer Mode
|
|
46
|
+
|
|
47
|
+
### 1a — Check Unity IAP 5 is installed
|
|
48
|
+
|
|
49
|
+
Search `Packages/manifest.json` for `com.unity.purchasing` with a version matching `5\.`. Record whether it is present.
|
|
50
|
+
|
|
51
|
+
### 1b — Check Adapty observer mode configuration
|
|
52
|
+
|
|
53
|
+
Search `Assets/**/*.cs` for:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Adapty\.Activate.*observerMode|observerMode\s*=\s*true|AdaptyProfileParameters|ReportTransaction
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Also search for `AdaptyObserverModeDelegate` or any class implementing it.
|
|
60
|
+
|
|
61
|
+
If found, observer mode is active.
|
|
62
|
+
|
|
63
|
+
**If both Unity IAP 5 is installed AND observer mode is active → this is Case 1. Skip Step 2 and go directly to [Case 1](#case-1--already-in-observer-mode-with-unity-iap-5).**
|
|
64
|
+
|
|
65
|
+
Otherwise continue to Step 2.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Step 2 — Feature Support Check
|
|
70
|
+
|
|
71
|
+
Search `Assets/**/*.cs`, `Assets/**/*.prefab`, and `Assets/**/*.unity` for usage of Adapty features that have **no Unity IAP 5 equivalent**. Record every feature found.
|
|
72
|
+
|
|
73
|
+
### 2a — Remote Paywalls / Paywall Builder
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
GetPaywall|PaywallView|AdaptyUI|AdaptyPaywall|GetPaywallForDefaultAudience|ShowPaywall|PresentCodeRedemptionSheet
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Also check `Packages/manifest.json` or imported packages for `adapty-ui` or `AdaptyUI`.
|
|
80
|
+
|
|
81
|
+
**What it means:** Adapty's Paywall Builder lets you configure paywall layouts and copy remotely without an app update. Unity IAP 5 has no remote paywall system — all products and UI must be defined in code or a local catalog. **Note: Paywall Builder is also unavailable in Adapty's own Observer Mode**, so even a partial migration loses this feature.
|
|
82
|
+
|
|
83
|
+
### 2b — A/B Testing / Experiments
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
variationId|GetPaywall.*audience|LogShowPaywall|LogStartCheckout
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**What it means:** Adapty runs A/B tests on paywall layouts and pricing server-side. Unity IAP 5 has no A/B testing capability. In Adapty Observer Mode, A/B testing also requires significant additional coding and is not automatic.
|
|
90
|
+
|
|
91
|
+
### 2c — Cross-Platform Entitlement Sync
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Adapty\.Identify|Adapty\.Profile|AdaptyProfile|accessLevels|isActive
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Specifically look for `Adapty.Identify` being called with a customer user ID — this indicates the project relies on Adapty as the cross-platform entitlement source of truth (a user who buys on iOS retains access on Android). Unity IAP 5 has no cross-platform entitlement layer.
|
|
98
|
+
|
|
99
|
+
### 2d — Webhook-Driven Backend Events
|
|
100
|
+
|
|
101
|
+
Search `Assets/**/*.cs` for patterns suggesting backend subscription event handling:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
webhook|SubscriptionCancelled|BillingIssue|AccessLevelUpdated
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Also ask the user: *"Does your backend receive Adapty webhook events for subscription renewals, cancellations, or billing issues?"* If yes, flag this — Unity IAP 5 delivers no server-side lifecycle events.
|
|
108
|
+
|
|
109
|
+
### 2e — Third-Party Analytics Integrations
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
AdaptyAttributionNetwork|UpdateAttribution|SetFallbackPaywalls|Amplitude|Mixpanel|AppsFlyer|Adjust
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**What it means:** Adapty forwards purchase and paywall events to third-party analytics tools automatically. Unity IAP 5 has no such integration layer — you would need to instrument each event manually.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Step 3 — Native Google BillingClient Check
|
|
120
|
+
|
|
121
|
+
Search the following for custom native billing code alongside Adapty:
|
|
122
|
+
|
|
123
|
+
- `Assets/**/*.cs` for: `AndroidJavaObject|AndroidJavaClass|BillingClient|BillingManager|GoogleBilling`
|
|
124
|
+
- `Assets/Plugins/Android/**/*.java`, `*.kt` for: `com\.android\.billingclient`
|
|
125
|
+
|
|
126
|
+
**If found:** The project has custom native Google BillingClient code in addition to Adapty. This is unusual — flag it to the user. Native BillingClient code will conflict with Unity IAP 5 at runtime and must be removed or replaced as part of the conversion. Add it to the blockers list in Case 2 if active and not just scaffolding.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Step 4 — Route to Outcome
|
|
131
|
+
|
|
132
|
+
Evaluate findings from Steps 2 and 3:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
If native BillingClient detected (Step 3) → Case 2 (flag as additional blocker)
|
|
136
|
+
If any Step 2 feature detected → Case 2
|
|
137
|
+
If no blockers from Steps 2 or 3 → Case 3
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If multiple blockers are found, list all of them in the Case 2 report — do not stop at the first.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Case 1 — Already in Observer Mode with Unity IAP 5
|
|
145
|
+
|
|
146
|
+
**Condition:** Unity IAP 5 installed and Adapty already configured in observer mode.
|
|
147
|
+
|
|
148
|
+
Report and stop:
|
|
149
|
+
|
|
150
|
+
> "This project already has Unity IAP 5 installed and Adapty is running in observer mode. Unity IAP 5 handles purchase transactions and Adapty validates and tracks them server-side via `ReportTransaction`.
|
|
151
|
+
>
|
|
152
|
+
> If you want to remove Adapty entirely, re-run this skill and specify that you want to stop using Adapty. That will route to a full removal assessment."
|
|
153
|
+
|
|
154
|
+
Do not make any changes.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Case 2 — Unsupported Features Detected
|
|
159
|
+
|
|
160
|
+
**Condition:** One or more Adapty-only features are in active use.
|
|
161
|
+
|
|
162
|
+
Produce a blockers report listing every issue found, then present two choices:
|
|
163
|
+
|
|
164
|
+
> "The following Adapty features are in use that have no Unity IAP 5 equivalent:
|
|
165
|
+
>
|
|
166
|
+
> [List all detected blockers, e.g.:]
|
|
167
|
+
> - **Remote Paywalls / Paywall Builder** (`GetPaywall` / `AdaptyUI` detected): Unity IAP 5 has no remote paywall system. Paywall layouts and copy must be hardcoded. Note: Adapty's Paywall Builder is also unavailable in Adapty Observer Mode — switching to observer mode does not preserve this feature.
|
|
168
|
+
> - **A/B Testing** (`variationId` / `logShowPaywall` detected): Unity IAP 5 has no A/B testing. In Adapty Observer Mode, A/B testing requires significant additional manual instrumentation and is not automatic.
|
|
169
|
+
> - **Cross-platform entitlement sync** (`Adapty.Identify` detected): Unity IAP 5 has no cross-platform entitlement layer. A user who purchases on iOS will not retain access on Android without a custom server-side solution.
|
|
170
|
+
> - **Webhook-driven backend events**: Your backend appears to receive Adapty subscription events. Unity IAP 5 delivers no server-side lifecycle events — you would need to build your own subscription tracking infrastructure.
|
|
171
|
+
> - **Third-party analytics integrations**: Adapty forwards purchase events to [detected tools]. Unity IAP 5 has no integration layer — each event would need manual instrumentation.
|
|
172
|
+
>
|
|
173
|
+
> **Your options:**
|
|
174
|
+
>
|
|
175
|
+
> **(a) Stop conversion (recommended)** — Keep Adapty as the billing backend. The features in use have no Unity IAP 5 equivalent. No changes will be made.
|
|
176
|
+
>
|
|
177
|
+
> **(b) Convert to observer mode + Unity IAP 5 anyway** — Unity IAP 5 takes over purchase transactions. Adapty switches to observer mode for server-side validation and lifecycle tracking. You accept the following consequences:
|
|
178
|
+
> - Adapty Paywall Builder stops working — you must build replacement paywall UI in Unity.
|
|
179
|
+
> - [List each unsupported feature and its specific consequence.]
|
|
180
|
+
> - BillingClient Gradle conflict between Unity IAP 5 and Adapty must be resolved by excluding the conflicting dependency.
|
|
181
|
+
>
|
|
182
|
+
> Which would you like to do?"
|
|
183
|
+
|
|
184
|
+
If the user chooses **(a)**: stop, make no changes.
|
|
185
|
+
|
|
186
|
+
If the user chooses **(b)**: continue with [Case 3, Path A](#path-a--convert-to-observer-mode--unity-iap-5) but preface the report with a clear warning documenting every accepted consequence.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Case 3 — No Blockers, Conversion Is Viable
|
|
191
|
+
|
|
192
|
+
**Condition:** No Adapty-only features detected — the project uses Adapty only for basic purchase flow.
|
|
193
|
+
|
|
194
|
+
Present two choices:
|
|
195
|
+
|
|
196
|
+
> "No blockers were found. The project uses Adapty only for basic purchase initiation and receipt handling (no remote paywalls, no cross-platform entitlements, no A/B testing). Two conversion paths are available:
|
|
197
|
+
>
|
|
198
|
+
> **(a) Observer mode + Unity IAP 5** — Unity IAP 5 handles purchase transactions. Adapty stays in observer mode, receiving `ReportTransaction` calls for server-side validation and subscription lifecycle tracking. Lower risk — Adapty's subscription event webhooks and analytics continue to work.
|
|
199
|
+
>
|
|
200
|
+
> **(b) Full removal — Unity IAP 5 only** — Adapty is removed entirely. Unity IAP 5 handles all billing. You lose Adapty's server-side receipt validation, subscription lifecycle tracking, and analytics. If the project had any Adapty paywall UI, replacement purchase UI must be built in Unity. Simpler architecture, no Adapty subscription cost.
|
|
201
|
+
>
|
|
202
|
+
> Which path would you like?"
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### Path A — Convert to Observer Mode + Unity IAP 5
|
|
207
|
+
|
|
208
|
+
1. **Install Unity IAP 5** if not already present — follow Step 1 of [path-add-iap-to-new-project.md](path-add-iap-to-new-project.md).
|
|
209
|
+
|
|
210
|
+
2. **Resolve the BillingClient Gradle conflict.** Both SDKs bundle `com.android.billingclient`. Unity IAP 5 declares `com.android.billingclient:billing:9.0.0` via `Plugins/UnityPurchasing/Android/IAPResolver/IAPAndroidDependencies.cs` — do **not** add a project-wide `configurations.all { exclude group: 'com.android.billingclient' }` in `mainTemplate.gradle`, as that will strip Unity IAP's own BillingClient along with Adapty's and leave the Android build with no BillingClient at all.
|
|
211
|
+
|
|
212
|
+
Instead, remove only Adapty's copy:
|
|
213
|
+
- Locate Adapty's EDM4U dependency file (typically `Assets/Adapty/Editor/AdaptyDependencies.xml` or similar) and delete the `<androidPackage spec="com.android.billingclient:billing:..."/>` entry, **or**
|
|
214
|
+
- After Force Resolve, delete the Adapty-contributed `billing-*.aar` from `Assets/Plugins/Android/` and keep the one contributed by Unity IAP.
|
|
215
|
+
|
|
216
|
+
Then run **Assets > External Dependency Manager > Android Resolver > Delete Resolved Libraries**, followed by **Force Resolve**.
|
|
217
|
+
|
|
218
|
+
3. **Enable Adapty observer mode.** In the `Adapty.Activate` call, set `observerMode: true`:
|
|
219
|
+
|
|
220
|
+
```csharp
|
|
221
|
+
var config = new AdaptyConfig("YOUR_PUBLIC_SDK_KEY")
|
|
222
|
+
{
|
|
223
|
+
ObserverMode = true
|
|
224
|
+
};
|
|
225
|
+
Adapty.Activate(config);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
4. **Implement Unity IAP 5 purchase flow.** Follow [path-add-iap-to-new-project.md](path-add-iap-to-new-project.md) using the product catalog extracted from existing Adapty `GetPaywall` calls or hardcoded product IDs found in the codebase.
|
|
229
|
+
|
|
230
|
+
5. **Call `Adapty.ReportTransaction()` after every confirmed purchase.** In the Unity IAP `OnPurchasePending` handler, after granting content and saving, report the transaction to Adapty:
|
|
231
|
+
|
|
232
|
+
```csharp
|
|
233
|
+
Adapty.ReportTransaction(transactionId, null, (error) =>
|
|
234
|
+
{
|
|
235
|
+
if (error != null) Debug.LogWarning($"Adapty ReportTransaction failed: {error}");
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
On Android, also call `Adapty.RestorePurchases` at app startup to sync any purchases Adapty may have missed.
|
|
240
|
+
|
|
241
|
+
6. **Produce a verification report** covering:
|
|
242
|
+
- Files changed
|
|
243
|
+
- Gradle conflict resolution confirmed
|
|
244
|
+
- Adapty observer mode configuration location
|
|
245
|
+
- `ReportTransaction` call location in purchase flow
|
|
246
|
+
- Manual steps: verify Adapty dashboard shows purchases from the updated build in sandbox; confirm Paywall Builder is no longer used (unavailable in observer mode)
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### Path B — Full Removal of Adapty
|
|
251
|
+
|
|
252
|
+
1. **Extract the product catalog** from existing Adapty usage — search for hardcoded product ID strings and `GetPaywall` / `GetPaywallForDefaultAudience` calls.
|
|
253
|
+
|
|
254
|
+
2. **Identify any Adapty paywall UI** — search for `AdaptyUI`, `PaywallView`, or `ShowPaywall`. These views must be replaced with custom Unity UI wired to `IAPManager.Buy(productId)`. Inform the user that building replacement shop UI is required before removal.
|
|
255
|
+
|
|
256
|
+
3. **Implement Unity IAP 5** using the extracted catalog — follow [path-add-iap-to-new-project.md](path-add-iap-to-new-project.md) in full.
|
|
257
|
+
|
|
258
|
+
4. **Remove Adapty SDK references from code.** Search `Assets/**/*.cs` for `using Adapty;` and all `Adapty.*` calls. Replace purchase calls with the Unity IAP 5 equivalents from the new `IAPManager`. Remove paywall presentation calls — these must be replaced by the new shop UI.
|
|
259
|
+
|
|
260
|
+
5. **Remove the Adapty package.** Delete the imported Adapty `.unitypackage` files from `Assets/` (typically under `Assets/Adapty/`). Remove any EDM4U dependency files Adapty registered.
|
|
261
|
+
|
|
262
|
+
6. **Run Assets > External Dependency Manager > Android Resolver > Force Resolve** after package removal to clean up Adapty's native dependencies.
|
|
263
|
+
|
|
264
|
+
7. **Produce a verification report** covering:
|
|
265
|
+
- Files changed and Adapty references removed
|
|
266
|
+
- Product catalog — confirm all product IDs are preserved in Unity IAP 5
|
|
267
|
+
- Paywall UI — confirm replacement UI is in place or listed as a pending TODO
|
|
268
|
+
- Manual steps: verify no orphaned Adapty Gradle entries remain in the Android build; confirm sandbox purchases succeed end-to-end with Unity IAP 5
|