Documentation

Complete a test purchase

Run a simulated purchase end to end to verify your In-App Purchasing integration before you connect a payment provider.
Read time 10 minutesLast updated 5 days ago

A test purchase uses the simulated payment provider that Unity In-App Purchasing (IAP) applies when an environment is in test mode. For test purchases, Unity IAP creates and tracks the order, your fulfilment code runs, and your webhook or Cloud Code module receives the purchase events. No money moves through the simulated provider, and you don't need a provider account, credentials, routing rule, or redirect URL. A completed test purchase proves that your catalog, your purchase call, and your purchase handling work before you start onboarding with a provider.

Prerequisites

To complete a test purchase, you need the following:
  • At least one catalog listing in your Remote Catalog. Add a catalog listing in the Unity Dashboard, or author one in the Unity Editor and upload it to the Remote Catalog service.
  • A Unity Cloud project connected to your Unity project, with IAP v5.4 and Unity Authentication installed. For more information, refer to Integrate D2C payment providers prerequisites.
  • An environment with test mode enabled and no payment provider connected. Refer to Turn test mode on or off.
  • A way to run your game and trigger a purchase. You can test a purchase in the Editor or in a player build. Unity opens the simulated checkout page in a browser outside of your game.
If your game already sells through a native store such as the Apple App Store or Google Play, keep your existing setup. The scripts on this page add a second
StoreController
for D2C payments alongside your native store.

Run a simulated purchase

To run a simulated purchase, follow these steps:
1

Initialize services

Create a startup script that initializes Unity Gaming Services, signs the player in, and then calls
InitializeIAP
on the
PurchaseManager
script you create in the next step.
Set your Unity Editor environment to an environment that has test mode enabled. For more information, refer to Configure your environment in the Unity Editor.
The following script is a short example for testing. For the full startup sequence, refer to Initialize services to handle the startup sequence.
using System;using Unity.Services.Authentication;using Unity.Services.Core;using UnityEngine;public class ServiceOrchestrator : MonoBehaviour{ // Assign this in the Inspector. public PurchaseManager purchaseManager; async void Awake() { try { await UnityServices.InitializeAsync(); await AuthenticationService.Instance.SignInAnonymouslyAsync(); await purchaseManager.InitializeIAP(); } catch (Exception e) { Debug.LogException(e); } }}
Note
This script signs in anonymously to keep the test short. If the session token is lost, for example when a player reinstalls your game, the player can't recover an unlinked anonymous account or the purchases tied to it. Before a player makes a purchase, link their anonymous account to a platform account or choose another approach. Refer to Approaches to authentication.
2

Fetch your catalog and handle the order

A purchase needs the catalog listings from your project's Remote Catalog, and your game needs to confirm each order it receives. Create a script that connects a
StoreController
to the D2C payment provider integration, fetches the catalog, and confirms orders as they arrive.
Pass
PaymentProvider.Name
as the store name to connect the controller to your D2C integration. Unity IAP decides which provider serves an order when the purchase starts, and when test mode is on that's always the simulated provider. All D2C orders arrive on this controller, so subscribe to its purchase events even if your game already handles purchase events on a native store controller.
Attach this script to the same GameObject as
ServiceOrchestrator
. For the complete implementation, refer to Fetch your Remote Catalog.
using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using UnityEngine.Purchasing;public class PurchaseManager : MonoBehaviour{ StoreController m_StoreController; // Called by ServiceOrchestrator after sign-in completes. public async Task InitializeIAP() { m_StoreController = UnityIAPServices.StoreController(PaymentProvider.Name); m_StoreController.OnProductsFetched += OnProductsFetched; m_StoreController.OnPurchasePending += OnPurchasePending; m_StoreController.OnPurchaseConfirmed += OnPurchaseConfirmed; m_StoreController.OnPurchaseFailed += OnPurchaseFailed; await m_StoreController.Connect(); RemoteCatalogProvider catalogProvider = new RemoteCatalogProvider(); var fetchRemoteCatalogResult = await catalogProvider.FetchRemoteCatalog(); if (!fetchRemoteCatalogResult.Success) { throw fetchRemoteCatalogResult.Exception!; } m_StoreController.FetchProducts(catalogProvider.GetProducts()); } void OnProductsFetched(List<Product> products) { // Log the catalog listing IDs that you can buy in the next step. foreach (var product in products) { foreach (var catalogListing in product.catalogListings.Values) { Debug.Log($"Catalog listing ID: {catalogListing.id}"); } } } void OnPurchasePending(PendingOrder order) { // Grant the player their items before you confirm the order. GrantPlayerItems(order); Debug.Log("Purchase pending. Confirming the order."); m_StoreController.ConfirmPurchase(order); } void GrantPlayerItems(PendingOrder order) { // Replace this with your own fulfillment code, for example adding currency // or unlocking content. foreach (var cartItem in order.CartOrdered.Items()) { Debug.Log($"Granted the player their items for {cartItem.CatalogListingId}."); } } void OnPurchaseConfirmed(Order order) { if (order is FailedOrder failedOrder) { Debug.LogWarning($"Confirmation failed: {failedOrder.FailureReason} {failedOrder.Details}"); return; } Debug.Log("Purchase confirmed."); } void OnPurchaseFailed(FailedOrder order) { Debug.LogWarning($"Purchase failed: {order.FailureReason} {order.Details}"); }}
A catalog listing ID might differ from the product ID in
catalogListing.definition.id
. Purchase calls require the catalog listing ID, so use the ID that this script logs.
Unity IAP invokes the
OnPurchasePending
callback when an order is paid and waiting for fulfillment. The handler you registered on your
StoreController
grants the player their items, then calls
ConfirmPurchase
. Unity IAP invokes the callback for a simulated purchase exactly as it does for a purchase through a connected provider, so your test exercises the same fulfillment code that your released game uses. For the fulfillment options in full, refer to Fulfill purchases.
3

Start the purchase from your game

Add the following method to
PurchaseManager
and call it from your buy button, passing one of the catalog listing IDs that the previous step logs:
public async void BuyProduct(string catalogListingId){ var extendedService = m_StoreController.PaymentProviderStoreExtendedService; // Use GetPaymentOptionProviderUITK for UI Toolkit projects. var paymentOptionProvider = extendedService?.GetPaymentOptionProviderUGUI(); if (paymentOptionProvider == null) { // The Purchase Options UI isn't available. Debug.LogWarning("Show your own purchase UI instead."); return; } // Show the Purchase Options UI with the eligible payment options. await paymentOptionProvider.ShowPurchaseOption(catalogListingId);}
Note
The uGUI Purchase Options UI adds an
EventSystem
with a
StandaloneInputModule
if your scene doesn't already have one. The Editor's Fake Store does the same. Because
StandaloneInputModule
uses the legacy
UnityEngine.Input
API, it logs an
InvalidOperationException
every frame when Active Input Handling is set to Input System Package (New). To check this setting, go to Edit > Project Settings > Player > Other Settings > Configuration. If it is set to Input System Package (New), add an
EventSystem
with an
InputSystemUIInputModule
to your scene before starting a purchase. A pre-existing
EventSystem
prevents either UI from adding an additional
EventSystem
.
When you call
BuyProduct
, what you see first depends on whether your game also has a native store set up:
  • D2C payments only: Only the simulated provider has your catalog listing, so Unity IAP skips the Purchase Options UI and opens the simulated checkout page directly.
  • Native store and D2C payments: If your game has already connected a native
    StoreController
    and fetched the same catalog listings, the Purchase Options UI presents the native option alongside Pay with mock. In the Editor, the Fake Store stands in for the native store as Pay with Mock Store. Select Pay with mock to open the simulated checkout page.
  • Native store only: A
    StoreController
    that you didn't create with
    PaymentProvider.Name
    has no
    PaymentProviderStoreExtendedService
    , so
    paymentOptionProvider
    is null. The sample logs a warning and returns instead of starting a purchase.
For the rules that decide when the UI appears, refer to When the Purchase Options UI doesn't appear. To present your own purchase interface instead of the Purchase Options UI, refer to Build your own purchase UI.
4

Complete the simulated checkout

The simulated checkout page stands in for a payment provider's hosted checkout and doesn't ask for card details. The page displays the order ID, the catalog listing, and the total, so you can verify that your purchase request includes the expected listing.
The mock checkout page in two states. Before payment, a Checkout summary shows the order ID, a coins.pack.small consumable at $4.99, and a $4.99 total, with Confirm Payment and Cancel options. After payment, the same page shows the "Payment confirmed, waiting for your backend to fulfill"  message above Return to Game, Fulfill Order, Refund, and Dispute. Both states display the following banner: This is a mock payment provider for testing purposes. Do not use in production."
To complete the purchase, follow these steps:
  1. Select Confirm Payment. The page reports that the payment is confirmed and waiting for your backend to fulfill the order.
  2. Return to your game. If your environment has a success redirect URL, select Return to Game. If it doesn't, the page shows a terminal screen instead, so close the browser and switch back to your game yourself. Unity IAP checks the order when your game regains focus, so in the Editor, select the Game view.
Your game receives the order through
OnPurchasePending
, and your handler calls
ConfirmPurchase
.
Note
Don't select Fulfill Order when you test from your game. The button represents a backend that fulfills orders server-side, such as a webhook or a Cloud Code module. It closes the order on the server, but your game doesn't receive a successful callback. If you select it before returning to your game, your game receives
OnPurchaseFailed
with the
OrderStateChanged
reason.
The page also offers Refund and Dispute actions. These actions move the order into the corresponding states and send the related purchase events to your webhook or Cloud Code module. Use them to test how your game and backend handle these order statuses. For more information, refer to Refunds and disputes for D2C payments.
5

Verify that the simulated purchase flow succeeded

The console indicates that the test purchase succeeds if it shows the following messages in order:
  1. Catalog listing ID: <your listing ID>
    appears when the catalog loads.
  2. Purchase pending. Confirming the order.
    , appears after you confirm the payment and return your game.
  3. Granted the player their items for <your listing ID>.
    , when your fulfillment code runs.
  4. Purchase confirmed.
    , when Unity IAP confirms the order.
The Console messages only prove that your handler ran in the right order. Also check that the player received what they bought, because Unity IAP confirms the order whether or not your fulfillment code granted anything.
If you configured a webhook or Cloud Code module, verify that it receives the purchase event.
If the test doesn't display those messages, it might be due to one of following causes:
  • The checkout page doesn't open, and the Console shows a
    NoEligibleStore
    warning
    : You passed an ID that isn't a catalog listing ID, or the Editor's environment doesn't have test mode on. In both cases no store has the listing cached, so
    ShowPurchaseOption
    returns
    null
    and starts no purchase. Pass a value that the
    Catalog listing ID
    log shows, and check your Editor environment.
  • The Console fills with
    InvalidOperationException
    errors about the
    UnityEngine.Input
    class
    : Your project uses the Input System package only. Add an
    EventSystem
    with an
    InputSystemUIInputModule
    to your scene.
  • OnPurchasePending
    doesn't run after checkout
    : Your game hasn't regained focus. Switch back to your game, or select the Game view in the Editor.
  • OnPurchaseFailed
    runs with the
    OrderStateChanged
    reason
    : You selected Fulfill Order before you returned to your game, so the order was already closed. Start a new purchase and don't select Fulfill Order.
Important
While test mode is on, Unity IAP simulates every payment in the environment. Make sure not to release with an environment that has test mode enabled. To turn off test mode and ensure you can take real payments, connect at least one payment provider.

Additional resources