needle-mirror/com.unity.purchasing/in-app-purchases

Claude Code and agent skill by needle-mirror/com.unity.purchasing

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Implement, configure, and debug Unity In-App Purchases (IAP) — store connection, product catalog, consumable/non-consumable/subscription purchases, two-step pending-confirm flow, receipt validation, entitlement checking, restore transactions, Apple extensions (promotional purchases, Ask-to-Buy, code redemption), and Google Play extensions (subscription upgrade/downgrade), D2C Capabilities(direct to customer), 3rd party payment provider (Stripe/Coda) via Unity IAP/Unity Cloud. Use when the user needs to add, modify, debug, or migrate from native Android/iOS billing, 3rd party packages(RevenueCat/Adapty/Essential Kit/Unipay supported) to IAP. Triggers on microtransactions (MTX), monetization, real-money purchases, store purchases, buying items, support D2C, purchase via Stripe/Coda, migrate from native billing(Google's BillingClient or Apple's StoreKit/SKPaymentQueue/SKProduct)/RevenueCat/Adapty/EssentialKit/Unipay.

Install

npx vskill@latest install needle-mirror/com.unity.purchasing/in-app-purchases

SKILL.md

Unity In-App Purchasing

Namespace: UnityEngine.Purchasing | Security: UnityEngine.Purchasing.Security Package: com.unity.purchasing

Unity IAP has its own initialization path via new PurchasingController(UnityIAPServices.GetDefaultStore()) → controller.Connect(). It does not require UnityServices.InitializeAsync(), but they can coexist if your project uses other UGS services (IAP D2C Capabilities do need it). If Analytics is present and InitializeAsync() is called, IAP will automatically send transaction events.

Always write code against `PurchasingController` (v5.5+) — one object for every store the app sells through: the platform store, a D2C payment provider, or both. Single-store apps use it too. Every event takes the store name first. See references/purchasing-controller.md.

  • Package without `PurchasingController` (5.4.x and earlier — check the installed package contains the type): recommend upgrading com.unity.purchasing. Fall back to StoreController only if the user can't or won't upgrade.
  • Project already on `StoreController`: offer to migrate the whole project to PurchasingController (migration guide) and do it only if the user agrees — never migrate unasked. If the user declines, stay on StoreController throughout, new code included: never add a PurchasingController next to existing StoreControllers.

Before You Start

Always read [references/pre-check.md](references/pre-check.md) first. It scans the project for third-party IAP packages, native Google Billing, and existing Unity IAP versions, then routes to the correct path. Do not read any other reference file or make any changes until routing is resolved.

Detailed References

Read reference files on demand — only when you need specific API signatures, platform extension details, or migration mappings.

Initialization Flow

  1. Create new PurchasingController(UnityIAPServices.GetDefaultStore()) — add PaymentProvider.Name for IAP D2C Capabilities, after Unity Services sign-in
  2. Subscribe to all required events (see Required Event Subscriptions below) before calling Connect() — handlers take the store name first: (storeName, order) => ...
  3. await controller.Connect() to connect every store
  4. On OnStoreConnected(storeName), call controller.FetchProducts(productDefinitions, storeName) to load that store's catalog
  5. On OnProductsFetched(storeName, products), that store's products are ready for display and purchase

Use Awake() for initialization — ensures IAP is ready before other Start() methods.

Product types: ProductType.Consumable, ProductType.NonConsumable, ProductType.Subscription.

Fetching Products

Define products as List<ProductDefinition> and pass them to controller.FetchProducts(definitions, storeName) — one call per store, since a definition carries a single store-specific ID. Use StoreSpecificIds when product IDs differ across Apple/Google stores. For complex catalogs, use CatalogProvider / RemoteCatalogProvider; one provider serves every store: foreach (var store in controller.StoreNames) catalogProvider.FetchProducts(defs => controller.FetchProductsWithNoRetries(defs, store), store);

Products managed in Unity Cloud come from the Remote Catalog (RemoteCatalogProvider.FetchRemoteCatalog()). Fetching it needs a signed-in player: call UnityServices.InitializeAsync() and sign in first, even when only native stores are used, and don't show a store before then. Without sign-in the fetch fails with Failed to fetch full remote catalog; paging stopped after cursor ...; that error usually means the player isn't signed in.

MethodBehavior
GetProducts(storeName)Returns that store's cached product list (synchronous, stale if FetchProducts not called)
FetchProducts(defs, storeName)Queries that store for fresh pricing/availability and updates its cache — the store name is required; call it once per store
GetProductById(id, storeName)Returns a single cached product by ID from that store

These are NOT interchangeable. Always call FetchProducts first before relying on GetProducts.

Two-Step Purchase Flow

IAP v5 uses a mandatory two-step flow: Pending → Confirm.

  1. controller.PurchaseProduct(product) — initiates the purchase in the store the product was fetched from (product.StoreName)
  2. OnPurchasePending(storeName, pendingOrder) fires — you receive a PendingOrder
  3. Validate the receipt, grant content to the player
  4. controller.ConfirmPurchase(pendingOrder) — finalizes the transaction in pendingOrder.StoreName
  5. OnPurchaseConfirmed(storeName, order) fires — receives Order base type; pattern-match ConfirmedOrder (success) vs FailedOrder (confirmation failed)

You MUST call `ConfirmPurchase(pendingOrder)` after granting content. Unconfirmed purchases are re-delivered on next app launch to prevent lost purchases.

De-duplication: OnPurchasePending may fire multiple times for the same purchase (e.g., app restart before confirmation). Always check if content was already granted.

Consumables: Confirmed consumable purchases are NOT returned by FetchPurchases. Track consumable grants yourself (e.g., in Cloud Save).

Deferred purchases: OnPurchaseDeferred fires for Ask-to-Buy (iOS) and Google Play deferred purchases. Do NOT grant content — wait for OnPurchasePending when approved.

Restore Transactions

controller.RestoreTransactions((storeName, success, error) => ...) re-delivers non-consumable and subscription purchases; the callback runs once per store. Each restored purchase triggers OnPurchasePending.

Required on iOS for Apple App Store compliance — add a "Restore Purchases" button.

Apple non-renewable subscriptions cannot be restored via RestoreTransactions. Track these server-side.

Receipt Validation

PlatformApproach
Google PlayCrossPlatformValidator with GooglePlayTangle.Data() — local validation supported
Apple (StoreKit 2)Local validation is a no-op. Use order.Info.Apple?.jwsRepresentation for server-side validation

Generate tangle data via Services > In-App Purchasing > Receipt Validation Obfuscator in the Unity Editor.

Entitlement Checking

Use when you don't have the Order and want to know the status of a specific product (replaces v4's product.hasReceipt). If you already have the Order, check its type instead: PendingOrder maps to EntitledUntilConsumed (consumables) or EntitledButNotFinished (non-consumables/subscriptions), ConfirmedOrder maps to FullyEntitled.

Call controller.CheckEntitlement(product) and handle controller.OnCheckEntitlement += (storeName, entitlement) => .... Check entitlement.Status == EntitlementStatus.FullyEntitled.

EntitlementStatus values: FullyEntitled, EntitledUntilConsumed, EntitledButNotFinished, NotEntitled, Unknown.

Fetch Existing Purchases

controller.FetchPurchases() retrieves all current purchases from every store (pass a store name for one). Useful at app startup.

MethodBehavior
GetPurchases(storeName)Returns that store's cached purchase list
FetchPurchases(storeName?)Queries the store(s) for current purchases and overwrites each store's cached list

FetchPurchases() replaces a store's entire cached list on each call. Only non-consumables and subscriptions are re-fetched — confirmed consumables are not returned (see Two-Step Purchase Flow above).

Subscription Info

Subscription info is on IPurchasedProductInfo, accessed via order.Info.PurchasedProductInfo — NOT on `CartItem` (CartItem only has Product and Quantity).

IsSubscribed() returns Result enum (True/False/Unsupported), NOT bool. Use == Result.True for null-safe comparison.

Required Event Subscriptions

Always subscribe to BOTH success and failure events. Not subscribing to failure events generates runtime warnings. On PurchasingController every event takes the store name first — one subscription covers every store.

CallSuccess EventFailure Event (REQUIRED)
FetchProductsOnProductsFetchedOnProductsFetchFailed
FetchPurchases()OnPurchasesFetchedOnPurchasesFetchFailed
Connect()OnStoreConnectedOnStoreDisconnected
PurchaseProduct()OnPurchasePendingOnPurchaseFailed
CheckEntitlement()OnCheckEntitlement—

Always subscribe to `OnPurchaseDeferred` — fires for Ask-to-Buy (iOS) and Google Play deferred purchases. Not subscribing silently drops deferred purchases.

Subscribe to events BEFORE calling Connect() — pending purchases from a previous session may fire immediately.

Failure Description Property Names

These property names are NOT interchangeable — using the wrong one causes CS1061:

TypeFieldPropertyNOT
StoreConnectionFailureDescription.message.Message`.reason`
ProductFetchFailed—.FailureReason, .FailedFetchProducts`.Message`
FailedOrder—.FailureReason, .Details—
PurchasesFetchFailureDescription.message, .failureReason.Message, .FailureReason—

Validation

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
39
Repository forks
11
7-day trend score
18

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/needle-mirror/com.unity.purchasing/in-app-purchases/badge)](https://verified-skill.com/skills/needle-mirror/com.unity.purchasing/in-app-purchases)

Something wrong with this skill or its page? Tell us.