> For the complete documentation index, see [llms.txt](https://assetstore.essentialkit.voxelbusters.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://assetstore.essentialkit.voxelbusters.com/migrations/gley/easy-iap/migration-guide.md).

# Migration Guide

## Table of Contents

* [Why Essential Kit Instead Of Unity IAP](#why-essential-kit-instead-of-unity-iap)
* [Quick API Cheat Sheet](#quick-api-cheat-sheet)
* [Before You Start](#1-before-you-start)
* [Remove The Old Dependency](#2-remove-the-old-dependency)
* [Configure Products In Essential Kit](#3-configure-products-in-essential-kit)
* [Replace Initialisation Code](#4-replace-initialisation-code)
* [Update Purchase Handling](#5-update-purchase-handling)
* [Restore Purchases](#6-restore-purchases)
* [Update UI Helpers (Price, Title, Reward Amount)](#7-update-ui-helpers-price-title-reward-amount)
* [Optional: Receipt Verification](#8-optional-receipt-verification)
* [Test And Clean Up](#9-test-and-clean-up)
* [Quick Checklist](#10-quick-checklist)
* [What You Gain With Essential Kit](#what-you-gain-with-essential-kit)
* [Need Help?](#need-help)

This guide distils the migration into the few changes you actually need to make. When you need deeper feature explanations, refer to `tutorials/v3/features/billing-services/usage.md`.

### Why Essential Kit Instead Of Unity IAP

* Direct native control so fixes and updates ship as soon as we build them (no Unity IAP dependency or review cycle).
* The Essential Kit team owns the roadmap and can respond to platform changes immediately.
* No analytics or telemetry packages bundled in; you keep full control over user data.
* Smaller project footprint with fewer package conflicts to manage.

### Quick API Cheat Sheet

| Task              | Gley Easy IAP                                               | Essential Kit                                                          |
| ----------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------- |
| Namespace         | `using Gley.EasyIAP;`                                       | `using VoxelBusters.EssentialKit; using VoxelBusters.CoreLibrary`      |
| Initialise store  | `API.Initialize(callback)`                                  | `BillingServices.InitializeStore()`                                    |
| Check can pay     | `API.IsInitialized()`                                       | `BillingServices.CanMakePayments()`                                    |
| Buy product       | `API.BuyProduct(ShopProductNames.Item, callback)`           | `BillingServices.BuyProduct("item_id")`                                |
| Purchase callback | `void OnPurchase(IAPOperationStatus, string, StoreProduct)` | `void HandleTransactions(BillingServicesTransactionStateChangeResult)` |
| Get product price | `API.GetLocalizedPriceString(ShopProductNames.Item)`        | `product.Price.LocalizedText`                                          |
| Grant reward      | `product.value`                                             | iterate `product.Payouts` (category/variant)                           |
| Restore purchases | `API.RestorePurchases(callback)`                            | `BillingServices.RestorePurchases(forceRefresh)`                       |
| Receipt string    | `product.receipt`                                           | `transaction.Receipt`                                                  |
| Check ownership   | `API.IsActive(ShopProductNames.RemoveAds)`                  | `BillingServices.IsProductPurchased("remove_ads")`                     |

## 1. Before You Start

* Export your existing product list (name, store IDs, product type, reward amount).
* Search your project for `Gley.EasyIAP` usages; keep this list beside you so nothing is missed.
* Decide when you will remove the Unity IAP and Gley packages (after you test the new flow is safest).

## 2. Remove The Old Dependency

1. In **Package Manager** remove **Unity IAP** if it is only used by Gley.
2. Delete the Gley Easy IAP asset folder from your project.

## 3. Configure Products In Essential Kit

1. Open **Window → Voxel Busters → Essential Kit → Open Settings → Services → Billing Services**.
2. For every product add:
   * **Product ID** – the identifier you use in code (e.g. `coins_100`).
   * **Product Type** – Consumable, NonConsumable, or Subscription.
   * **Store IDs** – iOS and Google Play identifiers exactly as created on each store.
   * **Payouts** (optional)– each reward is `Category + Variant + Quantity`. Use `Currency` for virtual currencies and name the variant clearly (e.g. `coins_primary`).
   * **Android Public Key** – paste the Base64-encoded RSA key from **Play Console → Monetization → Licensing** so Essential Kit can validate Google Play receipts.

Example:

```
Product ID: coins_100
Product Type: Consumable
iOS Store ID: com.yourgame.coins_100
Google Play ID: coins_100
Payouts:
  - Category: Currency
    Variant: coins_primary
    Quantity: 100
```

> Essential Kit keeps the category and variant so you can differentiate primary/secondary currencies later. On Android, an incorrect or missing public key leads to purchases staying in a pending state or failing verification, so double-check the value you paste.

## 4. Replace Initialisation Code

```csharp
using UnityEngine;
using VoxelBusters.CoreLibrary;
using VoxelBusters.EssentialKit;

public sealed class StoreBootstrap : MonoBehaviour
{
    void OnEnable()
    {
        BillingServices.OnInitializeStoreComplete += HandleStoreInitialised;
    }

    void OnDisable()
    {
        BillingServices.OnInitializeStoreComplete -= HandleStoreInitialised;
    }

    void Start()
    {
        BillingServices.InitializeStore();
    }

    private void HandleStoreInitialised(BillingServicesInitializeStoreResult result, Error error)
    {
        if (error != null)
        {
            Debug.LogError($"Billing store init failed: {error.Description}");
            return;
        }

        if (BillingServices.IsProductPurchased("remove_ads"))
        {
            DisableAds();
        }
    }

    private void DisableAds()
    {
        // Implement your existing remove-ads logic
    }
}
```

## 5. Update Purchase Handling

Subscribe once and centralise transaction handling. Notice the explicit null checks – `IBillingTransaction.Product` can be null if the product was removed from settings, so we guard before using it.

```csharp
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;

public sealed class StoreFront : MonoBehaviour
{
    [SerializeField] private int coins;

    void OnEnable()
    {
        BillingServices.OnTransactionStateChange += HandleTransactions;
    }

    void OnDisable()
    {
        BillingServices.OnTransactionStateChange -= HandleTransactions;
    }

    public void BuyCoins()
    {
        BillingServices.BuyProduct("coins_100");
    }

    private void HandleTransactions(BillingServicesTransactionStateChangeResult result)
    {
        foreach (var transaction in result.Transactions)
        {
            if (transaction.TransactionState != BillingTransactionState.Purchased)
                continue;

            var product = transaction.Product;
            if (product == null)
                continue; // Product not configured anymore – nothing to grant

            GrantReward(product);
        }
    }

    private void GrantReward(IBillingProduct product)
    {
        if (product.Id == "remove_ads")
        {
            DisableAds();
            return;
        }

        foreach (var payout in product.Payouts)
        {
            if (payout.Category == BillingProductPayoutCategory.Currency && payout.Variant == "coins_primary")
            {
                coins += (int)payout.Quantity;
            }
        }

        SaveProgress();
    }

    private void DisableAds() { /* existing code */ }
    private void SaveProgress() { /* existing code */ }
}
```

## 6. Restore Purchases

```csharp
void OnEnable()
{
    BillingServices.OnRestorePurchasesComplete += HandleRestoreComplete;
}

void OnDisable()
{
    BillingServices.OnRestorePurchasesComplete -= HandleRestoreComplete;
}

public void RestorePurchases()
{
    // Use forceRefresh:false so iOS does not show the login dialog automatically on launch
    BillingServices.RestorePurchases(forceRefresh: false);
}

private void HandleRestoreComplete(BillingServicesRestorePurchasesResult result, Error error)
{
    if (error != null)
    {
        Debug.LogError($"Restore failed: {error.Description}");
        return;
    }

    foreach (var transaction in result.Transactions)
    {
        var product = transaction.Product;
        if (product != null)
        {
            GrantReward(product);
        }
    }
}
```

Reuse the same `GrantReward` helper from the purchase path so your rewards stay in one place. When a user explicitly taps a “Restore Purchases” button on iOS, re-run `BillingServices.RestorePurchases(forceRefresh: true)` to present the App Store login dialog as required. See the [restore guidance](#id-6.-restore-purchases) for more detail.

## 7. Update UI Helpers (Price, Title, Reward Amount)

```csharp
var product = BillingServices.GetProductWithId("coins_100");
if (product != null)
{
    string localizedPrice = product.Price.LocalizedText;
    int reward = 0;

    foreach (var payout in product.Payouts)
    {
        if (payout.Category == BillingProductPayoutCategory.Currency && payout.Variant == "coins_primary")
        {
            reward = (int)payout.Quantity;
            break;
        }
    }

    coinsButton.text = $"Buy {reward} coins {localizedPrice}";
}
```

## 8. Optional: Receipt Verification

If you previously sent receipts to a server, keep doing it with the new API. Essential Kit performs local verification automatically; for server-side verification see `tutorials/v3/features/billing-services/usage.md#server-side-receipt-verification`.

```csharp
private void HandleTransactions(BillingServicesTransactionStateChangeResult result)
{
    foreach (var transaction in result.Transactions)
    {
        if (transaction.TransactionState != BillingTransactionState.Purchased)
            continue;

        SendReceiptToServer(transaction.Receipt, isValid =>
        {
            if (isValid && transaction.Product != null)
            {
                GrantReward(transaction.Product);
            }

            BillingServices.FinishTransactions(new[] { transaction });
        });
    }
}
```

## 9. Test And Clean Up

* Run through purchase, restore, and subscription scenarios on iOS and Android sandbox accounts.
* Interrupt a purchase to confirm pending transactions are processed on next launch.
* Once you ship with Essential Kit, delete any unused Gley scripts and `ShopProductNames` enums.

## 10. Quick Checklist

* [ ] Products recreated in Essential Kit settings with category/variant payouts.
* [ ] Code uses `BillingServices.InitializeStore()` and event handlers instead of Gley callbacks.
* [ ] Purchases, restores, and UI price displays updated.
* [ ] Receipt flow (if any) references the new API.
* [ ] Unity IAP and Gley Easy IAP assets removed after validation.

## What You Gain With Essential Kit

* Built-in receipt verification (local) with optional server extensions.
* Purchase quantity support via `BuyProductOptions`.
* Multiple payout definitions per product for multi-currency bundles.
* Pending transaction recovery handled automatically after initialization.
* Runtime configuration support (load product catalog from your backend and call `BillingServices.Initialize(settings)`).

## Need Help?

* Review the [Essential Kit](https://www.voxelbusters.com/products/essential-kit) product overview
* Chat with the team and other adopters on [Discord](https://discord.gg/Rw5SAec4Md)
* Browse the full [Billing Services usage guide](/features/billing-services/usage.md)

You are now running on Essential Kit’s native billing system. For advanced features (pending transactions, subscription status, offers) can refer [main tutorial](/features/billing-services/usage.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://assetstore.essentialkit.voxelbusters.com/migrations/gley/easy-iap/migration-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
