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,446 @@
|
|
|
1
|
+
# Convert Native iOS StoreKit to Unity IAP 5
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Trigger Phrases](#trigger-phrases)
|
|
6
|
+
- [Important Constraints](#important-constraints)
|
|
7
|
+
- [Step 1 — Pre-Conversion Scan](#step-1--pre-conversion-scan)
|
|
8
|
+
- [Step 2 — StoreKit Version Detection](#step-2--storekit-version-detection)
|
|
9
|
+
- [Step 3 — Product Classification](#step-3--product-classification)
|
|
10
|
+
- [Step 4 — Migration Report (produce before any edits)](#step-4--migration-report-produce-before-any-edits)
|
|
11
|
+
- [Step 5 — Blocker Detection](#step-5--blocker-detection)
|
|
12
|
+
- [Step 6 — Target Architecture](#step-6--target-architecture)
|
|
13
|
+
- [Step 7 — Migration Phases](#step-7--migration-phases)
|
|
14
|
+
- [Step 8 — Behavior Change Rules](#step-8--behavior-change-rules)
|
|
15
|
+
- [Step 9 — Native → Unity IAP Concept Mapping](#step-9--native--unity-iap-concept-mapping)
|
|
16
|
+
- [Step 10 — Rollback Plan](#step-10--rollback-plan)
|
|
17
|
+
- [Step 11 — Test Checklist](#step-11--test-checklist)
|
|
18
|
+
- [Step 12 — Questions to Ask (only when required)](#step-12--questions-to-ask-only-when-required)
|
|
19
|
+
|
|
20
|
+
Use this reference when the project uses a custom native iOS StoreKit plugin (Objective-C or Swift, bridged into Unity via `[DllImport("__Internal")]` and `UnitySendMessage`) and needs to be migrated to `com.unity.purchasing`. Always install the latest stable version.
|
|
21
|
+
|
|
22
|
+
## Trigger Phrases
|
|
23
|
+
|
|
24
|
+
- "Convert native StoreKit to Unity IAP"
|
|
25
|
+
- "Migrate SKPaymentQueue integration to Unity IAP"
|
|
26
|
+
- "Replace custom iOS billing plugin with Unity IAP"
|
|
27
|
+
- "Remove native StoreKit and use Unity IAP"
|
|
28
|
+
- "Migrate DllImport StoreKit bridge to Unity IAP"
|
|
29
|
+
- "Replace SKProduct / SKPayment with Unity IAP"
|
|
30
|
+
|
|
31
|
+
## Important Constraints
|
|
32
|
+
|
|
33
|
+
- **Do not delete existing native iOS plugin files unless explicitly requested.** Always preserve with `#if !USE_UNITY_IAP_V5` guards on the C# side. ObjC/Swift files cannot use C# defines — guard their call sites in C# instead.
|
|
34
|
+
- **Create the new Unity IAP adapter first**, then mark the old native adapter as deprecated.
|
|
35
|
+
- **Preserve public game-facing APIs** via a compatibility facade so gameplay code does not need large rewrites.
|
|
36
|
+
- **Do not change product IDs.**
|
|
37
|
+
- **Do not change backend validation endpoints** unless the receipt format change makes it unavoidable — document any backend change required. The receipt format change from SK1 bundle to per-transaction JWS is a breaking backend change; flag it prominently.
|
|
38
|
+
- **Do not grant purchases on the client** if the existing project uses server-side validation.
|
|
39
|
+
- **Do not assume all products are consumables.** Detect and classify each product.
|
|
40
|
+
- **Do not assume subscriptions are simple.** Detect promotional offers, offer codes, and introductory price usage. If found, surface as migration blockers and let the user choose a path.
|
|
41
|
+
- **If the project contains RevenueCat, Adapty, or Essential Kit**, stop and notify the user — this skill does not support converting those packages.
|
|
42
|
+
- **If StoreKit-specific features are not cleanly expressible in Unity IAP**, report them as migration blockers rather than silently removing them.
|
|
43
|
+
- **A mixed Unity IAP + native StoreKit architecture is not recommended.** Both systems register as `SKPaymentQueue` observers — only one reliably receives transaction callbacks. Do not offer a mixed approach; if the user asks, explain the risk and redirect to Option A or B (see Subscription Blocker Options).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Step 1 — Pre-Conversion Scan
|
|
48
|
+
|
|
49
|
+
Scan before making any changes. Do not edit files until the migration report is accepted.
|
|
50
|
+
|
|
51
|
+
### 1a — Native iOS Plugin Files
|
|
52
|
+
|
|
53
|
+
Search `Assets/Plugins/iOS/` for:
|
|
54
|
+
- `*.m`, `*.mm` files — Objective-C plugins
|
|
55
|
+
- `*.h` files — headers with StoreKit imports or `extern "C"` bridge declarations
|
|
56
|
+
- `*.swift` files — Swift plugins (rare but increasingly common)
|
|
57
|
+
|
|
58
|
+
List all files found. For each, note whether it imports `StoreKit` or `StoreKit2`.
|
|
59
|
+
|
|
60
|
+
### 1b — C# Bridge Patterns
|
|
61
|
+
|
|
62
|
+
Search `Assets/**/*.cs` for:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
DllImport.*__Internal
|
|
66
|
+
UnitySendMessage
|
|
67
|
+
SKProduct|SKPayment|SKPaymentTransaction|SKPaymentQueue|SKReceiptRefresh
|
|
68
|
+
StoreKit|AppStore|AppleIAP|iOSBilling|iOSIAP|iOSPurchase
|
|
69
|
+
Application\.platform.*IPhonePlayer|RuntimePlatform\.IPhonePlayer
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Also search for callback method names referenced in `UnitySendMessage` calls in the native files — these will be C# methods receiving callbacks.
|
|
73
|
+
|
|
74
|
+
### 1c — StoreKit 1 Patterns (in .m / .mm / .h files)
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
SKProductsRequest|SKProductsRequestDelegate
|
|
78
|
+
SKPaymentQueue|SKPaymentTransactionObserver
|
|
79
|
+
SKPaymentTransaction|SKPayment|SKMutablePayment
|
|
80
|
+
SKProduct|SKProductSubscriptionPeriod|SKProductDiscount
|
|
81
|
+
paymentQueue:updatedTransactions:|finishTransaction:
|
|
82
|
+
SKReceiptRefreshRequest
|
|
83
|
+
SKPaymentDiscount|paymentDiscount
|
|
84
|
+
SKStorefront|paymentQueueDidChangeStorefront
|
|
85
|
+
canMakePayments
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 1d — StoreKit 2 Patterns (in .swift files)
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
import StoreKit
|
|
92
|
+
Product\.products\(for:\)|product\.purchase\(\)
|
|
93
|
+
Transaction\.currentEntitlements|Transaction\.updates|Transaction\.finish
|
|
94
|
+
verificationResult|JWSTransaction
|
|
95
|
+
winBackOffer|eligibleWinBackOffers
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 1e — Scene / Prefab / Asset Scan
|
|
99
|
+
|
|
100
|
+
Search `.unity`, `.prefab`, `.asset` files for references to billing-related MonoBehaviour names found in 1b. List all scene references so they can be rewired after migration.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Step 2 — StoreKit Version Detection
|
|
105
|
+
|
|
106
|
+
Classify the native plugin as **StoreKit 1**, **StoreKit 2**, or **Mixed** based on Step 1 findings.
|
|
107
|
+
|
|
108
|
+
| Generation | Signals | Notes |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| **StoreKit 1** | `SKPaymentQueue`, `SKPaymentTransaction`, `SKProductsRequest` | Most Unity projects pre-2023 |
|
|
111
|
+
| **StoreKit 2** | `Product.products(for:)`, `Transaction.currentEntitlements`, `Transaction.updates` | Swift 5.5+, iOS 15+ |
|
|
112
|
+
| **Mixed** | Both sets of signals present | Treat as StoreKit 1 for blocker purposes; flag SK2 usage for review |
|
|
113
|
+
|
|
114
|
+
Document the detected generation in the migration report. StoreKit 2 has additional blockers (see Step 5).
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Step 3 — Product Classification
|
|
119
|
+
|
|
120
|
+
Classify every detected product into one of: **Consumable**, **NonConsumable**, **Subscription**, **Unknown**.
|
|
121
|
+
|
|
122
|
+
Infer from (in priority order):
|
|
123
|
+
|
|
124
|
+
1. `SKProduct.subscriptionPeriod` non-nil → **Subscription**
|
|
125
|
+
2. `finishTransaction:` called after grant without a "restore" path → **Consumable**
|
|
126
|
+
3. `restoreCompletedTransactions` handling or checking `originalTransaction` → **NonConsumable** or **Subscription**
|
|
127
|
+
4. Product ID naming conventions (`coins`, `gems`, `pack` → Consumable; `remove_ads`, `unlock`, `premium` → NonConsumable; `monthly`, `annual`, `sub` → Subscription)
|
|
128
|
+
5. Entitlement / backend grant code patterns
|
|
129
|
+
6. Comments or config files
|
|
130
|
+
|
|
131
|
+
If type cannot be confidently inferred, mark as **Unknown** and add to the blocker question list.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Step 4 — Migration Report (produce before any edits)
|
|
136
|
+
|
|
137
|
+
Generate all sections before touching any file. Do not skip any.
|
|
138
|
+
|
|
139
|
+
1. **Current native billing architecture** — describe the bridge pattern (C# `[DllImport("__Internal")]` → ObjC/Swift plugin → StoreKit)
|
|
140
|
+
2. **StoreKit generation** — SK1, SK2, or Mixed
|
|
141
|
+
3. **Files using native StoreKit** — full list with role of each file (plugin, bridge, callback receiver, UI)
|
|
142
|
+
4. **Product catalog discovered** — all product IDs found
|
|
143
|
+
5. **Product type mapping** — inferred type + confidence + source of inference for each product
|
|
144
|
+
6. **Purchase flow mapping** — native → Unity IAP step-by-step
|
|
145
|
+
7. **Restore purchase flow mapping** — `restoreCompletedTransactions` / `Transaction.currentEntitlements` → `store.FetchPurchases()` + `store.RestoreTransactions(callback)`
|
|
146
|
+
8. **Backend validation mapping** — what data the old flow sent vs what Unity IAP provides (see Behavior Change Rules — receipt format is a breaking change)
|
|
147
|
+
9. **Transaction finish behavior mapping** — `finishTransaction:` → `store.ConfirmPurchase(pendingOrder)` (only after grant)
|
|
148
|
+
10. **Subscription behavior mapping** — detected subscription products, restore path, renewal handling, introductory price usage
|
|
149
|
+
11. **StoreKit-specific features detected** — list each (see Blocker Detection)
|
|
150
|
+
12. **Migration blockers** — features that cannot be cleanly mapped
|
|
151
|
+
13. **Proposed new Unity IAP architecture** — new files and their roles
|
|
152
|
+
14. **Code files to create** — with paths
|
|
153
|
+
15. **Code files to modify** — with nature of each change
|
|
154
|
+
16. **Manual App Store Connect checks** — product ID match, subscription group configuration
|
|
155
|
+
17. **Test plan** — from the checklist in this document
|
|
156
|
+
18. **Rollback plan** — from the rollback plan section in this document
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Step 5 — Blocker Detection
|
|
161
|
+
|
|
162
|
+
Report these as migration blockers if detected. Do not silently remove them.
|
|
163
|
+
|
|
164
|
+
| Feature | Detection Signal | Blocker Level |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| Promotional offer signing (SK1 `SKPaymentDiscount`) | `SKPaymentDiscount`, `paymentDiscount`, `offerIdentifier`, `keyIdentifier`, `nonce`, `signature` | **Hard** — requires server-side signed offers; Unity IAP has no equivalent |
|
|
167
|
+
| SK2 promotional offer signing | `promotionalOffer`, `PromotionalOffer`, `eligiblePromotionOfferSignature` | **Hard** — not exposed in Unity IAP |
|
|
168
|
+
| Win-back offers (SK2) | `winBackOffer`, `eligibleWinBackOffers` | **Hard** — not supported in Unity IAP 5.4 |
|
|
169
|
+
| `SKReceiptRefreshRequest` (manual receipt refresh) | `SKReceiptRefreshRequest` | **Soft** — `store.FetchPurchases()` covers the re-delivery use case; document loss of explicit refresh |
|
|
170
|
+
| `SKStorefront` / storefront change observer | `SKStorefront`, `paymentQueueDidChangeStorefront` | **Soft** — not exposed in Unity IAP; document loss |
|
|
171
|
+
| Multi-quantity purchases | `quantity > 1` in `SKMutablePayment` | **Hard** — Unity IAP does not support quantity > 1 per transaction |
|
|
172
|
+
| Custom `SKPaymentQueue` observers remaining alongside Unity IAP | Other `addTransactionObserver` calls not being removed | **Hard** — both observers compete; only one reliably receives callbacks |
|
|
173
|
+
| `SKOverlay` / `SKStoreProductViewController` | `SKOverlay`, `SKStoreProductViewController` | **Soft** — not IAP-related; these are store UI overlays. Document as out of scope for this migration |
|
|
174
|
+
| Direct App Store receipt bundle validation (`appStoreReceiptURL`) | `appStoreReceiptURL`, `receiptData`, `/verifyReceipt` in backend URLs | **Hard blocker for backend** — Unity IAP uses per-transaction JWS, not the receipt bundle. Backend must migrate from `/verifyReceipt` to JWS validation |
|
|
175
|
+
|
|
176
|
+
### Subscription Blocker Options (present to user when hard subscription blockers found)
|
|
177
|
+
|
|
178
|
+
**Option A — Remove the blocking feature and simplify App Store Connect setup for Unity IAP compatibility**
|
|
179
|
+
Remove promotional offer signing or simplify subscription configuration so Unity IAP can handle all products without native StoreKit. Recommended — single system, no coexistence risk.
|
|
180
|
+
|
|
181
|
+
**Option B — Stay on native StoreKit for all products**
|
|
182
|
+
Do not migrate at this time. Keep the native StoreKit plugin as-is until the App Store Connect configuration can be simplified or Unity IAP exposes the required features. Stop the conversion and document the blocker and this decision in `IapMigrationNotes.md`.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Step 6 — Target Architecture
|
|
187
|
+
|
|
188
|
+
Create under `Assets/Scripts/IAP/`:
|
|
189
|
+
|
|
190
|
+
| File | Purpose |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `IStorePurchaseService.cs` | Game-facing interface (stable public API) |
|
|
193
|
+
| `UnityIapStorePurchaseService.cs` | Unity IAP 5.x implementation |
|
|
194
|
+
| `ProductCatalogDefinition.cs` | ScriptableObject or plain class holding all `ProductDefinitionEntry` |
|
|
195
|
+
| `ProductDefinitionEntry.cs` | Per-product data: ID, type, store-specific IDs |
|
|
196
|
+
| `PurchaseEntitlementMapper.cs` | Maps Unity IAP order data → game grant calls |
|
|
197
|
+
| `PurchaseValidationRequest.cs` | DTO sent to backend |
|
|
198
|
+
| `PurchaseValidationResult.cs` | DTO received from backend |
|
|
199
|
+
| `PurchaseRestoreResult.cs` | Result of restore operation |
|
|
200
|
+
| `LegacyStoreKitAdapterDeprecated.cs` | Compatibility facade (only if game code calls old StoreKit bridge API directly) |
|
|
201
|
+
| `IapMigrationNotes.md` | Human-readable notes on what changed, blockers found, manual steps |
|
|
202
|
+
|
|
203
|
+
### Game-Facing Interface (preserve or adapt from existing API surface)
|
|
204
|
+
|
|
205
|
+
```csharp
|
|
206
|
+
public interface IStorePurchaseService
|
|
207
|
+
{
|
|
208
|
+
Task InitializeAsync();
|
|
209
|
+
Task FetchProductsAsync();
|
|
210
|
+
IReadOnlyList<StoreProductInfo> GetProducts();
|
|
211
|
+
Task PurchaseAsync(string productId);
|
|
212
|
+
Task RestorePurchasesAsync();
|
|
213
|
+
bool IsInitialized { get; }
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Step 7 — Migration Phases
|
|
220
|
+
|
|
221
|
+
### Phase 1 — Package Check
|
|
222
|
+
|
|
223
|
+
- Verify `com.unity.purchasing` exists in `Packages/manifest.json`.
|
|
224
|
+
- If absent or outdated, install the latest stable version via **Window > Package Manager > Unity Registry > In App Purchasing**. Do not auto-edit `manifest.json` unless the user explicitly allows it.
|
|
225
|
+
- Confirm installation before proceeding.
|
|
226
|
+
|
|
227
|
+
### Phase 2 — Product Catalog Extraction
|
|
228
|
+
|
|
229
|
+
- Extract all product IDs and inferred types from the scan.
|
|
230
|
+
- Generate `ProductCatalogDefinition.cs` / `ProductDefinitionEntry.cs`.
|
|
231
|
+
- Each entry: `productId`, `ProductType`, optional `storeSpecificIds`, `notes`.
|
|
232
|
+
|
|
233
|
+
### Phase 3 — New Unity IAP Service
|
|
234
|
+
|
|
235
|
+
Create `UnityIapStorePurchaseService`:
|
|
236
|
+
|
|
237
|
+
1. **Initialization** — `UnityIAPServices.StoreController()`, subscribe all events **before** `Connect()`.
|
|
238
|
+
2. **Product fetching** — build `List<ProductDefinition>` from catalog, call `store.FetchProducts()`, handle `OnProductsFetched` / `OnProductsFetchFailed`.
|
|
239
|
+
3. **Purchase initiation** — `store.PurchaseProduct(product)`.
|
|
240
|
+
4. **Purchase callback** — `OnPurchasePending`: validate receipt → grant entitlement → `ConfirmPurchase(pendingOrder)`. **Never confirm before grant.**
|
|
241
|
+
5. **Deferred purchases (Ask-to-Buy)** — `OnPurchaseDeferred`: show pending UI, do not grant. When approved, `OnPurchasePending` fires.
|
|
242
|
+
6. **Promotional purchase interception** — if the existing plugin intercepts App Store promotional purchases, wire up:
|
|
243
|
+
```csharp
|
|
244
|
+
if (store.AppleStoreExtendedPurchaseService != null)
|
|
245
|
+
store.AppleStoreExtendedPurchaseService.OnPromotionalPurchaseIntercepted += OnPromotionalPurchase;
|
|
246
|
+
```
|
|
247
|
+
Call `store.AppleStoreExtendedPurchaseService?.ContinuePromotionalPurchases()` when ready to proceed.
|
|
248
|
+
7. **Failed purchases** — `OnPurchaseFailed`: map `FailureReason` to user-facing error.
|
|
249
|
+
8. **Restore** — `store.FetchPurchases()` re-delivers unconfirmed purchases via `OnPurchasePending`. Add `store.RestoreTransactions(callback)` for the explicit "Restore Purchases" button (required on iOS).
|
|
250
|
+
9. **Validation handoff** — send `order.Info.Apple?.jwsRepresentation` to backend in place of the SK1 receipt bundle. Document the backend endpoint change required (see Behavior Change Rules).
|
|
251
|
+
|
|
252
|
+
### Phase 4 — Compatibility Facade
|
|
253
|
+
|
|
254
|
+
If game code calls the old native bridge directly (e.g., `NativeStoreKit.Purchase(id)`):
|
|
255
|
+
|
|
256
|
+
- Create `LegacyStoreKitAdapterDeprecated.cs` that implements the same method signatures.
|
|
257
|
+
- Delegate all calls to `UnityIapStorePurchaseService`.
|
|
258
|
+
- Mark all methods `[Obsolete("Use IStorePurchaseService — remove after migration validation")]`.
|
|
259
|
+
|
|
260
|
+
### Phase 5 — Remove Native Plugin from Active Path
|
|
261
|
+
|
|
262
|
+
- Stop calling `[DllImport("__Internal")]` bridge methods for migrated products.
|
|
263
|
+
- Wrap all native bridge calls in C# with `#if !USE_UNITY_IAP_V5` — do not delete them.
|
|
264
|
+
- Do not delete ObjC/Swift plugin files — leave them in `Assets/Plugins/iOS/`. The `#if` guards on the C# call sites are sufficient to prevent the old bridge from being invoked.
|
|
265
|
+
- If the existing plugin registers its own `SKPaymentQueue` observer (common), add a note in `IapMigrationNotes.md` that the plugin's observer registration must be removed or disabled before shipping — both observers competing for the same queue is a hard conflict.
|
|
266
|
+
|
|
267
|
+
### Phase 6 — Test Scaffolding
|
|
268
|
+
|
|
269
|
+
- Add debug logs at each IAP event.
|
|
270
|
+
- Add editor stubs for sandbox testing.
|
|
271
|
+
- Add the QA checklist from the test plan below to `IapMigrationNotes.md`.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Step 8 — Behavior Change Rules
|
|
276
|
+
|
|
277
|
+
### Receipt Format (backend-breaking change)
|
|
278
|
+
|
|
279
|
+
The receipt format is fundamentally different between native StoreKit and Unity IAP. Flag this as a **required backend change** if server-side validation is in use.
|
|
280
|
+
|
|
281
|
+
| Native approach | Unity IAP equivalent |
|
|
282
|
+
|---|---|
|
|
283
|
+
| SK1: `[NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]` base64-encoded, validated against Apple's `/verifyReceipt` endpoint | `order.Info.Apple?.jwsRepresentation` — a per-transaction JWS string, validated against Apple's App Store Server API |
|
|
284
|
+
| SK2: Per-transaction JWS string from `verificationResult` | Same — `order.Info.Apple?.jwsRepresentation` |
|
|
285
|
+
|
|
286
|
+
Unity IAP uses StoreKit 2 under the hood (on iOS 15+). The resulting receipt is a JWS per transaction, not the SK1 app receipt bundle. If the backend currently calls `/verifyReceipt` with a base64 receipt, it must migrate to Apple's App Store Server API (`GET /inApps/v1/transactions/{transactionId}`) or use the JWS directly.
|
|
287
|
+
|
|
288
|
+
### Transaction Finishing (Acknowledgement / Consumption)
|
|
289
|
+
|
|
290
|
+
| Native StoreKit | Unity IAP |
|
|
291
|
+
|---|---|
|
|
292
|
+
| `SKPaymentQueue.default().finishTransaction(transaction)` — called after grant for all types | `store.ConfirmPurchase(pendingOrder)` — called after grant for all types |
|
|
293
|
+
|
|
294
|
+
**Never call `ConfirmPurchase` before the entitlement is safely granted and saved.**
|
|
295
|
+
|
|
296
|
+
### Restore Transactions
|
|
297
|
+
|
|
298
|
+
| Native StoreKit | Unity IAP |
|
|
299
|
+
|---|---|
|
|
300
|
+
| `SKPaymentQueue.default().restoreCompletedTransactions()` | `store.RestoreTransactions(callback)` — for the explicit "Restore Purchases" button |
|
|
301
|
+
| Checking for existing transactions at startup | `store.FetchPurchases()` — re-delivers any unconfirmed purchases via `OnPurchasePending` |
|
|
302
|
+
|
|
303
|
+
`store.FetchPurchases()` should be called at startup. `RestoreTransactions` is only needed for the user-triggered restore button (required on iOS for Apple App Store compliance).
|
|
304
|
+
|
|
305
|
+
### App Account Token (replacing `applicationUsername`)
|
|
306
|
+
|
|
307
|
+
If the native plugin set `SKMutablePayment.applicationUsername` to associate purchases with a backend user:
|
|
308
|
+
|
|
309
|
+
```csharp
|
|
310
|
+
// Set after Connect() — accepts a Guid, not a string hash
|
|
311
|
+
store.AppleStoreExtendedService?.SetAppAccountToken(Guid.Parse(userAccountGuid));
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Must be called **after** `await store.Connect()`. The token is attached to the transaction and surfaced in the JWS payload.
|
|
315
|
+
|
|
316
|
+
### Ask-to-Buy (Deferred Purchases)
|
|
317
|
+
|
|
318
|
+
| Native StoreKit | Unity IAP |
|
|
319
|
+
|---|---|
|
|
320
|
+
| `SKPaymentTransactionStatePurchasing` + parent approval pending → `paymentQueue:removedTransactions:` | `store.OnPurchaseDeferred` — show pending UI, do not grant |
|
|
321
|
+
| Purchase approved → `paymentQueue:updatedTransactions:` with `SKPaymentTransactionStatePurchased` | `store.OnPurchasePending` — grant content, then confirm |
|
|
322
|
+
|
|
323
|
+
If the existing plugin simulates Ask-to-Buy via `store.AppleStoreExtendedPurchaseService?.simulateAskToBuy`, that property is still available in Unity IAP 5.
|
|
324
|
+
|
|
325
|
+
### Code Redemption Sheet
|
|
326
|
+
|
|
327
|
+
If the native plugin calls `SKPaymentQueue.default().presentCodeRedemptionSheet()`:
|
|
328
|
+
|
|
329
|
+
```csharp
|
|
330
|
+
store.AppleStoreExtendedPurchaseService?.PresentCodeRedemptionSheet();
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Available after `Connect()`. Null-check required — only non-null on iOS.
|
|
334
|
+
|
|
335
|
+
### Promotional Purchases
|
|
336
|
+
|
|
337
|
+
If the native plugin intercepts App Store promotional purchases (products promoted on the App Store product page):
|
|
338
|
+
|
|
339
|
+
```csharp
|
|
340
|
+
// Subscribe after Connect() — if() required (?.+= does not work with events)
|
|
341
|
+
if (store.AppleStoreExtendedPurchaseService != null)
|
|
342
|
+
store.AppleStoreExtendedPurchaseService.OnPromotionalPurchaseIntercepted += OnPromotionalPurchase;
|
|
343
|
+
|
|
344
|
+
void OnPromotionalPurchase(Product product)
|
|
345
|
+
{
|
|
346
|
+
// Show confirmation UI, then:
|
|
347
|
+
store.AppleStoreExtendedPurchaseService?.ContinuePromotionalPurchases();
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Step 9 — Native → Unity IAP Concept Mapping
|
|
354
|
+
|
|
355
|
+
### StoreKit 1
|
|
356
|
+
|
|
357
|
+
| Native StoreKit 1 | Unity IAP 5.x |
|
|
358
|
+
|---|---|
|
|
359
|
+
| `SKProduct` | `Product` (via `store.GetProductById`) |
|
|
360
|
+
| `SKPaymentTransaction` | `PendingOrder` (on `OnPurchasePending`) |
|
|
361
|
+
| `SKPaymentTransaction.transactionIdentifier` | `pendingOrder.Info.TransactionID` |
|
|
362
|
+
| `SKPaymentTransaction.payment.productIdentifier` | `pendingOrder.CartOrdered.Items().First().Product.definition.id` |
|
|
363
|
+
| App receipt (`appStoreReceiptURL` base64) | `order.Info.Apple?.jwsRepresentation` (per-transaction JWS — see receipt format note) |
|
|
364
|
+
| `SKPaymentQueue.default().finishTransaction:` | `store.ConfirmPurchase(pendingOrder)` |
|
|
365
|
+
| `SKPaymentQueue.default().restoreCompletedTransactions()` | `store.RestoreTransactions(callback)` |
|
|
366
|
+
| `SKPaymentTransactionStatePurchased` | `store.OnPurchasePending` |
|
|
367
|
+
| `SKPaymentTransactionStateFailed` | `store.OnPurchaseFailed` |
|
|
368
|
+
| `SKPaymentTransactionStateDeferred` | `store.OnPurchaseDeferred` |
|
|
369
|
+
| `SKPaymentTransactionStateRestored` | `store.OnPurchasesFetched` (via `FetchPurchases`) or `store.OnPurchasePending` (via `RestoreTransactions`) |
|
|
370
|
+
| `SKProductsRequest` + `productsRequest:didReceiveResponse:` | `store.FetchProducts(definitions)` → `store.OnProductsFetched` |
|
|
371
|
+
| `SKPaymentQueue.canMakePayments()` | `store.AppleStoreExtendedService?.canMakePayments` (after `Connect()`) |
|
|
372
|
+
| `SKMutablePayment.applicationUsername` | `store.AppleStoreExtendedService?.SetAppAccountToken(Guid)` (after `Connect()`) |
|
|
373
|
+
| `SKPaymentDiscount` (promotional offer) | **Not supported** — report as hard blocker |
|
|
374
|
+
| `SKReceiptRefreshRequest` | No direct equivalent — `store.FetchPurchases()` covers re-delivery |
|
|
375
|
+
| `SKStorefront` | Not exposed in Unity IAP |
|
|
376
|
+
| `product.subscriptionPeriod` | `store.AppleStoreExtendedProductService?.GetProductDetails()` (returns raw JSON) |
|
|
377
|
+
| `product.introductoryPrice` | `store.AppleStoreExtendedProductService?.GetIntroductoryPriceDictionary()` |
|
|
378
|
+
|
|
379
|
+
### StoreKit 2
|
|
380
|
+
|
|
381
|
+
| Native StoreKit 2 | Unity IAP 5.x |
|
|
382
|
+
|---|---|
|
|
383
|
+
| `Product.products(for:)` | `store.FetchProducts(definitions)` → `store.OnProductsFetched` |
|
|
384
|
+
| `product.purchase()` | `store.PurchaseProduct(product)` |
|
|
385
|
+
| `Transaction.currentEntitlements` (async stream) | `store.FetchPurchases()` → `store.OnPurchasesFetched` |
|
|
386
|
+
| `Transaction.updates` (async stream) | `store.OnPurchasePending` (event-driven) |
|
|
387
|
+
| `transaction.finish()` | `store.ConfirmPurchase(pendingOrder)` |
|
|
388
|
+
| JWS transaction string from `verificationResult` | `order.Info.Apple?.jwsRepresentation` |
|
|
389
|
+
| `Transaction.currentEntitlements` for entitlement check | `store.CheckEntitlement(product)` → `store.OnCheckEntitlement` |
|
|
390
|
+
| `winBackOffer` / `eligibleWinBackOffers` | **Not supported** — report as hard blocker |
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Step 10 — Rollback Plan
|
|
395
|
+
|
|
396
|
+
- Wrap all new Unity IAP C# code in `#if USE_UNITY_IAP_V5`.
|
|
397
|
+
- Wrap all replaced native bridge calls (`[DllImport("__Internal")]` invocations) in `#if !USE_UNITY_IAP_V5`.
|
|
398
|
+
- ObjC/Swift plugin files cannot use C# defines — leave them in place untouched. The `#if` guards on the C# call sites prevent the bridge from being invoked.
|
|
399
|
+
- To revert: remove `USE_UNITY_IAP_V5` from **Edit > Project Settings > Player > Scripting Define Symbols**. The native StoreKit bridge is restored without any code changes.
|
|
400
|
+
- Never activate both purchase systems simultaneously — both register as `SKPaymentQueue` observers and will compete.
|
|
401
|
+
- Document the rollback steps in `IapMigrationNotes.md`.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Step 11 — Test Checklist
|
|
406
|
+
|
|
407
|
+
Include this in `IapMigrationNotes.md` after migration:
|
|
408
|
+
|
|
409
|
+
- [ ] Fresh install — products load and display prices
|
|
410
|
+
- [ ] Upgrade from old app version to migrated version — no duplicate entitlements
|
|
411
|
+
- [ ] Consumable purchase — content granted
|
|
412
|
+
- [ ] Repeat consumable purchase — repeatable, no block
|
|
413
|
+
- [ ] Non-consumable purchase — content granted once
|
|
414
|
+
- [ ] Non-consumable restore via "Restore Purchases" button — content restored on reinstall
|
|
415
|
+
- [ ] Subscription purchase — subscription active
|
|
416
|
+
- [ ] Subscription restore — status refreshed on launch
|
|
417
|
+
- [ ] Ask-to-Buy (deferred) — pending UI shown, no premature grant; purchase completes when parent approves
|
|
418
|
+
- [ ] Promotional purchase intercept (if applicable) — `OnPromotionalPurchaseIntercepted` fires, `ContinuePromotionalPurchases()` proceeds correctly
|
|
419
|
+
- [ ] Code redemption sheet (if applicable) — `PresentCodeRedemptionSheet()` opens
|
|
420
|
+
- [ ] Interrupted purchase flow — re-delivered on next launch via `OnPurchasePending`
|
|
421
|
+
- [ ] App killed during purchase — pending order re-delivered on next launch
|
|
422
|
+
- [ ] Backend validation success — entitlement granted (JWS payload accepted by backend)
|
|
423
|
+
- [ ] Backend validation failure — entitlement NOT granted, purchase not confirmed
|
|
424
|
+
- [ ] Network failure during validation — purchase left pending, re-delivered on next launch
|
|
425
|
+
- [ ] Sandbox tester account — no real charges, sandbox transactions process correctly
|
|
426
|
+
- [ ] TestFlight build — products load and sandbox purchases succeed
|
|
427
|
+
- [ ] App Store Connect product ID match — all IDs match exactly
|
|
428
|
+
- [ ] App Store Connect subscription group — base plan configuration compatible with Unity IAP
|
|
429
|
+
- [ ] No duplicate entitlement grant
|
|
430
|
+
- [ ] No unfinished transactions remaining (all purchases confirmed)
|
|
431
|
+
- [ ] No double grant of consumables
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Step 12 — Questions to Ask (only when required)
|
|
436
|
+
|
|
437
|
+
Ask only when the information cannot be inferred:
|
|
438
|
+
|
|
439
|
+
1. Which product IDs are consumable, non-consumable, or subscription?
|
|
440
|
+
2. Does the backend currently validate using the SK1 receipt bundle (base64 + `/verifyReceipt`)? If so, a backend migration to JWS validation is required.
|
|
441
|
+
3. Does the plugin intercept App Store promotional purchases (`paymentQueue:shouldAddStorePayment:forProduct:`)?
|
|
442
|
+
4. Are any subscriptions using promotional offer signing (`SKPaymentDiscount`)? If so, this is a hard blocker.
|
|
443
|
+
5. Should the skill preserve the old public C# billing API as a compatibility facade?
|
|
444
|
+
6. Should native ObjC/Swift plugin files be kept, disabled, or removed after migration validation?
|
|
445
|
+
|
|
446
|
+
If enough information can be inferred, proceed with a best-effort plan and mark uncertain items as **TODO**.
|