IAP tutorial series: Upgrading from Unity IAP4 to IAP5

Hi. This is the first article of the new IAP5 series, which consists of 3 video tutorials with companion articles covering the following topics:

  • Upgrading from IAP4 to IAP5
  • Implementing Payment providers (coming soon)
  • Creating our webshop (coming soon)

The transition from Unity In-App Purchasing (IAP) 4 to version 5 introduces major architectural improvements. IAP5 offers streamlined product fetching, improved error handling, and better initialization workflow. Crucially, version 5 paves the way for advanced web-to-mobile monetization channels, integration with custom payment processors like Stripe or Coda, and standalone webshops which we will cover in later articles.

This article is a written version of the accompanying YouTube tutorial, in this article we expand on some aspects.

In addition to the video series, you can get the following infographic showing the whole purchase flow including IAP5 and the new features, webshops and payment providers.

IAP V5 infographic.pdf (1.9 MB)

1.Setup

Upgrading the package inside the Unity Package Manager will immediately prompt compilation errors due to changed type definitions and interface deprecations.

One of the immediate breaking modifications is how store-specific IDs are instantiated. The legacy IDs class has been renamed to better reflect its purpose:

  • Legacy type: new IDs()
  • Refactored type: new StoreSpecificIds()

If you want to follow along looking at an implementation of IAP5 code, you can get the sample in the Package Manager, in the In-App Purchasing package, and the Minimal Coded IAP Sample.

Open the script PaywallManager for IAP5 code.

In IAP4, classes handling purchases had to implement the IStoreListener or IDetailedStoreListener interfaces for store initialization and purchase processing.

IAP v5 completely eliminates these interface dependencies. Instead, it consolidates interactions around a concrete UnityIAPServices.StoreController() class that fires targeted events throughout the purchase lifecycle.

2. Initialization

Legacy initialization required packaging product IDs into a ConfigurationBuilder prior to initializing Unity Purchasing. Under IAP5, initialization operates asynchronously and explicitly requests platform connections.

Note: before using UnityIAPServices, initialize the Unity Gaming Services with await UnityServices.InitializeAsync(options). We recommend doing this at the start of the game and demonstrate this in a single entry point pattern in the script GameInitializer in our GemHunter Unity Gaming Services project demo.

3. StoreController events

Everything is handled by the StoreController in IAP5 instead of being split into IStoreListener and IStoreController like in IAP4.

Once we save a reference to the StoreController we subscribe to its events. They are self explanatory. Once we have it, we connect to the stores (Google Play or AppStore) with Connect(), in this event (OnStoreConnected and OnStoreDisconnected) we can enable and disable UI buttons relative to purchases.

4. Product and purchase fetching

Once connected we send our product definitions to the store with m_StoreController.FetchProducts(products); basically, we send AppStore or Google Play a list of products asking for the actual price and purchase description in the user’s device language.

In IAP5 we don’t use configuration builder anymore from IAP4. In IAP5, we pass product definition objects directly to FetchProducts(products) whenever you are ready. They have an ID and type so there’s less platform specific code to write compared to IAP4.

We can create our product definitions with List products = new List(); and add products in two ways:

products = BuildProductDefinitionsFromIAPCatalog();
  • Add the product list programmatically
products.Add(new ProductDefinition("gem_chest_01", ProductType.Consumable));
products.Add(new ProductDefinition("premium_subscription", ProductType.Subscription));

We can then fetch the products and use the events listeners to log the information retrieved.

When products are fetched we can then check if any purchase is pending to process with m_StoreController.FetchPurchases()

Non-completed purchases that were interrupted or postponed have a chance to be completed here with our OnPurchaseFetched(Orders orders) event listener.

For every pending purchase in this session we will also get a OnPurchasePending event to handle them but there’s a chance that these will slip through depending on iOS version. To make sure nothing is missed we can process them in our orders.PendingOders.

Even if both (OnPurchasePending and our ProcessPurchaseAsync(order) function) get fired we can avoid duplication which we show later.

We can also retrieve the confirmed purchases. If you have non-consumable or subscriptions. Products that are meant to be purchased only once, for example, and ads removal products can be entitled/enabled right away.

As a last step in the initialization process we can initialize receipt validators if needed.

Apple stores validates receipts with StoreKit2 so by the time we get the receipt on OnPurchasePending it will be already validated. Google Play handles this differently, requiring an identifier, which is your google license key via the obfuscator tool in the IAP package, and this is where we use our cross platform validation for.

Note: With IAP 5, the intent was to just support StoreKit2 and the new jws transaction representations. Apple handles on-device validation for us with this flow, and these also allow you to validate specific transactions, instead of “all” of the player’s transaction in one big file.

You can still get the StoreKit1 receipt but they’re not especially reliable when using StoreKit2.

Now with StoreKit1 in newer versions of IAP 5.x, you can force the use of StoreKit1 on all devices, or just have devices on iOS versions before 15.0, where StoreKit2 is not supported, run StoreKit1. In these cases, developers can interact with StoreKit1 (or rather they must interact with it), where there is no jws transaction representation, only (more reliable) receipt access

5. Purchase flow

UI handlers can stay the same when moving from IA4 to IAP5, they simply call to initiate purchase with the product ID.

a. Initialization

We can use a flag to disable buttons while a current purchase is in process to avoid accidental duplicated requests.
We can retrieve all the products to populate a store listing with m_StoreController.GetProducts(), but in this menu we already know which product we want to offer so we get the product with m_StoreController.GetProductById(productId).

Since we might get this event triggered twice, from OnPurchasePending and our ProcessPurchaseAsync(order) function or to avoid duplicated requests, we want to avoid granting the player the purchases more than once. It’s good practice to check the processed

  • (from OnPurchasesConfirmed) in the session against the ones pending.

Transaction Ids can be used in our Cloud Code for stronger server side verification and granting of purchases to players via Economy. We cover this topic and more in the Unity Gaming Services series that you can watch in Unity’s Youtube Playlist.

Lastly, we can run Google Play validation, grant the player the items and confirm the purchase to the store with m_StoreController.ConfirmPurchase(pendingOrder)

Confirm ensures that the whole purchase flow was completed however OnPurchaseFailed will be triggered if the order failed. We should handle the different reasons accordingly, if the user canceled the purchase there’s no need to inform the player, but if it failed for other reasons we should inform the player about it.

Since IAP5 uses events, remember to unsubscribe to them when the manager is removed

We experienced first hand the process of upgrading to IAP5 our Gem Hunter Match cloud edition, and while the repository still needs updating we updated the purchase scripts to IAP5. In this project, you can observe a more complex but realistic set up for a project where many validations are made server side with Cloud Code.

GameInitializer.cs (9.4 KB)
StoreClient.cs (8.7 KB)
StoreClient_IAP5.cs (20.7 KB)

In the following articles of this 3-part serie we will look into:

  • Implementing Payment providers
  • Creating our webshop
8 Likes

This is incredibly useful, when will the payment providers for us to use become live? You said coming soon so wanted to ask

1 Like

mm keep an eye on the Unity comms next week :slight_smile:

Thanks @eduardooriz

I’ll be starting on the IAP4 to IAP5 upgrade soon.

Am I right in saying that IAP5 doesn’t require Unity Analytics or Unity Authentication to be installed? (unless using D2C)

Having either of these active (in order to support IAP5) is going to create a big problem for me (in terms of privacy & listing declarations etc)

1 Like

Hi David,

You are correct, neither Unity Analytics nor Unity Authentication is required by IAP 5 unless you are using D2C.

D2C requires authentication in order to maintain your user’s identity across devices/installs and sessions for entitlement purposes.

1 Like

That’s great news, thanks @Laurie-Unity

is the edge case with pending purchases not firing correctly on some minor version of iOS only happening in StoreKit 2 or does it also applies to StoreKit1? Mentioned here:

This should not be considered StoreKit 2-specific. The problem is with the underlying iOS transaction flow on those OS versions so it could affect either API, though the way it surfaces may differ.

1 Like