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.
Read time 8 minutesLast updated 14 hours ago
PurchasingControllerThis page explains when to use , how it routes calls to each store, how it provides platform extensions, and how to migrate an existing project from .
PurchasingControllerStoreControllerWhen to use the purchasing controller
PurchasingControllerFor example, a project that sells only through the platform-native store creates its controller with one store name:
m_Controller = new PurchasingController(UnityIAPServices.GetDefaultStore());
Adding D2C payment providers to that project later takes two code changes. The first adds to the constructor:
PaymentProvider.Namem_Controller = new PurchasingController(UnityIAPServices.GetDefaultStore(), PaymentProvider.Name);
The second starts purchases through the Purchase Options UI, which lets the player choose how to pay:
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 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.
StoreNamesStoreControllerStoreControllerPurchasingControllerHow the purchasing controller routes calls
You create a with the names of the stores it controls. For example, controls the platform-native store and D2C payment providers. The property lists the stores in the order you passed them.
PurchasingControllernew PurchasingController(UnityIAPServices.GetDefaultStore(), PaymentProvider.Name)StoreNamesThe constructor throws an if you pass no store name, an empty name, or the same name twice, and a if a name isn't a registered store.
ArgumentExceptionStoreExceptionThe 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 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.
storeNameMethod | Behavior with no store name |
|---|---|
| Connects every store. The returned |
| Fetches purchases from every store. Each store reports its result separately. |
| Restores the transactions of every store. The callback runs once for each store, with its name. |
| Applies the setting to every store. |
| 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
FetchProductsFetchProductsWithNoRetriesOne catalog provider serves every store. For each store, get that store's definitions from the provider with , and pass them to the controller with the same store name:
GetProductsforeach (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 , with its own price.
ProductPurchases 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 and each holds the name of its store in a read-only property.
ProductOrderStoreNameMethod | Store that receives the call |
|---|---|
| The store the product came from, |
| The store that all the products in the cart come from. |
| The store the product came from, |
| The store the order belongs to, |
| 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: , , , , and .
GetProductsGetPurchasesGetProductByIdGetProductByCatalogListingIdGetConnectionStateEvents report their store
PurchasingControllerStoreControllerOnPurchasePendingOnProductsFetchedOnPurchasePending(string storeName, PendingOrder order)One subscription covers every store on the controller. To handle one store differently, check the parameter in your handler:
storeNamem_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);};
OnAuthAccountChangedMethods 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 or . A call that can't be routed reports a through . Lookups return an empty collection, , or .
OnPurchaseFailedOnProductsFetchFailedConfirmPurchaseFailedOrderOnPurchaseConfirmednullConnectionState.UnavailableFor a call with no store to report under, such as a 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, and , also only log an error.
nullSetStoreReconnectionRetryPolicyOnDisconnectionProcessPendingOrdersOnPurchasesFetchedPlatform extensions
PurchasingControllerStoreControllerAppleStoreExtendedServiceGooglePlayStoreExtendedPurchaseServicePaymentProvidersExtendedServicenullTo use the Purchase Options UI, get it from with or . 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, , and .
PaymentProvidersExtendedServiceGetPaymentOptionProviderUGUIGetPaymentOptionProviderUITKUnityIAPServices.GetDefaultStore()PaymentProvider.NameMigrate an existing project
To move a project from to , follow these steps:
StoreControllerPurchasingController- Replace every in the project with one
StoreControllerthat lists every store those controllers use. For example, replacePurchasingControllerandUnityIAPServices.StoreController()withUnityIAPServices.StoreController(PaymentProvider.Name). If one of the controllers usesnew PurchasingController(UnityIAPServices.GetDefaultStore(), PaymentProvider.Name), callPaymentProvider.Nameafter Unity Gaming Services initializes, and fetch products after the player signs in.Connect - Replace the event subscriptions of each with one subscription to each
StoreControllerevent, and add the store name as the first parameter of each handler. For example,PurchasingControllerbecomesOnPurchasePending(PendingOrder order).OnPurchasePending(string storeName, PendingOrder order) - Pass a store name to every product fetch and every lookup. For example, becomes
GetProducts(), andGetProducts(storeName)becomesFetchProducts(definitions), called once for each store.FetchProducts(definitions, storeName) - Replace catalog provider calls that pass a controller method directly, such as , with a call on the controller for each store. For each store, call
catalog.FetchProducts(m_StoreController.FetchProductsWithNoRetries), as shown in Product fetches name their store.m_Controller.FetchProductsWithNoRetries(catalog.GetProducts(storeName), storeName) - Update the callback of to take the store name as its first parameter. The callback then takes
RestoreTransactions.(storeName, success, error) - Remove every remaining use of , so that the project doesn't use both.
StoreController
Calls that take a product or an order, such as and , don't change. They go to the store of the object you pass.
PurchaseProduct(Product)ConfirmPurchase(PendingOrder)PurchasingControllerIStoreServiceIProductServiceIPurchaseServiceStoreController