Skip to main content

Unity PC — Xsolla SDK 3 Integration Guide

893cdd33-30aa-4b8f-bb60-184a99bb4add.gif

Xsolla Purchase Flow Demo

This guide primarily benefits developers targeting off-platform PC distribution and Epic Games Store with dual billing. For testing and release, you'll want to set up a web shop and/or the Xsolla Game Launcher via your Publisher Account. For this guide though, you can follow along without them.

What We're Building

We're using the Unity 2D Game Kit as our demo project. The scenario is simple: a player dies, a popup appears offering two options. Restart and lose progress, or buy a full health restoration and respawn from the last checkpoint. That's a classic impulse-buy moment, and it's a perfect fit for Pay Station.

This guide covers the complete integration: Authenticate, Load Catalog, Purchase Product, and Finalize Purchase. Follow it end-to-end to build the flow yourself, or jump to the step you need. Either way, everything here maps directly to the SDK Explorer and Integration Widget, so you'll always know where to find more detail.

Here's what we're working with:

  • Unity version: 6.3.5

  • Demo project: Unity 2D Game Kit

  • Xsolla SDK

  • Authentication method: Device ID (no login required)

  • Payment UI: Xsolla Pay Station opening in the user's default browser (desktop default)

  • Purchase validation: Client-side only

Prerequisites

Before you get started, make sure you've taken care of the following:

  • Unity installed. This guide uses Unity 6.3.5, but any recent version with IAP support should work.

  • A Unity project to work with. If you don't have a project ready, you can follow along with the Unity 2D Game Kit, which is what we use throughout this guide.

  • Unity IAP package installed. The Xsolla SDK integrates with Unity's In-App Purchasing framework and requires the IAP package as a dependency.

  • An active Xsolla Publisher Account. We'll walk through everything you need to do inside your Publisher Account as part of this guide, but creating the account itself isn't covered. If you haven't done that yet, head to the Xsolla Publisher Account Sign Up Page and get that set up first.

Your Reference Resources

Whether you're following along with this article or going deeper afterwards, there are three places you'll want to know about.

The Xsolla SDK Documentation is your main reference. From here, you can find guides for creating your Publisher Account, other integration flows, and links to the tools below.

The SDK Explorer is a playable, interactive demo of the SDK. You can walk through the four steps of monetization by testing purchases in-game and seeing the logs output in real-time.

The Integration Widget is an interactive code reference available in the sidebar of the SDK documentation. It lets you step through each stage of the integration and see exactly what code gets added at each point. This is what we'll be copying from throughout this guide.

fe51f1dc-037a-4b4b-ad6e-8da36ce1f13e.png

SDK Explorer - Integration Widget View (Large)

In summary, SDK Explorer is the playable demo, while Integration Widget serves as the code reference. Both cover the same four steps but address different needs.

The Four Stages of Integration

The Xsolla SDK organizes a complete payment integration into four distinct stages:

  1. Authenticate: establishing who the player is. Before any transaction can happen, the SDK needs a valid user identity.

  2. Load Catalog: fetching your products. The SDK pulls your item catalog from Xsolla's backend and registers those products with Unity's IAP layer, making them available for purchase at runtime.

  3. Purchase Product: initiating the transaction. The moment the player hits buy, the SDK hands off to Pay Station, which handles the payment UI, processes the transaction, and returns a success or failure signal along with your purchase receipt.

  4. Finalize Purchase: acting on the result. Your game responds to a completed transaction by validating it, granting the item, updating game state, and closing the transaction cleanly.

If you keep these four stages in mind, you'll always know where you are in the process and where to look in the SDK Explorer or Integration Widget for reference.

Step 0: Install the Xsolla SDK Package

The Integration Widget gives you the exact Git URL you need. Grab it from there, or directly from the GitHub repo:

https://github.com/xsolla/xsolla-sdk-unity.git

Then in Unity:

  1. Go to Window > Package Manager

  2. Click the + icon in the top left

  3. Select Install package from Git URL

  4. Paste the URL and click Install

You should now see the Xsolla SDK listed in your Package Manager.

51c47e41-8da6-4897-af39-1f8c66a51414.png

Add SDK via Github URL

591ca407-bf4e-43d5-b25e-060617cc184b.png

Xsolla SDK Installed via Package Manager

Step 1: Authenticate

Set Up the Script

Create a new empty GameObject in your scene hierarchy (we placed ours under a "System" label) and add a new C# script to it as a component. We're calling ours XsollaPayStation.

cc9e346f-8426-4bcf-856f-75f83c50066f.png

Create an Empty Object

5edab766-969c-455f-90c9-ebffdc6fbd88.png

Add XsollaPayStation.cs to your object

Head to the Integration Widget and select the Authenticate tab. This step provides the full class. Copy it and paste it over your script file, then update the class name to match your file name. Later steps will provide snippets to add to this class.

using System;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Extension;
using Xsolla.SDK.Store;
using Xsolla.SDK.Common;
using Xsolla.SDK.UnityPurchasing;

public class YourSDKIntegrationBehaviour : MonoBehaviour, IDetailedStoreListener
{
IStoreController _storeController;
IExtensionProvider _storeExtensions;

void Start()
{
var settings = XsollaStoreClientSettings.Builder.Create()
.SetProjectId(301871)
.SetLoginId("dfcb133b-6d0b-4937-b8d2-c4f4d58fb53a")
.Build();

var configuration = XsollaStoreClientConfiguration.Builder.Create()
.SetSettings(settings)
.SetSandbox(true)
.SetLogLevel(XsollaLogLevel.Debug)
.Build();

var module = XsollaPurchasingModule.Builder.Create()
.SetConfiguration(configuration)
.Build();

var configurationBuilder = ConfigurationBuilder.Instance(module)
// ... product registration (see "Load Catalog" step)
;

UnityPurchasing.Initialize(this, configurationBuilder);
}

public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
_storeController = controller;
_storeExtensions = extensions;
}

public void OnInitializeFailed(InitializationFailureReason error)
{
Debug.LogError($"Initialization failed: {error}");
}

public void OnInitializeFailed(InitializationFailureReason error, string message)
{
Debug.LogError($"Initialization failed: {error}. Details: {message}");
}

// ... ProcessPurchase, OnPurchaseFailed (see "Purchase Product" and "Finalize Purchase" steps)
}

338f6d3f-ef47-420f-8ae6-a91ac0d806dd.png

Step 1. Authentication

Expected Compilation Errors

The code from the Authenticate step does not satisfy the full Unity IAP interface contract. You'll see compile errors because PurchaseProcessingResult and OnPurchaseFailed are not yet implemented. Add the following placeholder methods to resolve them. We'll add the proper implementation for OnPurchaseFailed in Step 3: Initiate a Purchase and PurchaseProcessingResult in Step 4: Finalize Purchase.

Placeholder methods to resolve compile errors

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
throw new NotImplementedException();
}

public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
throw new NotImplementedException();
}

public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
throw new NotImplementedException();
}

This is a known friction point when using the Integration Widget step-by-step. The Widget builds up these methods across multiple steps, so some interface requirements won't be satisfied until later.

Configure Your Project ID and Login ID

Inside the copied code you'll see SetProjectId and SetLoginId calls with default placeholder values. These point to a generic Xsolla demo project. Fine for a quick sanity check, but you'll want to replace them with your own credentials to work with your own catalog. For that, you need to set up your project in your Publisher Account.

Creating a New Project in Publisher Account

  1. Log into your Publisher Account and click Create Project in the sidebar

32dd095f-82ea-4c1e-a00b-368078285eee.png

Publisher Account: Create New Project

  1. Fill in the basics. For this guide:
  • Project Type: Game

  • Release Platforms: PC

  • Monetization: In-game transactions

  • Development Stage: Concept

  • Genre: Adventure

  • Game Engine: Unity

5e8ae1cc-0851-4568-9111-5c0ebe288afa.png

Completed Project Details

  1. Click Create Project and take note of your new Project ID

f0c2fa2a-8058-468b-9173-50ee1133a0e1.png

Setting Up a Login Project

A login project tells your SDK which authentication setup you want to use for your app. This can be configured by authentication type and platform. To create one:

  1. In your Publisher Account project, navigate to Players > Login in the sidebar

0f5e74d8-5b51-414e-bef3-570fbacbe5f0.png

  1. Create a new Login Project

ed468261-aabb-4c45-a9ec-24815e9d8d10.png

Standard Login Project

  1. Select Classic Login as the base method.

0cfe4ce8-58ad-45f0-8dee-95cd22a9ae17.png

Configuring default method for login project as Classic Login

  1. Click Configure and navigate to Login API Integration

cab0aaff-3804-4c1e-a549-15d4c11b3091.png

  1. Make sure Login with Device ID is enabled

6d437782-10c4-4678-95ed-e29d0ce10e4d.png

Toggling on API Integration to enable Device ID authentication

Device ID is not the standard PC login flow

Device ID authentication is not the typical approach for PC, but it gets us up and running the fastest for this guide. For a production release, see authentication methods for all available options.

Save your settings and return to the login dashboard. You'll now see both your Project ID and your Login ID.

2c7e1788-8d42-4e12-b9b9-fbd449e36466.png

Head back to your editor and replace the placeholder values:

var settings = XsollaStoreClientSettings.Builder.Create()
.SetProjectId(YOUR_PROJECT_ID)
.SetLoginId("YOUR-LOGIN-UUID")
.Build();

Verify Authentication is Working

Run your game and check the Unity Console. You're looking for log entries confirming that the store initialized successfully and authentication completed.

You'll also see an error about no products being returned. That's completely expected at this stage since we have not loaded the catalog yet.

8b7b445e-e128-4d28-89d9-44022466a116.png

Error: No Products Available

Step 2: Load Catalog

Create Your Item in Publisher Account

  1. Head to your project in your Publisher Account and navigate to Item Catalog > All Items in the sidebar.

216553c1-e6b5-433c-bd53-b0bc9f8cdf82.png

Navigating to the Catalog manager in the Publisher Account Dashboard

  1. Click Create Manually > Virtual Item.

5cd34a17-e0fa-4efe-91d6-a6cae7e6764a.png

3db58e33-96f1-4ca7-8f27-e8f33b99b982.png

  1. Fill in the details:
  • SKU: This maps to your item ID in-game.

  • Name: What players should see during the purchase flow.

  • Price: Set in real currency.

  • Leave the remaining options as defaults for now

2c605619-5d63-4d80-809c-2616c7c15594.png

Mark the item as Available!

Before saving, make sure you toggle the item to Available at the top right of the page. Without this, the item won't appear in your game even if the SKU is correct. This is the first thing to check if you later see a "no products found" error.

  1. Scroll down and click Create Item.

Connect the Catalog to Your Game

Return to the Integration Widget and select the Load Catalog step. There are two pieces of code to add.

public class YourSDKIntegrationBehaviour : MonoBehaviour, IDetailedStoreListener
{
// ... fields, methods, callbacks from previous steps

void Start()
{
// ... settings, configuration, module initialization (see "Authenticate" step)

var configurationBuilder = ConfigurationBuilder.Instance(module)
.AddProduct("ticket.10", ProductType.Consumable)
.AddProduct("ticket.20", ProductType.Consumable)
.AddProduct("ticket.30", ProductType.Consumable);

// ... UnityPurchasing.Initialize() invocation (see "Authenticate" step)
}

public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
// ... callback initialization (see "Authenticate" step)

// Products available via _storeController.products.all
foreach (var product in _storeController.products.all)
{
string id = product.definition.id;
string title = product.metadata.localizedTitle;
string price = product.metadata.localizedPriceString;
string currency = product.metadata.isoCurrencyCode;
// Display product to user
}
}
}

First, in your Start function, set up the configuration builder to register your product with Unity's IAP layer:

Register your product SKU

var configurationBuilder = ConfigurationBuilder.Instance(module)
.AddProduct("full-health", ProductType.Consumable);

ProductType.Consumable tells Unity this item can be purchased more than once. Important for something like a health upgrade that players might buy repeatedly. If the Integration Widget includes multiple sample products, strip them down to just yours.

Second, the OnInitialized callback is where you capture references to the store controller and extensions. Storing these as class fields (_storeController and _storeExtensions) makes them available to the rest of your code for initiating purchases, looking up prices, and validating transactions in later steps.

Store references in OnInitialized

public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
_storeController = controller;
_storeExtensions = extensions;
}

Retrieving product info from the controller

Once the controller is stored, you can iterate through the loaded catalog to pull each product's ID, title, price, and currency. Not required for the integration, but useful for verifying your catalog loaded correctly or for building store UI later.

Optional: log loaded products

foreach (var product in _storeController.products.all)
{
string id = product.definition.id;
string title = product.metadata.localizedTitle;
string price = product.metadata.localizedPriceString;
string currency = product.metadata.isoCurrencyCode;
Debug.Log($"Product: {id}, Title: {title}, Price: {price} {currency}");
}

Verify the Catalog is Loading

Run the game and check the Console. You should see your product listed with its details, and the "no products" error from Step 1 should now be gone.

1f2a1a84-c22d-4f3d-91c3-91f920b25bc7.png

Confirm products were requested and retrieved successfully

Step 3: Purchase Product

Add the Purchase Code

Head back to the Integration Widget and select the Purchase Product step. The key addition is the PurchaseSku() function:

public class YourSDKIntegrationBehaviour : MonoBehaviour, IDetailedStoreListener
{
// ... fields, methods, callbacks from previous steps

// Example purchase method
public void PurchaseSku()
{
_storeController.InitiatePurchase("ticket.10");
}

// Purchase result arrives in ProcessPurchase (see "Finalize Purchase" step)

public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
if (failureReason == PurchaseFailureReason.UserCancelled)
{
// Handle user cancellation here
return;
}

Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureReason}");
}

public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
if (failureDescription.reason == PurchaseFailureReason.UserCancelled)
{
// Handle user cancellation here
return;
}

Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureDescription.reason}, message: {failureDescription.message}");
}
}

Purchase trigger function

For this example, we modified the original function to except a productID.

public void PurchaseSku(string productID)
{
_storeController.InitiatePurchase(productID);
}

The productID parameter means the same function works for any SKU in your catalog. When called, InitiatePurchase() launches Pay Station in the player's default browser to complete the checkout. There's no in-app web view. The player completes payment in their browser and returns to the game afterwards. Once the transaction finishes, Unity IAP fires ProcessPurchase() on success or OnPurchaseFailed() on failure.

Implement OnPurchaseFailed

The Purchase Product step also provides the failure handlers. Copy these from the Integration Widget to replace the NotImplementedException() placeholders from Step 1:

Replace both OnPurchaseFailed placeholders

public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
if (failureReason == PurchaseFailureReason.UserCancelled)
{
// Handle user cancellation here
return;
}
Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureReason}");
}

public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
if (failureDescription.reason == PurchaseFailureReason.UserCancelled)
{
// Handle user cancellation here
return;
}
Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureDescription.reason}, message: {failureDescription.message}");
}

Both methods check for user cancellation first (the player closed Pay Station without completing the purchase) and log an error for any other failure reason. The second variant includes a more detailed failure description. Two versions exist because Unity IAP has two listener interfaces, each firing a slightly different signature. Having both means you're covered regardless of which interface is in play. These are complete as-is for this guide.

Wire Up Your Game Logic

The Integration Widget covers the purchase mechanics, but how you trigger PurchaseSku is up to you. Here's what we did for the 2D Game Kit demo project:

Singleton pattern.

We made the class a singleton so it's accessible from any script in the project. Quick to set up, though you might prefer a different pattern for your final release.

public class XsollaPayStation : MonoBehaviour, IDetailedStoreListener
{
public static XsollaPayStation Instance { get; private set; }

void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}

Live pricing helper

We added a GetProductPrice function that pulls the current price for a given SKU from the store controller. Updating the price in your Publisher Account means the in-game price updates the next time the catalog loads. Keep in mind: if the catalog isn't reloaded during a session, players will still see the previous price.

public string GetProductPrice(string productId)
{
if (_storeController == null) return "...";
var product = _storeController.products.WithID(productId);
return product != null ? product.metadata.localizedPriceString : "...";
}

UI Implementation

In a separate UI class, we wired the purchase button to call XsollaPayStation.Instance.PurchaseSku("full-health") on click, and used a co-routine to update the price text via GetProductPrice().

c3f96843-18b1-400e-a732-e1e85bdfb2dd.png

Buy Health Button Implementation

Test Your Purchase Flow

Run the game and trigger your purchase. Your default browser should open with Pay Station showing your product name, price, and payment options.

Since we're in sandbox mode, you'll have access to test cards directly within Pay Station. Use these rather than your own card details. You can find the full list of test card details in the Xsolla developer resources.

Complete the purchase and check your Unity Console for the purchase confirmation in the logs.

5516124d-81c6-4efd-b590-05abec78d351.png

Browser Checkout

At this point, nothing has happened in-game yet. The purchase was processed, but the game doesn't know what to do with it. That's the final step.

Step 4: Finalize Purchase

Understanding Client-Side vs Server-Side Validation

There are two validation approaches available.

Server-side validation uses webhooks or the Xsolla Events API. Unity IAP is told the purchase is pending and your code polls an event queue waiting for external confirmation. When that confirmation arrives, ConfirmPendingPurchase fires and consumes the transaction.

Client-side validation, which is what we're using, skips all of that. We confirm the purchase directly within the client the moment the transaction succeeds. No waiting, no polling, no pending state.

Why straight to Purchase Processing Complete?

Our ProcessPurchase returns PurchaseProcessingResult.Complete immediately, rather than Pending. This is intentional. The Pending + ConfirmPendingPurchase pattern provides no additional benefit in a client-side only setup. There is no server or Events API maintaining a persistent transaction queue. Returning Complete is the correct approach when you're validating and fulfilling entirely on the client.

Server-side validation for production

Client-side validation is a good starting point and appropriate for many games. For high-value economies or competitive games where fraudulent item grants would have meaningful impact, consider migrating to server-side validation using Xsolla's webhook system . Server-side validation is harder to intercept or tamper with, as the receipt never passes through client code you don't control.

Implement Finalize Purchase

Head to the Integration Widget, select Finalize Purchase, and copy the PurchaseProcessingResult() function.

public class YourSDKIntegrationBehaviour : MonoBehaviour, IDetailedStoreListener
{
// ... fields, methods, callbacks from previous steps

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
var product = args.purchasedProduct;
var validator = _storeExtensions.GetExtension().GetValidator();

validator.Validate(product.receipt, (success, error) =>
{
if (error == null && success)
{
// Purchase is valid — grant the item to the player, then confirm:
_storeController.ConfirmPendingPurchase(product);
}
else
{
// Purchase validation failed — do not award item
Debug.LogWarning($"Validation failed for {product.definition.id}");
}
});

return PurchaseProcessingResult.Pending;
}
}

Here's what our final implementation looks like after adapting it for Client-side Validation:

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
var product = args.purchasedProduct;
var validator = _storeExtensions
.GetExtension()
.GetValidator();

validator.Validate(product.receipt, (success, error) =>
{
if (success)
{
Debug.Log($"Purchase successful: {product.definition.id}");
// Your game logic goes here: grant the item, update state, etc.
}
else
{
Debug.LogWarning($"Validation failed for {product.definition.id}");
}
});

return PurchaseProcessingResult.Complete;
}

Compared to the Integration Widget default, we've stripped out the server-side validation code and are using only the client-side path. We return Complete immediately and handle the result inside the Validate callback.

Add Your Game Logic

The success branch inside the Validate callback is where your game responds to a confirmed purchase. The key point is that this is where it lives.

For our 2D Game Kit demo project, we:

  • Destroyed the purchase UI popup

  • Triggered the player respawn function with full health from the last checkpoint

Example game logic in success branch

if (success)
{
Debug.Log($"Purchase successful: {product.definition.id}");

// Destroy the purchase popup
if (purchaseUI != null)
{
Destroy(purchaseUI);
}

// Trigger respawn with full health
PlayerController.Instance.RespawnWithFullHealth();
}

Verify the Complete Flow

Run the game end-to-end. This is the full test:

  1. Player dies. Popup appears with the purchase option showing the live price

  2. Click the purchase button. Default browser opens with Pay Station

  3. Complete the purchase using a test card

  4. Return to Unity. Player respawns with full health, UI popup is dismissed

893cdd33-30aa-4b8f-bb60-184a99bb4add.gif

Xsolla Purchase Flow Demo Check the Console for the full finalization sequence: validation firing, the purchase confirmation log, and the transaction being closed.

What You've Built

That's a complete Xsolla payment integration in Unity for PC. Device ID authentication, a dynamic catalog, Pay Station via external browser, and client-side purchase validation.

The final class structure you should have:

  • Initialization and authentication configured with your Project ID and Login ID

  • Catalog registration with your product SKU(s) declared to Unity IAP

  • PurchaseSku wired up to your game UI trigger

  • ProcessPurchase returning Complete immediately and triggering your game's post-purchase logic

  • OnPurchaseFailed (both variants) present and handling failure gracefully

Remember to disable sandbox before release

If you've been testing with SetSandbox(true), make sure to set it to false before shipping. Leaving sandbox enabled in production means real purchases won't process.

Where to Go Next

Next steps from here:

  • Add more products.

  • Extend AddProduct and your store UI to support a full catalog

  • Move to server-side validation or Events API.

  • For a production release, the Xsolla Events API or webhook-based server validation is the recommended upgrade from the client-side approach used in this guide. The SDK Explorer makes it easy to see exactly what changes

  • You may need to contact your account manager or email integrations@xsolla.com to get the Events API configured for your project

  • Explore other authentication methods.

  • Device ID is quick to set up but doesn't persist across device changes. Xsolla supports email, social, and platform-native authentication

  • Configure your Pay Station.

  • Publisher Account gives you control over payment methods, UI theming, and regional settings

  • Set up a Web Shop and/or the Xsolla Game Launcher.

  • For off-platform PC distribution and release, these are some of your options

The Xsolla SDK site has detailed walkthroughs, quick guides, and videos for every feature covered here and beyond. The SDK Explorer is worth revisiting once you have a working integration.

Good luck, and if you run into anything, the Xsolla developer community and support team are there to help.