How to use Pay Station in combination with Firebase authentication

If you have already implemented user authentication in your application using Firebase, you can generate a payment token on Firebase side and then pass it to the client side of the application to open the payment UI.

Using this integration option, you need to independently implement the logic for determining the user’s country and currency to pay for the purchase.

Integration flow:

  1. Create a project.
  1. Sign up to Publisher Account and create a new project. You will need the ID of the created project in further steps.

  1. Set up a catalog:
    • Create an item catalog on Xsolla side. You can add items manually or import them from Google Play or PlayFab.
    • Implement getting and displaying the catalog on the client side of the application using the SDK.

  1. Set up an item purchase:
    • Create an order with user and item data on the client side of the application using the Firebase Cloud Function.
    • Implement the opening of the payment UI on the client side of your application using the SDK.

  1. Set up an order status tracking.

Notice

To complete the integration and start accepting real payments, you need to sign a licensing agreement with Xsolla.

You can sign the licensing agreement at any integration step, but keep in mind that the review process can take up to 3 business days.

Use the sample application as an example to implement the combined use of Firebase authentication and Pay Station. The source code for the sample application is available on GitHub.

Create project

Sign up to Publisher Account

Publisher Account is the main tool to configure Xsolla features, as well as to work with analytics and transactions.

The data about the company and your application specified during registration will be used to create a draft licensing agreement with Xsolla and to generate recommendations on solutions that are suitable for you. You can change the data later, but providing the correct data when you sign up will speed up the process of signing the licensing agreement.

To sign up, go to Publisher Account and create an account.

Note

The password from Publisher Account can consist of Latin letters, numerals, and special characters and must contain at least:

  • 8 characters
  • one digit
  • one capital letter
  • one lowercase letter

To ensure password security, we recommend:

  • changing your password at least once every 90 days
  • using a new password that does not match the last 4 passwords on your account
  • using a unique password that does not match passwords used anywhere else
  • not storing your password where it is easily accessible
  • using password managers to store your password

Publisher account uses two-factor authentication and sends a confirmation code with each authentication attempt.

Create project in Publisher Account

If you have multiple applications, we recommend creating a separate project for each application. Based on the data specified during project creation, Xsolla generates recommendations on solutions that are suitable for you.

To create a new project:

  1. Open Publisher Account.
  2. In the side menu, click Create project.

  1. Enter your project name in English (required).

Note
After you’ve created the project, you can add additional languages and localized project names in the Project settings section.

  1. Select one or several release platforms of your game (required).
  2. Add a link to your game. If your game doesn’t have a website yet, add a link to the source that includes information about the game (required).
  3. Select the game engine.
  4. Select the monetization options you use or plan to use.
  5. Specify if the game is already released. If the game hasn’t been released yet, specify the planned release date.
  6. Click Create project. You will see a page with the Xsolla products recommended for you.

During the integration process, you need to provide the project ID that can be found in your Publisher Account next to the project name.

Set up catalog

Create items in Publisher Account

Notice

You need to create a catalog on Xsolla side. You can add items manually or import them from Google Play or PlayFab. When importing from Google Play, you can import a maximum of 100 items at a time.

These instructions provide steps for basic setup of a virtual item. Later, you can add other items to your catalog (virtual currency, bundles, game keys), create item groups, set up promotional campaigns, regional prices, etc.

To add a virtual item with basic settings to the catalog:

  1. Open your project in Publisher Account and go to the Items catalog > Virtual items section.
  2. In the drop-down menu, select Create item.

  1. Set the basic settings of the item in the following fields:
    • Image (optional)
    • SKU (item unique ID)
    • Item name
    • Description (optional)

  1. Specify item price:
    1. Set the Price in real currency toggle to On.
    2. In the Default currency field, change the currency (optional) and specify the item price.
    3. If you changed the currency in the Default currency field, select the same currency in the Price in real currency field.

Note
To ensure that the API calls for getting the catalog work correctly, make sure that the default currency and the list of currencies in which prices are specified match for all items.

  1. Change the item status to Available.

  1. Click Create item.

Display catalog on client side of application

  1. Download the latest SDK version (recommended) or choose the required SDK version on GitHub and download it.
  2. Unzip the package.
  3. In the main menu, go to Assets > Import Package > Custom Package and select the downloaded SDK.
  4. In the main menu, go to Window > Xsolla > Edit Settings.
  5. Go to Inspector panel. In the Project ID field, specify the project ID that can be found in Publisher Account next to the name of your project.

  1. On the client side of the application, add a UI to display the item catalog.
  2. Implement requesting for an item catalog from Xsolla servers.

Note
To get a list of virtual items, use the GetCatalog SDK method. You can also get information about catalog items using other SDK methods.

Set up item purchase

Create order using the Cloud Function

To create an order with user and item data on Xsolla side, add a Cloud Function to the project that uses the Create payment token for purchase API call. This call will return a payment token, which is required to open the payment UI and make a purchase.

Limitations:

  • You need to pass either the user country or the user’s IP address when requesting the payment token.
  • If you don’t pass the currency in the token, it is determined by the country.
  • If you pass the currency in the token, the user pays in this currency.

Notice
You need to first create and initialize a Firebase project, and activate user authentication using Firebase. For details on these steps, refer to the following Firebase instructions:

To add a Cloud Function to a project:

  1. Install the Firebase CLI (Command-Line Interface). To do this, run the CLI command:

Copy
Full screen
Small screen
    1npm install -g firebase-tools
    

    1. To link your project to the Firebase project, initialize the Firebase project by running the CLI command:

    Copy
    Full screen
    Small screen
      1firebase init functions
      

      1. Follow the installer’s instructions to configure the settings:
        1. Select an existing codebase.
        2. Specify JavaScript as the language for creating cloud functions.
        3. Install dependencies.

      1. Open functions/index.js and modify it:

      Copy
      Full screen
      Small screen
       1// The Cloud Functions for Firebase SDK to create Cloud Functions and triggers.
       2const functions = require('firebase-functions/v1');
       3
       4const projectId = <projectId>;
       5const apiKey = <apiKey>;
       6
       7exports.getXsollaPaymentToken = functions.https.onRequest((req, res) => {
       8
       9  const requestBody = req.body;
      10    if (!requestBody) {
      11      res.status(400).send('Request body is missing');
      12      return;
      13    }
      14
      15  const userId = requestBody.data.uid;
      16  const email = requestBody.data.email;
      17  const sku = requestBody.data.sku;
      18  const returnUrl = requestBody.data.returnUrl;
      19
      20  const payload = {
      21    user: {
      22      id: {value: userId},
      23      name: {
      24        value: email
      25      },
      26      email: {
      27        value: email
      28      },
      29      country: {
      30        value: 'US',
      31        allow_modify: false
      32      }
      33    },
      34    purchase: {
      35      items: [
      36        {
      37          sku: sku,
      38          quantity: 1
      39        }
      40      ]
      41    },
      42    sandbox: true,
      43    settings: {
      44      language: 'en',
      45      currency: 'USD',
      46      return_url: returnUrl,
      47      ui: {
      48        theme: '63295aab2e47fab76f7708e3'
      49      }
      50    }
      51  }
      52
      53  let url = "https://store.xsolla.com/api/v3/project/" + projectId.toString() + "/admin/payment/token";
      54
      55  fetch(
      56    url,
      57    {
      58      method: "POST",
      59      headers: {
      60        'Content-Type': 'application/json',
      61        Authorization: 'Basic ' + btoa(`${projectId}:${apiKey}`)
      62      },
      63      body: JSON.stringify(payload)
      64    },
      65  )
      66  .then(xsollaRes => {
      67    // Handle the response data
      68    if (xsollaRes.ok) {
      69      return xsollaRes.json();
      70    } else {
      71      throw new Error(`HTTP request failed with status ${xsollaRes.status} and statusText: ${xsollaRes.statusText}`)
      72    }
      73  })
      74  .then(data => {
      75    res.send(JSON.stringify(data));
      76  })
      77  .catch(error => {
      78    res.send("Error = " + error);
      79  });
      80});
      81
      82exports.webhookFakeResponse = functions.https.onRequest((request, response) => {
      83  response.status(200).send()
      84})
      

      1. In the script, specify the values ​​for the variables:
        • projectId — project ID that you can find in your Publisher Account next to the project name.

      1. To test the Cloud Function with the emulator, run the CLI command:

      Copy
      Full screen
      Small screen
        1firebase emulators:start
        

        1. After running the Cloud Function, you can call the following methods on the client side of your application:
          • getXsollaPaymentToken — returns the payment token for opening the payment interface.
          • webhookFakeResponse — sends the HTTP code 200 in response to the Payment webhook. The method doesn’t contain purchase validation logic: use it only for testing. For the full list of webhooks and general information about working with them, refer to the webhooks documentation.

        1. To call methods locally, use the https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken and https://localhost:5001/{firebase-project-id}/us-central1/webhookFakeResponse URLs, where {firebase-project-id} is the Firebase project ID (Firebase console > Project Settings > Project ID).

        1. To deploy the Cloud Function in production, run the CLI command:

        Copy
        Full screen
        Small screen
          1firebase deploy --only functions
          

          1. Once deployed in production, you can call methods via the https://us-central1-{firebase-project-id}.cloudfunctions.net/getXsollaPaymentToken and https://us-central1-{firebase-project-id}.cloudfunctions.net/webhookFakeResponse URLs, where {firebase-project-id} is the Firebase project ID (Firebase console > Project Settings > Project ID). For details on running the feature in production, refer to the Firebase documentation.

          Create order and open the payment UI in Unity project

          1. Open your Unity project.
          2. Make changes to the page controller script:
            1. Add the MakeCloudFunctionRequest method to call cloud function. To call the getXsollaPaymentToken method, provide one of the following URLs, where {firebase-project-id} is the Firebase project ID (Firebase console > Project Settings > Project ID):

              • for local access — https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken
              • for access in the production — https://us-central1-{firebase-project-id}.cloudfunctions.net/getXsollaPaymentToken

          Copy
          Full screen
          Small screen
           1IEnumerator MakeCloudFunctionRequest(string sku)
           2   {
           3       string url = "https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken";
           4
           5       using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
           6       {
           7           var userData = new UserData()
           8           {
           9               data = new UserData.Data() {
          10                   uid = user.UserId,
          11                   email = user.Email,
          12                   sku = sku,
          13                   returnUrl = "app://xpayment.com.xsolla.unitysample"
          14               }
          15           };
          16
          17           byte[] data = System.Text.Encoding.UTF8.GetBytes(JsonUtility.ToJson(userData, true));
          18           UploadHandlerRaw upHandler = new UploadHandlerRaw(data);
          19           upHandler.contentType = "application/json";
          20           webRequest.uploadHandler = upHandler;
          21           webRequest.method = "POST";
          22           yield return webRequest.SendWebRequest();
          23
          24           if (webRequest.result != UnityWebRequest.Result.Success)
          25           {
          26               Debug.LogError("Error: " + webRequest.error);
          27           }
          28           else
          29           {
          30               var paymentToken = "";
          31               XsollaWebBrowser.OpenPurchaseUI(
          32                       paymentToken,
          33                       false);
          34               Debug.Log("Response: " + webRequest.downloadHandler.text);
          35           }
          36       }
          37   }
          

            1. Add Cloud Function callback on buy button click:

          Copy
          Full screen
          Small screen
           1private void OnItemsRequestSuccess(StoreItems storeItems)
           2    {
           3        foreach (var storeItem in storeItems.items)
           4        {
           5            var widgetGo = Instantiate(WidgetPrefab, WidgetsContainer, false);
           6            var widget = widgetGo.GetComponent<StoreItemWidget>();
           7
           8            widget.BuyButton.onClick.AddListener(() =>
           9            {
          10                StartCoroutine(MakeCloudFunctionRequest(storeItem.sku));
          11            });
          12
          13            widget.NameText.text = storeItem.name;
          14            widget.DescriptionText.text = storeItem.description;
          15
          16            if (storeItem.price != null)
          17            {
          18                var realMoneyPrice = storeItem.price;
          19                widget.PriceText.text = $"{realMoneyPrice.amount} {realMoneyPrice.currency}";
          20            }
          21
          22            ImageLoader.LoadSprite(storeItem.image_url, sprite => widget.IconImage.sprite = sprite);
          23        }
          24    }
          

          You can use the test project as an implementation example. The source code for the Unity project is available on GitHub.

          Example page controller script:

          Copy
          Full screen
          Small screen
            1using Firebase.Extensions;
            2using System;
            3using System.Collections;
            4using UnityEngine;
            5using UnityEngine.Networking;
            6using UnityEngine.UI;
            7using Xsolla.Catalog;
            8using Xsolla.Core;
            9
           10[Serializable]
           11public class UserData
           12{
           13    public Data data;
           14
           15    [Serializable]
           16    public class Data
           17    {
           18        public string uid;
           19        public string email;
           20        public string sku;
           21        public string returnUrl;
           22    }
           23}
           24
           25public class FirebaseExamplePage : MonoBehaviour
           26{
           27    public GameObject LoginContainer;
           28    public GameObject StoreItemsContainer;
           29
           30    public InputField EmailInputField;
           31    public InputField PasswordInputField;
           32    public Button LoginButton;
           33    public Button RegisterButton;
           34
           35    public Transform WidgetsContainer;
           36    public GameObject WidgetPrefab;
           37
           38    protected Firebase.Auth.FirebaseAuth auth;
           39    Firebase.Auth.FirebaseUser user = null;
           40
           41    Firebase.DependencyStatus dependencyStatus = Firebase.DependencyStatus.UnavailableOther;
           42
           43    public virtual void Start()
           44    {
           45        Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => {
           46            dependencyStatus = task.Result;
           47            if (dependencyStatus == Firebase.DependencyStatus.Available)
           48            {
           49                InitializeFirebase();
           50            }
           51            else
           52            {
           53                Debug.LogError(
           54                  "Could not resolve all Firebase dependencies: " + dependencyStatus);
           55            }
           56        });
           57    }
           58
           59    protected void InitializeFirebase()
           60    {
           61        StoreItemsContainer.SetActive(false);
           62
           63        Debug.Log("Setting up Firebase Auth");
           64        auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
           65        auth.StateChanged += AuthStateChanged;
           66        RegisterButton.onClick.AddListener(() =>
           67        {
           68            auth.CreateUserWithEmailAndPasswordAsync(EmailInputField.text, PasswordInputField.text).ContinueWith(task =>
           69            {
           70                if (task.IsCanceled)
           71                {
           72                    Debug.LogError("CreateUserWithEmailAndPasswordAsync was canceled.");
           73                    return;
           74                }
           75                if (task.IsFaulted)
           76                {
           77                    Debug.LogError("CreateUserWithEmailAndPasswordAsync encountered an error: " + task.Exception);
           78                    return;
           79                }
           80
           81                Firebase.Auth.AuthResult result = task.Result;
           82                Debug.LogFormat("Firebase user created successfully: {0} ({1})",
           83                    result.User.DisplayName, result.User.UserId);
           84            });
           85        });
           86
           87        LoginButton.onClick.AddListener(() =>
           88        {
           89            auth.SignInWithEmailAndPasswordAsync(EmailInputField.text, PasswordInputField.text).ContinueWith(task =>
           90            {
           91                if (task.IsCanceled)
           92                {
           93                    Debug.LogError("SignInWithEmailAndPasswordAsync was canceled.");
           94                    return;
           95                }
           96                if (task.IsFaulted)
           97                {
           98                    Debug.LogError("SignInWithEmailAndPasswordAsync encountered an error: " + task.Exception);
           99                    return;
          100                }
          101
          102                Firebase.Auth.AuthResult result = task.Result;
          103                Debug.LogFormat("Firebase user logged in successfully: {0} ({1})",
          104                    result.User.DisplayName, result.User.UserId);
          105            });
          106        });
          107    }
          108
          109    void AuthStateChanged(object sender, System.EventArgs eventArgs)
          110    {
          111        Firebase.Auth.FirebaseAuth senderAuth = sender as Firebase.Auth.FirebaseAuth;
          112        if (senderAuth == auth && senderAuth.CurrentUser != user)
          113        {
          114            bool signedIn = user != senderAuth.CurrentUser && senderAuth.CurrentUser != null;
          115            if (!signedIn && user != null)
          116            {
          117                Debug.Log("Signed out " + user.UserId);
          118            }
          119            user = senderAuth.CurrentUser;
          120            if (signedIn)
          121            {
          122                Debug.Log("AuthStateChanged Signed in " + user.UserId);
          123                LoadCatalog();
          124            }
          125        }
          126    }
          127
          128    void OnDestroy()
          129    {
          130        if (auth != null)
          131        {
          132            auth.SignOut();
          133            auth.StateChanged -= AuthStateChanged;
          134            auth = null;
          135        }
          136    }
          137    private void LoadCatalog()
          138    {
          139        LoginContainer.SetActive(false);
          140        StoreItemsContainer.SetActive(true);
          141        XsollaCatalog.GetCatalog(OnItemsRequestSuccess, OnError);
          142    }
          143
          144    private void OnItemsRequestSuccess(StoreItems storeItems)
          145    {
          146
          147        foreach (var storeItem in storeItems.items)
          148        {
          149            var widgetGo = Instantiate(WidgetPrefab, WidgetsContainer, false);
          150            var widget = widgetGo.GetComponent<StoreItemWidget>();
          151
          152            if(widget != null)
          153            {
          154                widget.NameText.text = storeItem.name;
          155                widget.DescriptionText.text = storeItem.description;
          156
          157                widget.BuyButton.onClick.AddListener(() =>
          158                {
          159                    StartCoroutine(MakeCloudFunctionRequest(storeItem.sku));
          160                });
          161
          162                if (storeItem.price != null)
          163                {
          164                    var realMoneyPrice = storeItem.price;
          165                    widget.PriceText.text = $"{realMoneyPrice.amount} {realMoneyPrice.currency}";
          166                }
          167
          168                ImageLoader.LoadSprite(storeItem.image_url, sprite => widget.IconImage.sprite = sprite);
          169            }
          170        }
          171    }
          172    IEnumerator MakeCloudFunctionRequest(string sku)
          173    {
          174        string url = "https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken";
          175
          176        using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
          177        {
          178            var userData = new UserData()
          179            {
          180                data = new UserData.Data() {
          181                    uid = user.UserId,
          182                    email = user.Email,
          183                    sku = sku,
          184                    returnUrl = "app://xpayment.com.xsolla.unitysample"
          185                }
          186            };
          187
          188            byte[] data = System.Text.Encoding.UTF8.GetBytes(JsonUtility.ToJson(userData, true));
          189            UploadHandlerRaw upHandler = new UploadHandlerRaw(data);
          190            upHandler.contentType = "application/json";
          191            webRequest.uploadHandler = upHandler;
          192            webRequest.method = "POST";
          193            yield return webRequest.SendWebRequest();
          194
          195            if (webRequest.result != UnityWebRequest.Result.Success)
          196            {
          197                Debug.LogError("Error: " + webRequest.error);
          198            }
          199            else
          200            {
          201                string responseJson = webRequest.downloadHandler.text;
          202                var responseData = JsonUtility.FromJson<OrderData>(responseJson);
          203
          204                var paymentToken = responseData.token;
          205                int orderId = responseData.order_id;
          206
          207                XsollaWebBrowser.OpenPurchaseUI(
          208                        paymentToken,
          209                        false);
          210                Debug.Log("Response: " + webRequest.downloadHandler.text);
          211            }
          212        }
          213    }
          214
          215    private void OnError(Error error)
          216    {
          217        Debug.LogError($"Error: {error.errorMessage}");
          218    }
          219}
          

          Set up order status tracking

          Tracking the order status is required to ensure that the payment was successful and to grant items to the user.

          Get order status on client side

          The order tracking logic is included in the GetXsollaPaymentToken method. To process a successful purchase, you need to pass a function that is called when order status changes to done.

          The AddOrderForTracking SDK method is used for tracking. For detailed information about how the method works, refer to Track order status.

          Get order status on server side

          Notice

          The SDK allows you to track the order status on the client side of your application. However, we recommend setting up the Payment webhook handler to receive order information in the back end of your application. This allows you to implement additional validation of completed purchases

          For the full list of webhooks and general information about working with them, refer to the webhooks documentation.

          To configure webhooks on Xsolla side:

          1. Open your project in Publisher Account and go to the Project settings > Webhooks section.
          2. In the Webhook server field, enter the URL to which Xsolla will send webhooks.

          Note

          For testing, you can specify https://us-central1-{firebase-project-id}.cloudfunctions.net/webhookFakeResponse, where {firebase-project-id} is the Firebase project ID (Firebase console > Project Settings > Project ID). In this case, Firebase simulates successful processing of the webhook. For a real project, you need to add purchase validation logic.

          To test webhooks, you can also choose any dedicated site, such as webhook.site, or a platform, such as ngrok.

          1. Copy and save the value from the Secret key field. This key is generated by default and is used to sign webhooks. If you want to change it, click the update icon.
          2. Click Enable webhooks.

          Was this article helpful?
          Thank you!
          Is there anything we can improve? Message
          We’re sorry to hear that
          Please explain why this article wasn’t helpful to you. Message
          Thank you for your feedback!
          We’ll review your message and use it to help us improve your experience.
          Last updated: August 29, 2025

          Found a typo or other text error? Select the text and press Ctrl+Enter.

          Report a problem
          We always review our content. Your feedback helps us improve it.
          Provide an email so we can follow up
          Thank you for your feedback!
          We couldn't send your feedback
          Try again later or contact us at doc_feedback@xsolla.com.