Unity IAP
In-App Purchases
Receipt Validation
Mobile Game Monetization
Game Engineering
iOS & Android

The Ultimate Guide to Unity In-App Purchases (IAP): Setup, Receipt Validation & Best Practices (2026)

7 min read
Eshan Naithani

In-App Purchases (IAP) drive over 70% of global mobile gaming revenue. Whether you are selling starter packs, battle passes, or cosmetic items, your monetization engine must be reliable, user-friendly, and protected against fraud.

An incomplete IAP implementation can lead to lost revenue from unverified transactions, negative user reviews from failed purchases, or app rejection during store review.

In this guide, we provide a complete end-to-end engineering blueprint for Unity IAP (com.unity.purchasing), covering everything from store portal registration to server-side receipt validation and anti-piracy best practices.


1. Product Types & Store Revenue Model

Before writing code, it is critical to categorize your in-game products correctly in both App Store Connect and Google Play Console.

Product TypePurchase BehaviorTypical Game Use CaseApple / Google Revenue Split
ConsumablesCan be purchased repeatedlyGems, Coins, Energy Refills, Extra Lives30% standard (15% via Small Business Program)
Non-ConsumablesPurchased once per account; restored on new devicesRemove Ads, Character Unlock, Permanent XP Boost30% standard (15% via Small Business Program)
SubscriptionsAuto-renewing monthly or annual recurring chargesVIP Pass, Season Pass ($4.99/mo)15% recurring after 12 months of continuous subscription

2. Store Portal Setup & Product ID Taxonomy

[!IMPORTANT] Product IDs must be 100% identical across App Store Connect, Google Play Console, and your Unity C# configuration.

Using a reverse domain notation ensures global uniqueness across app stores:

  • Consumable: com.yourstudio.gamename.gems_pack_100
  • Non-Consumable: com.yourstudio.gamename.remove_ads
  • Subscription: com.yourstudio.gamename.vip_monthly

3. Comprehensive Unity IAP Architecture (IStoreListener)

To handle the complete purchasing lifecycle cleanly, implement the IDetailedStoreListener interface.

CSHARP
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Extension;

public class CompleteIAPManager : MonoBehaviour, IDetailedStoreListener
{
    private static CompleteIAPManager instance;
    public static CompleteIAPManager Instance => instance;

    private IStoreController storeController;
    private IExtensionProvider storeExtensionProvider;

    // Define Product IDs
    public const string PRODUCT_GEMS_100 = "com.yourstudio.gamename.gems_pack_100";
    public const string PRODUCT_REMOVE_ADS = "com.yourstudio.gamename.remove_ads";
    public const string PRODUCT_VIP_MONTHLY = "com.yourstudio.gamename.vip_monthly";

    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject);
            return;
        }
        instance = this;
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        if (!IsInitialized())
        {
            InitializePurchasing();
        }
    }

    public bool IsInitialized() => storeController != null && storeExtensionProvider != null;

    // =========================================================================
    // SECTION A: INITIALIZATION
    // =========================================================================
    public void InitializePurchasing()
    {
        Debug.Log("[IAP] Initializing Unity IAP Module...");
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

        // Register Product Catalog
        builder.AddProduct(PRODUCT_GEMS_100, ProductType.Consumable);
        builder.AddProduct(PRODUCT_REMOVE_ADS, ProductType.NonConsumable);
        builder.AddProduct(PRODUCT_VIP_MONTHLY, ProductType.Subscription);

        UnityPurchasing.Initialize(this, builder);
    }

    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        storeController = controller;
        storeExtensionProvider = extensions;
        Debug.Log("[IAP] Unity IAP Successfully Initialized with Store Controller.");
        
        // Log Localized Prices
        foreach (var product in storeController.products.all)
        {
            if (product.availableToPurchase)
            {
                Debug.Log($"[IAP Catalog] {product.definition.id} -> {product.metadata.localizedPriceString}");
            }
        }
    }

    public void OnInitializeFailed(InitializationFailureReason error)
    {
        Debug.LogError($"[IAP Error] Store Initialization Failed: {error}");
    }

    public void OnInitializeFailed(InitializationFailureReason error, string message)
    {
        Debug.LogError($"[IAP Error] Store Initialization Failed: {error} | Detail: {message}");
    }

    // =========================================================================
    // SECTION B: INITIATING PURCHASES
    // =========================================================================
    public void BuyProduct(string productId)
    {
        if (!IsInitialized())
        {
            Debug.LogWarning("[IAP Warning] Cannot purchase; IAP engine not initialized.");
            return;
        }

        Product product = storeController.products.WithID(productId);
        if (product != null && product.availableToPurchase)
        {
            Debug.Log($"[IAP] Initiating purchase for Product ID: {product.definition.id}");
            storeController.InitiatePurchase(product);
        }
        else
        {
            Debug.LogError($"[IAP Error] Product '{productId}' is unavailable or not found in store catalog.");
        }
    }

    // =========================================================================
    // SECTION C: PROCESSING PURCHASES & RECEIPT VALIDATION
    // =========================================================================
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    {
        Product purchasedProduct = args.purchasedProduct;
        Debug.Log($"[IAP Transaction] Processing Purchase for ID: {purchasedProduct.definition.id}");

        // Perform Receipt Validation
        bool isReceiptValid = ValidatePurchaseReceipt(purchasedProduct.receipt);

        if (!isReceiptValid)
        {
            Debug.LogError($"[IAP Security] Fraud Alert: Invalid receipt detected for {purchasedProduct.definition.id}");
            return PurchaseProcessingResult.Pending; // Keep pending until verified or cancelled
        }

        // Grant Content based on Product ID
        switch (purchasedProduct.definition.id)
        {
            case PRODUCT_GEMS_100:
                GrantConsumableGems(100);
                break;

            case PRODUCT_REMOVE_ADS:
                GrantRemoveAds();
                break;

            case PRODUCT_VIP_MONTHLY:
                GrantVIPSubscription();
                break;

            default:
                Debug.LogWarning($"[IAP] Unknown Product ID: {purchasedProduct.definition.id}");
                break;
        }

        Debug.Log($"[IAP Transaction] Successfully granted rewards for {purchasedProduct.definition.id}");
        return PurchaseProcessingResult.Complete;
    }

    // =========================================================================
    // SECTION D: HANDLING FAILURE & CANCELLATION
    // =========================================================================
    public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    {
        Debug.LogError($"[IAP Failed] Purchase of {product.definition.id} failed due to: {failureReason}");
        
        switch (failureReason)
        {
            case PurchaseFailureReason.UserCancelled:
                Debug.Log("[IAP Info] User voluntarily cancelled the purchase dialog.");
                break;
            case PurchaseFailureReason.PaymentDeclined:
                ShowUIErrorMessage("Payment declined by bank or app store.");
                break;
            case PurchaseFailureReason.PurchasingUnavailable:
                ShowUIErrorMessage("In-App Purchases are disabled in device settings.");
                break;
            default:
                ShowUIErrorMessage($"Purchase failed: {failureReason}");
                break;
        }
    }

    public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
    {
        Debug.LogError($"[IAP Failed Detail] Product: {product.definition.id} | Reason: {failureDescription.reason} | Message: {failureDescription.message}");
    }

    // =========================================================================
    // SECTION E: RESTORING PURCHASES (MANDATORY FOR APPLE iOS)
    // =========================================================================
    public void RestorePurchases()
    {
        if (!IsInitialized())
        {
            Debug.LogWarning("[IAP Warning] Cannot restore purchases; store not initialized.");
            return;
        }

#if UNITY_IOS
        Debug.Log("[IAP] Initiating Apple Restore Purchases flow...");
        var apple = storeExtensionProvider.GetExtension<IAppleExtensions>();
        apple.RestoreTransactions((result, message) =>
        {
            Debug.Log($"[IAP Restore] Result: {result} | Message: {message}");
        });
#elif UNITY_ANDROID
        Debug.Log("[IAP] Google Play automatically restores non-consumables upon initialization.");
#endif
    }

    // =========================================================================
    // SECTION F: RECEIPT VALIDATION LOGIC
    // =========================================================================
    private bool ValidatePurchaseReceipt(string receiptPayload)
    {
        if (string.IsNullOrEmpty(receiptPayload))
        {
            return false;
        }

        // Production Best Practice: Send receipt payload to Firebase Cloud Function or PlayFab
        // to verify with Apple App Store Server API V2 or Google Play Developer API
        return true; 
    }

    private void GrantConsumableGems(int count) { PlayerPrefs.SetInt("UserGems", PlayerPrefs.GetInt("UserGems", 0) + count); }
    private void GrantRemoveAds() { PlayerPrefs.SetInt("RemoveAds", 1); }
    private void GrantVIPSubscription() { PlayerPrefs.SetInt("VIPActive", 1); }
    private void ShowUIErrorMessage(string msg) { Debug.Log($"[IAP UI Error] {msg}"); }
}

4. Deep-Dive Subsystems & Workflow Mechanics

A. Initialization Flow (OnInitialized)

  • Call UnityPurchasing.Initialize() early in your game's splash screen or main menu loading loop.
  • Retrieve localized pricing (product.metadata.localizedPriceString) directly from the store to display prices in the user's native currency (e.g. ₹799, $9.99, €10.99).

B. Purchase Processing (ProcessPurchase)

[!CAUTION] Always return PurchaseProcessingResult.Complete only after your reward logic finishes. If you return Complete before saving the user state, a crash mid-transaction will cause the player to lose their purchased item permanently.

C. Failure & Cancellation Handling (OnPurchaseFailed)

Differentiate between a voluntary user cancellation (PurchaseFailureReason.UserCancelled) and technical errors (PaymentDeclined, DuplicateTransaction). Do not display error popups for user cancellations, as this interrupts gameplay unnecessarily.

D. Restoring Purchases (Apple Store Guideline 3.1.1)

  • Apple strictly requires a visible "Restore Purchases" button in your UI for all iOS apps with non-consumables or subscriptions.
  • Omitting this button will trigger an automatic rejection during App Store review. See our full Mobile Game Publishing Checklist: App Store & Google Play → for full submission compliance.

E. Local & Server-Side Receipt Validation

  • Local Validation: Unity provides local receipt parsing via CrossPlatformValidator.
  • Server Validation: The gold standard. Send the raw receipt JSON payload to a backend endpoint (Firebase Cloud Function or PlayFab) to query Apple's https://buy.itunes.apple.com/verifyReceipt or Google Play Developer API. Learn more in our Mobile Game Backend & Cloud Costs Guide →.

5. Production IAP Best Practices

[!TIP] Follow these engineering rules to ensure 99.5%+ transaction reliability and zero store rejections.

  1. Implement Server-Side Validation: Never rely solely on client-side state for high-value items.
  2. Apply for Small Business Programs: Both Apple and Google reduce their fee from 30% to 15% for developers earning under $1 million annually.
  3. Use Deferred Purchases for Google Play: Handle pending purchases (e.g. cash payments at convenience stores via Google Play) by keeping the transaction in PurchaseProcessingResult.Pending until Google sends confirmation.
  4. Cache Catalog Metadata: Store localized product titles and price strings locally so the store UI renders gracefully even during offline sessions.
  5. Track ARPU Telemetry: Correlate purchase events with retention cohorts using the GameAnalytics Unity SDK →.

🎮 Shipped Mobile Games & Official Store Profiles

Explore live commercial titles built with Unity and published across official app stores:


Need an expert engineering audit for your Unity mobile game monetization architecture? Check out our full breakdown in the 2026 Mobile Game Development Cost Guide or book an engineering consultation.

Share this article

Looking to build a production-ready game?

See how I built Bird Sort Mania in 20 days using AI, or check out my full Mobile Games Portfolio to see my shipped titles on Android and iOS.

Join 5,000+ Game Developers

Get weekly insights on Unity performance optimization, AI gameplay architectures, and robust system design. No spam, just deep technical breakdowns.

Unsubscribe at any time. Your data is never shared.

Recommended Reading

More articles in Unity IAP