---
title: Introduction to the purchasing controller
product: In-App Purchasing
canonical: https://docs.unity.com/en-us/iap/purchasing-controller
last_updated: 2026-11-05
generated_at: 2026-11-06T04:30:50.092Z
locale: en-us
---

# Introduction to the purchasing controller

> Learn how one controller connects to every store your application sells through, and how it sends each call to the right store.

[`PurchasingController`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.PurchasingController.html) is a single object that controls every store your application sells through, such as the Apple App Store or Google Play, plus Direct-to-Consumer (D2C) payment providers. It connects to each store, fetches products and purchases from each store, and reports every store's events through one set of event handlers.

This page explains when to use `PurchasingController`, how it routes calls to each store, how it provides platform extensions, and how to migrate an existing project from [`StoreController`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.StoreController.html).

> **Note:**
>
> `PurchasingController` is available in Unity In-App Purchasing (IAP) 5.5 and later. In IAP 5.4, use `StoreController`. The setup and D2C payment provider pages show the code for both versions in tabs. For an example, refer to [Write the startup script](./set-up-in-app-purchasing.md#write-the-startup-script).

## When to use the purchasing controller

`PurchasingController` is the recommended controller for all new projects, including projects that sell through a single store. A project that starts with one store can add another later with a small code change, because the rest of its purchasing code already handles more than one store.

For example, a project that sells only through the platform-native store creates its controller with one store name:

```csharp
m_Controller = new PurchasingController(UnityIAPServices.GetDefaultStore());
```

Adding D2C payment providers to that project later takes two code changes. The first adds `PaymentProvider.Name` to the constructor:

```csharp
m_Controller = new PurchasingController(UnityIAPServices.GetDefaultStore(), PaymentProvider.Name);
```

The second starts purchases through the [Purchase Options UI](./payment-providers/initiate-purchase.md), which lets the player choose how to pay:

```csharp
var purchaseOptions = m_Controller.PaymentProvidersExtendedService?.GetPaymentOptionProviderUGUI();
if (purchaseOptions != null)
{
    await purchaseOptions.ShowPurchaseOption(catalogListingId);
}
```

The event handlers, the purchase confirmation, and a product fetch that loops over `StoreNames` stay as they are. D2C payment providers also need a Remote Catalog, a signed-in player, and a configured provider. For the full list of requirements, refer to [Integrate D2C payment providers](./payment-providers/workflow.md).

> **Important:**
>
> You can create the controller with `PaymentProvider.Name` at any time, but call `Connect` only after Unity Gaming Services initializes, and fetch products only after the player signs in. If you call them earlier, they fail as follows:
>
> * `Connect` fails for `PaymentProvider.Name`, and `OnStoreDisconnected` reports `Unity Services uninitialized. Make sure you use 'UnityServices.InitializeAsync()' before connecting.`
> * The product fetch fails, and `OnProductsFetchFailed` reports `Unknown error: Player is not authorized.`
>
> If your project connects its controller before Unity Gaming Services initializes, move the `Connect` call to after initialization when you add `PaymentProvider.Name`.

`StoreController` remains supported. A project that uses `StoreController` keeps working without changes. For a complete startup script that uses `PurchasingController`, refer to [Set up and initialize In-App Purchasing](./set-up-in-app-purchasing.md#write-the-startup-script).

> **Important:**
>
> Create one `PurchasingController` for the lifetime of your application, and reuse it in every scene. Don't create a second `PurchasingController`, and don't use `StoreController` in the same project. Controllers that include the same store share that store's services, so each controller receives the same events and your handlers run twice, which can grant an item twice. A product that you fetch with one and purchase with the other can also produce an order that `PurchasingController` doesn't confirm.

## How the purchasing controller routes calls

You create a `PurchasingController` with the names of the stores it controls. For example, `new PurchasingController(UnityIAPServices.GetDefaultStore(), PaymentProvider.Name)` controls the platform-native store and D2C payment providers. The `StoreNames` property lists the stores in the order you passed them.

The constructor throws an `ArgumentException` if you pass no store name, an empty name, or the same name twice, and a `StoreException` if a name isn't a registered store.

The controller sends each call to a store according to the following rules.

### Background calls apply to every store

Calls that start work in the background take an optional `storeName` parameter. When you omit it, the call applies to every store on the controller. When you pass a store name, the call applies to that store only.

| **Method**                                       | **Behavior with no store name**                                                                                   |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `Connect`                                        | Connects every store. The returned `Task` completes when every store has connected or failed.                     |
| `FetchPurchases`                                 | Fetches purchases from every store. Each store reports its result separately.                                     |
| `RestoreTransactions`                            | Restores the transactions of every store. The callback runs once for each store, with its name.                   |
| `ProcessPendingOrdersOnPurchasesFetched`         | Applies the setting to every store.                                                                               |
| `SetStoreReconnectionRetryPolicyOnDisconnection` | Applies the same retry policy instance to every store. A policy that keeps its own state shares it across stores. |

### Product fetches name their store

`FetchProducts` and `FetchProductsWithNoRetries` require a store name and fetch products from that one store. A product definition holds the product ID of a single store, so each store needs its own definitions.

One catalog provider serves every store. For each store, get that store's definitions from the provider with `GetProducts`, and pass them to the controller with the same store name:

```csharp
foreach (var storeName in m_Controller.StoreNames)
{
    m_Controller.FetchProductsWithNoRetries(m_Catalog.GetProducts(storeName), storeName);
}
```

When two stores fetch the same catalog listing, each store returns its own `Product`, with its own price.

### Purchases follow the product or the order

Calls that take a product, a cart, or an order go to the store that object belongs to. Each `Product` and each `Order` holds the name of its store in a read-only `StoreName` property.

| **Method**                                                   | **Store that receives the call**                                              |
| ------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| `PurchaseProduct(Product)`                                   | The store the product came from, `Product.StoreName`.                         |
| `Purchase(ICart)`                                            | The store that all the products in the cart come from.                        |
| `CheckEntitlement(Product)`                                  | The store the product came from, `Product.StoreName`.                         |
| `ConfirmPurchase(PendingOrder)`                              | The store the order belongs to, `Order.StoreName`.                            |
| `PurchaseProduct(string catalogListingId, string storeName)` | The store you name, because a catalog listing ID doesn't belong to one store. |

A purchase uses one store. A cart that mixes products from different stores fails.

### Lookups name their store

Methods that return a store's state require a store name, so that you always know which store answered: `GetProducts`, `GetPurchases`, `GetProductById`, `GetProductByCatalogListingId`, and `GetConnectionState`.

### Events report their store

`PurchasingController` has the same events as `StoreController`, such as `OnPurchasePending` and `OnProductsFetched`. Each event passes the name of the store it comes from as its first parameter. For example, an `OnPurchasePending` handler takes `(string storeName, PendingOrder order)`.

One subscription covers every store on the controller. To handle one store differently, check the `storeName` parameter in your handler:

```csharp
m_Controller.OnPurchasePending += (storeName, order) =>
{
    if (storeName == PaymentProvider.Name)
    {
        // Handle D2C orders differently here, if you need to.
    }

    // Grant the content for the order, then confirm it with its own store.
    m_Controller.ConfirmPurchase(order);
};
```

`OnAuthAccountChanged` fires once for each store when the signed-in player changes. Each store clears its product and purchase caches before the event fires, so fetch your catalog again and then fetch products and purchases from the store the event names. For more information, refer to [Handle an account change](./set-up-in-app-purchasing.md#handle-an-account-change).

### Methods report errors instead of throwing

After you create the controller, its methods don't throw exceptions. When the controller can't route a call, for example, because it names a store that isn't on the controller, the controller logs an error and reports the failure through the event or callback that the call normally uses, such as `OnPurchaseFailed` or `OnProductsFetchFailed`. A `ConfirmPurchase` call that can't be routed reports a `FailedOrder` through `OnPurchaseConfirmed`. Lookups return an empty collection, `null`, or `ConnectionState.Unavailable`.

For a call with no store to report under, such as a `null` order or a product with no store name, the controller only logs an error. Settings calls that name a store that isn't on the controller, `SetStoreReconnectionRetryPolicyOnDisconnection` and `ProcessPendingOrdersOnPurchasesFetched`, also only log an error.

## Platform extensions

`PurchasingController` has the same platform extension properties as `StoreController`, such as `AppleStoreExtendedService`, `GooglePlayStoreExtendedPurchaseService`, and `PaymentProvidersExtendedService`. Each property returns the extension of the store on the controller that provides it, or `null` if no store does.

To use the Purchase Options UI, get it from `PaymentProvidersExtendedService` with `GetPaymentOptionProviderUGUI` or `GetPaymentOptionProviderUITK`. Purchases that the player makes through it arrive on the controller's events, as long as the controller includes both the platform's default store, `UnityIAPServices.GetDefaultStore()`, and `PaymentProvider.Name`.

## Migrate an existing project

To move a project from `StoreController` to `PurchasingController`, follow these steps:

1. Replace every `StoreController` in the project with one `PurchasingController` that lists every store those controllers use. For example, replace `UnityIAPServices.StoreController()` and `UnityIAPServices.StoreController(PaymentProvider.Name)` with `new PurchasingController(UnityIAPServices.GetDefaultStore(), PaymentProvider.Name)`. If one of the controllers uses `PaymentProvider.Name`, call `Connect` after Unity Gaming Services initializes, and fetch products after the player signs in.
2. Replace the event subscriptions of each `StoreController` with one subscription to each `PurchasingController` event, and add the store name as the first parameter of each handler. For example, `OnPurchasePending(PendingOrder order)` becomes `OnPurchasePending(string storeName, PendingOrder order)`.
3. Pass a store name to every product fetch and every lookup. For example, `GetProducts()` becomes `GetProducts(storeName)`, and `FetchProducts(definitions)` becomes `FetchProducts(definitions, storeName)`, called once for each store.
4. Replace catalog provider calls that pass a controller method directly, such as `catalog.FetchProducts(m_StoreController.FetchProductsWithNoRetries)`, with a call on the controller for each store. For each store, call `m_Controller.FetchProductsWithNoRetries(catalog.GetProducts(storeName), storeName)`, as shown in [Product fetches name their store](#product-fetches-name-their-store).
5. Update the callback of `RestoreTransactions` to take the store name as its first parameter. The callback then takes `(storeName, success, error)`.
6. Remove every remaining use of `StoreController`, so that the project doesn't use both.

Calls that take a product or an order, such as `PurchaseProduct(Product)` and `ConfirmPurchase(PendingOrder)`, don't change. They go to the store of the object you pass.

`PurchasingController` doesn't implement `IStoreService`, `IProductService`, or `IPurchaseService`. If code in your project, such as a third-party plug-in, requires those interfaces, keep the project on `StoreController`.

## Additional resources

* [Set up and initialize In-App Purchasing](./set-up-in-app-purchasing.md)
* [Integrate D2C payment providers](./payment-providers/workflow.md)
* [Initiate a purchase](./payment-providers/initiate-purchase.md)
* [`PurchasingController` API documentation](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.PurchasingController.html)
