Documentation

Set up and initialize In-App Purchasing

Install the In-App Purchasing package and write the startup script that signs the player in, connects to your stores, and fetches your product catalog.
Read time 14 minutesLast updated 17 hours ago

Unity In-App Purchasing (IAP) sells products through platform-native stores, such as the Apple App Store and Google Play, and through Direct-to-Consumer (D2C) payment providers, such as Stripe and Coda. Both paths share one setup: install the package, enable the service, and run a startup script that connects your application to the stores you sell through.
You can enable platform-native stores, D2C payment providers, or both. When you enable both, players choose how to pay through the Purchase Options UI.
Note
If your project already initializes Unity Gaming Services (UGS) and signs the player in, skip steps 1 to 3 of the startup script and start at the step that connects to your stores.

Prerequisites

Before you begin, make sure you meet the following requirements:
  • A Unity project that runs on a supported Unity version. Unity recommends Unity 6.0 or later, and supports Unity 2022.3 as the minimum version.
  • Build support modules installed for each platform you target, such as Android Build Support or iOS Build Support.
Important
If you use Unity 2022.3 to build for Android, refer to Troubleshooting In-App Purchasing 5.4.0 Android builds on Unity 2022.3 for the required build configuration.

Install the In-App Purchasing package

To install the IAP package, follow these steps:
  1. In the Unity Editor, select Window > Package Management > Package Manager.
  2. Select the Unity Registry tab.
  3. Search for In-App Purchasing, or
    com.unity.purchasing
    .
  4. Select the package, then select Install.
When you link your Unity project to a cloud project, the project gets the Unity Project ID that UGS requires at runtime.
Complete this task if you use the Remote Catalog, D2C payment providers, Unity Authentication, IAP analytics, a Google Play license key, or any other Unity service. A project that sells only through platform-native stores and defines its products in a local catalog can skip this step.
To link your project and enable IAP, follow these steps:
  1. In the Unity Editor, select Services > General Settings. Unity opens the Services General Settings page.
  2. Set Organization to your organization.
  3. Link a cloud project in one of the following ways:
    • To use an existing cloud project, set Cloud project to that project, then select Link Unity project to cloud project.
    • To create a new cloud project, select Create a new cloud project and follow the steps.
  4. Confirm that the Unity Project ID appears.
  5. Select Services > In-App Purchasing.
  6. Enable In-App Purchases.
  7. If you sell through Google Play, add your Google license key. For more information, refer to Add a Google license key to your project.
Linking your project to a cloud project and adding your Google license key lets Unity verify your Google Play transactions, so your revenue reporting counts them as verified. From In-App Purchasing 5.4.4, Android builds log a warning until you complete both.

Set your environment

The environment you select in the Unity Editor determines where IAP runs after you initialize UGS in code.
To set your environment, follow these steps:
  1. In the Unity Editor, select Edit > Project Settings > Services > Environments.
  2. Select the environment you want IAP to run in.

Choose how you define your products

Before you write your startup script, decide where your product definitions come from. Your choice determines which steps the script performs.

Catalog

When to use it

What the startup script does

Remote CatalogRecommended for all projects, and required for D2C payment providers. You can update your products without shipping a build.Signs the player in, connects to your stores, and fetches the catalog at runtime.
Local catalogProjects that sell only through platform-native stores and ship a fixed product list with each build.Connects to the store and fetches products from the catalog in the build. No sign-in.
A local catalog still works. If you use one, you don't need the Remote Catalog steps on this page, and you can skip signing the player in. Unity recommends that you import or migrate your catalog to the Remote Catalog service, because it lets you change prices and products between releases and is the only option that supports D2C payment providers.

Write the startup script

Your startup script performs the following steps in order:
  1. Initialize Unity Gaming Services.
  2. Set up Authentication event handlers.
  3. Sign the player in.
  4. Get a
    StoreController
    for each store you sell through, and connect to it.
  5. Fetch the Remote Catalog and pass its products to each store.
Important
Don't use anonymous sign-in as your authentication method in production. An anonymous player loses their purchase history when they reinstall your application or clear its data. If frictionless platform-specific providers aren't viable for your application, refer to anonymous authentication and linking.
The following script initializes a project for platform-native stores and D2C payment providers at the same time. Each store has its own toggle, so you can enable either one or both. Attach the script to a GameObject in your first scene.
Important
The GameObject you attach the script to must live for as long as your application sells anything. Unity destroys an ordinary GameObject when its scene unloads, and
OnDestroy
detaches the handlers that fulfill and confirm purchases. The store itself keeps running, because Unity IAP holds the store connection, not your GameObject. If your first scene unloads, call
DontDestroyOnLoad
on the GameObject, or put the script on an object that already persists.
using System;using System.Collections.Generic;using System.Threading.Tasks;using Unity.Services.Authentication;using Unity.Services.Core;using UnityEngine;using UnityEngine.Purchasing;using UnityEngine.Purchasing.Extension;public class IapInitializer : MonoBehaviour{ // Native stores (App Store, Google Play) and IAP D2C Capabilities can run on their own or // together. With both on, the Purchase Options UI lets the player choose how to pay. [SerializeField] bool m_UseNative = true; [SerializeField] bool m_UseD2C = true; StoreController m_NativeStore; StoreController m_D2CStore; IPaymentOptionProvider m_PurchaseOptions; readonly IRemoteCatalogProvider m_Catalog = new RemoteCatalogProvider(); async void Start() { try { await SetUpAsync(); } catch (Exception e) { Debug.LogError($"In-App Purchasing setup failed: {e.Message}"); } } async Task SetUpAsync() { // 1. Initialize Unity Gaming Services. Skip steps 1 to 3 if your project // already does this. await UnityServices.InitializeAsync(); // 2. Attach the Authentication handlers before you sign the player in, so // that no event is missed. AuthenticationService.Instance.SignedIn += OnSignedIn; AuthenticationService.Instance.SignInFailed += OnSignInFailed; AuthenticationService.Instance.SignedOut += OnSignedOut; // 3. Sign in. The Remote Catalog and IAP D2C Capabilities need a signed-in player. // Replace anonymous sign-in with your own authentication: an anonymous player loses // their purchase history when the app is reinstalled or its data is cleared. if (!AuthenticationService.Instance.IsSignedIn) { await AuthenticationService.Instance.SignInAnonymouslyAsync(); } // 4. Get one StoreController per kind of store. Subscribe to every event // before you call Connect(), or you lose the events that fire first. if (m_UseNative) { m_NativeStore = UnityIAPServices.StoreController(); Subscribe(m_NativeStore); m_NativeStore.OnPurchasePending += OnNativePurchasePending; await m_NativeStore.Connect(); } if (m_UseD2C) { m_D2CStore = UnityIAPServices.StoreController(PaymentProvider.Name); Subscribe(m_D2CStore); m_D2CStore.OnPurchasePending += OnD2CPurchasePending; await m_D2CStore.Connect(); m_PurchaseOptions = m_D2CStore.PaymentProviderStoreExtendedService?.GetPaymentOptionProviderUGUI(); } // 5. One Remote Catalog works for every store. var result = await m_Catalog.FetchRemoteCatalog(); if (!result.Success) { Debug.LogError($"Could not fetch the Remote Catalog: {result.Exception?.Message}"); return; } if (m_NativeStore != null) { m_Catalog.FetchProducts(m_NativeStore.FetchProductsWithNoRetries, UnityIAPServices.GetDefaultStore()); } if (m_D2CStore != null) { m_Catalog.FetchProducts(m_D2CStore.FetchProductsWithNoRetries, PaymentProvider.Name); } } // Call this from your buy button. public async void Buy(string catalogListingId) { if (m_PurchaseOptions != null) { // This offers every store that has this listing: native, D2C, // and webshop. With a single option, it buys directly without // showing the picker. await m_PurchaseOptions.ShowPurchaseOption(catalogListingId); return; } // No D2C store, so buy from the native store. if (m_NativeStore != null) { m_NativeStore.PurchaseProduct(catalogListingId); } else { Debug.LogWarning($"No way to pay for {catalogListingId}."); } } // A purchase arrives on the controller of the store the player paid with, // so each controller confirms its own orders. void OnNativePurchasePending(PendingOrder order) { // Grant the content for the order here, then confirm it. m_NativeStore.ConfirmPurchase(order); } void OnD2CPurchasePending(PendingOrder order) { // Grant the content for the order here, then confirm it. m_D2CStore.ConfirmPurchase(order); } void Subscribe(StoreController store) { store.OnStoreDisconnected += OnStoreDisconnected; store.OnProductsFetched += OnProductsFetched; store.OnProductsFetchFailed += OnProductsFetchFailed; } void Unsubscribe(StoreController store) { store.OnStoreDisconnected -= OnStoreDisconnected; store.OnProductsFetched -= OnProductsFetched; store.OnProductsFetchFailed -= OnProductsFetchFailed; } void OnProductsFetched(List<Product> products) => Debug.Log($"{products.Count} products are ready to display."); void OnProductsFetchFailed(ProductFetchFailed failure) => Debug.LogError($"Product fetch failed: {failure.FailureReason}"); void OnStoreDisconnected(StoreConnectionFailureDescription description) => Debug.LogError($"Store disconnected: {description.message}"); void OnSignedIn() => Debug.Log($"Signed in as {AuthenticationService.Instance.PlayerId}."); void OnSignInFailed(RequestFailedException e) => Debug.LogError($"Sign-in failed: {e.Message}"); void OnSignedOut() => Debug.Log("Signed out."); void OnDestroy() { if (m_NativeStore != null) { Unsubscribe(m_NativeStore); m_NativeStore.OnPurchasePending -= OnNativePurchasePending; } if (m_D2CStore != null) { Unsubscribe(m_D2CStore); m_D2CStore.OnPurchasePending -= OnD2CPurchasePending; } AuthenticationService.Instance.SignedIn -= OnSignedIn; AuthenticationService.Instance.SignInFailed -= OnSignInFailed; AuthenticationService.Instance.SignedOut -= OnSignedOut; }}
After the script runs, your application is connected to each store you enabled and holds the products from your catalog, so it can start a purchase.
The following details of the script might not be clear from the code sample alone:
  • The script needs both
    using UnityEngine.Purchasing;
    and
    using UnityEngine.Purchasing.Extension;
    .
    RemoteCatalogProvider
    and
    IRemoteCatalogProvider
    are in different namespaces, and if you omit either directive, the type doesn't resolve.
  • The script initializes IAP in
    Start
    rather than
    Awake
    , because it awaits UGS initialization and sign-in, which can't complete in
    Awake
    .
  • The
    IsSignedIn
    guard covers a sign-in that's already complete, but not one that's still in progress.
    SignInAnonymouslyAsync
    throws in both cases, so a project that signs players in elsewhere must remove steps 1 to 3 instead of relying on the guard.
  • The script doesn't cover a change of signed-in player, because anonymous sign-in keeps the same player for the session. If players can switch accounts while your application runs, attach an
    OnAuthAccountChanged
    handler to each store and re-fetch from it. Refer to Handle an account change.

Connect to each store

StoreController
is the main interface for IAP functionality. Call
UnityIAPServices.StoreController
to get one. The store name you pass is the only difference between the two kinds of store:
  • For platform-native stores, call
    UnityIAPServices.StoreController()
    with no argument to get the controller for the default store.
  • For D2C payment providers, call
    UnityIAPServices.StoreController(PaymentProvider.Name)
    .
Each kind of store needs its own
StoreController
. A project that sells through both keeps two controllers for the lifetime of the application.
Important
PaymentProvider.Name
identifies the D2C payment provider integration in configuration. It doesn't return the display name of a provider.
Attach handlers to the
StoreController
events before you call
Connect
, or you lose the events that fire during connection. Attach handlers to the following events at minimum:
Connect
returns a
Task
that resolves when the connection completes or fails, and a failure invokes
OnStoreDisconnected
. You must connect to a store before you use any other IAP functionality on it. For the full list of events, refer to Events in the
StoreController
API documentation.

Fetch the Remote Catalog

Instantiate a
RemoteCatalogProvider
and call
FetchRemoteCatalog
to retrieve your products from the Remote Catalog service. One provider serves every store: the Remote Catalog maps each listing to the native store IDs it carries, so the same fetch supplies a native store and a D2C payment provider in the same application.
Pass the catalog's products to each store with
FetchProducts
, along with the name of the store that receives them. Use
UnityIAPServices.GetDefaultStore()
for a platform-native store and
PaymentProvider.Name
for a D2C payment provider.
Important
Pass
FetchProductsWithNoRetries
as the callback, not
FetchProducts
.
RemoteCatalogProvider.FetchProducts
expects an
Action<List<ProductDefinition>>
, and
StoreController.FetchProducts
takes an optional
IRetryPolicy
parameter. Optional parameters don't participate in method group conversion, so passing
StoreController.FetchProducts
doesn't compile.
You can call
FetchProducts
more than once at runtime, but wait for each call to finish before you make the next one.
FetchProducts
appends its results to the list that
GetProducts
returns, so the two methods aren't interchangeable. Call
FetchProducts
during initialization.

Fetch products from a catalog created in the Editor

If you define your products with the IAP Catalog window instead of the Remote Catalog, replace the
RemoteCatalogProvider
in the script with a
CatalogProvider
built from the local catalog:
// Load the catalog created in the Editor.ProductCatalog catalog = ProductCatalog.LoadDefaultCatalog();CatalogProvider catalogProvider = CodelessCatalogProvider.PopulateCatalogProvider(catalog);
ProductCatalog.LoadDefaultCatalog
loads the catalog in your build, and
CodelessCatalogProvider.PopulateCatalogProvider
returns a
CatalogProvider
populated with its products. Call
FetchProducts
on that provider in place of the Remote Catalog fetch. You can also define a catalog in code.
A local catalog doesn't support D2C payment providers.

Fetch purchases

Call
StoreController.FetchPurchases
to request the player's active orders, which include pending, confirmed, and deferred orders. Add the call to
OnProductsFetched
, so that it runs after the store returns your products.
This step isn't required to start using IAP, but Unity recommends that you fetch and handle existing purchases before you initiate new ones. Fetch purchases again whenever you fetch additional products at runtime.
FetchPurchases
invokes either
OnPurchasesFetched
, with an
Orders
object containing the orders the store returned, or
OnPurchasesFetchFailed
. On Google Play, it also invokes
OnPurchasePending
for any unprocessed purchases. On the Apple App Store,
OnPurchasePending
can fire for unprocessed orders after
FetchProducts
completes.
Unlike
FetchProducts
,
FetchPurchases
overwrites the list that
GetPurchases
returns rather than appending to it. For more information, refer to Purchases.

Handle an account change

When the signed-in player changes while your application runs, Unity IAP raises
OnAuthAccountChanged
on each of your stores. This event requires the Unity Authentication package (
com.unity.services.authentication
) version 3.0.0 or later.
A project that sells through both a platform-native store and a direct-to-consumer payment provider receives the event once for each store.
Important
Unity IAP clears a store's product and purchase caches immediately before it raises
OnAuthAccountChanged
, but only if that store has an
OnAuthAccountChanged
handler attached. If a store has no handler, it keeps the previous player's products and purchases. To prevent this, attach a handler to every store you connect to, and in each handler, fetch products and purchases from that store again. Controllers created with the same store name share one cache, so they need only one handler between them.
On the Apple App Store, Unity IAP also clears the product details that
GetProductDetails
and
GetIntroductoryPriceDictionary
return. If a product fetch is in progress when the account changes, Unity IAP fails that fetch instead of caching the previous player's results against the new account.
From your handler, fetch products and purchases for that store again in the same order as at startup:
  1. Fetch your catalog.
  2. Call
    FetchProducts
    .
  3. If your application fetches purchases, call
    FetchPurchases
    .
Don't read
GetProducts
or
GetPurchases
in the handler, because Unity IAP has already emptied both.
Important
Construct a new
RemoteCatalogProvider
before you fetch the catalog again.
FetchRemoteCatalog
adds to the products that the provider already holds instead of replacing them, so a reused provider duplicates every listing.

Start a purchase

Call
Buy
from your buy button, with the
catalogListingId
of the listing you want to sell.
When you enable D2C payment providers,
Buy
shows the Purchase Options UI, which presents every way the player can pay for that listing: the platform-native store, an eligible D2C payment provider, and a webshop if the listing has one. If only one option is available, the Purchase Options UI skips the choice and completes the purchase through that option. When you don't enable D2C payment providers,
Buy
sends the purchase to the platform-native store.
A completed purchase arrives on the
StoreController
of the store the player paid through, so each controller handles and confirms its own orders. Grant the player their content in
OnPurchasePending
, then call
ConfirmPurchase
to close the order.
Important
Always confirm orders with Unity IAP, even if you also manage entitlements on your own server. For consumable products, Unity IAP prevents a player from buying the same item again until you confirm the previous order.
For more information, refer to Fulfill purchases through the SDK.

Next steps

Additional resources