IAP (In App Purchasing) - VirtueSky/sunflower_2 GitHub Wiki

Setup

1. Add IapManager to the Scene

Create a GameObject in your scene (typically the first scene or Loading scene) and attach the IapManager component to it.

IapManager Inspector fields:

Field Description
Is Persistent DontDestroyOnLoad when changing scenes
Init Type When IAP initialization happens (see table below)
Skus Data Static product configuration list
Products (ReadOnly) Products created at runtime from Skus Data

Init Type options:

Value Initializes in
InitOnAwake Awake() of IapManager
InitOnEnable OnEnable() of IapManager
InitOnStart Start() of IapManager
InitManually Call IapManager.Initialization() manually

2. Configure Skus Data

Add products directly to the Skus Data list on the IapManager component:

Android Id   : com.yourapp.removeads
iOS Id       : com.yourapp.removeads
Product Type : NonConsumable
Price Config : 2.99

At runtime, IapManager will automatically create the corresponding IapDataProduct entries in Products.

3. Configure Validate Purchase (IapSettings)

Open IapSettings (via Magic Panel or find the asset in the Project window):

  • Enable Is Validate Purchase to validate receipts
  • Enter Google Play Store Key, then click Obfuscator Key
  • Enable Is Custom Validate Purchase to use a custom validator

Accessing Products

// Get an IapDataProduct by id
IapDataProduct product = IapManager.GetIapProduct("com.yourapp.removeads");

// Or get the full list
List<IapDataProduct> allProducts = IapManager.Products;

Handling Purchases

Option 1 — Direct callback (for UI):

public void OnClickRemoveAds()
{
    var productRemoveAds = IapManager.GetIapProduct("com.yourapp.removeads");
    productRemoveAds.OnCompleted(() =>
    {
        // handle success
    }).OnFailed(error =>
    {
        // handle failed
    }).Purchase();
}

public void OnClick1000Gem()
{
    var product1000Gem = IapManager.PurchaseProduct("com.yourapp.1000gem");
    product1000Gem.OnCompleted(() =>
    {
        // handle success
    }).OnFailed(error =>
    {
        // handle failed
    });
}

Option 2 — Global events (for game logic and restore purchase):

public class HandlePurchaseIap : MonoBehaviour
{
    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
        IapManager.OnPurchaseSucceedEvent += HandlePurchaseSuccess;
        IapManager.OnPurchaseFailedEvent += HandlePurchaseFailed;
    }

    private void OnDestroy()
    {
        IapManager.OnPurchaseSucceedEvent -= HandlePurchaseSuccess;
        IapManager.OnPurchaseFailedEvent -= HandlePurchaseFailed;
    }

    void HandlePurchaseSuccess(string id)
    {
        switch (id)
        {
            case "com.yourapp.removeads":
                // remove ads
                break;
            case "com.yourapp.1000gem":
                // grant gem
                break;
        }
    }

    void HandlePurchaseFailed(string id, string error)
    {
        Debug.LogWarning($"Purchase failed: {id} - {error}");
    }
}

Note: Option 1 is suitable for temporary UI feedback after a purchase. Option 2 should be used for game logic (remove ads, unlock content, etc.) as it also handles restore purchase correctly.


Checking if a Product is Purchased (NonConsumable only)

private void OnEnable()
{
    bool isPurchased = IapManager.IsPurchasedProduct("com.yourapp.removeads");
    buttonRemoveAds.gameObject.SetActive(!isPurchased);
}
// Or
private void OnEnable()
{
    var productRemoveAds = IapManager.GetIapProduct("com.yourapp.removeads");
    bool isPurchased = productRemoveAds.IsPurchased();
    buttonRemoveAds.gameObject.SetActive(!isPurchased);
}

Adding Products Dynamically from Remote Config

Use InitManually and call AddProduct before Initialization():

public class GameBootstrap : MonoBehaviour
{
    private async void Start()
    {
        // Fetch remote config first
        await RemoteConfigService.Instance.FetchAsync();

        // Create and add products from remote data
        string packId = RemoteConfig.GetString("special_pack_id");
        float packPrice = RemoteConfig.GetFloat("special_pack_price");

        var dynamicProduct = new IapDataProduct(packId, packId, IapProductType.Consumable, packPrice);
        IapManager.AddProduct(dynamicProduct); // skipped silently if id already exists

        // Initialize IAP after all products have been registered
        IapManager.Initialization();
    }
}

Important: AddProduct must be called before IapManager.Initialization() runs. Once initialization has started, AddProduct calls will be ignored and a warning will be logged.


Restore Purchase (iOS)

Restore purchase only applies to NonConsumable products.

  • Android: Automatically restored on reinstall via OnInitialized.
  • iOS: A Restore Purchase button is required to publish on the App Store.
public void OnClickRestorePurchase()
{
#if UNITY_IOS
    IapManager.RestorePurchase();
#endif
}

When restore succeeds, the purchase callback for each product fires automatically — handle game logic via Option 2 (global events) to ensure it works correctly on restore.