Skip to main content

Unity SDK

The official CROSS Pay SDK for Unity. It opens a CROSS Pay checkout session inside an in-app WebView, waits for the deep-link callback, and returns the result — no API keys on the client.

Compatibility

EnvironmentSupported versions
Unity2022.3 LTS and later
PlatformsAndroid · iOS · Windows
LanguageC#
Packagecom.crossx.sdk.unity (UPM)

Installation

Option A — Scoped Registry

Add the registry and package to Packages/manifest.json:

{
"scopedRegistries": [
{
"name": "NexusCROSSxUPM",
"url": "https://registry.npmjs.org",
"scopes": ["com.crossx"]
}
],
"dependencies": {
"com.crossx.sdk.unity": "<version>"
}
}

Option B — Git URL

Window → Package Manager → Add package from git URL:

https://github.com/to-nexus/crossy-sdk-unity.git?path=src/CROSSx.Sdk.Unity#<version>

The Webkit SDK (com.crossx.webkit.sdk.unity) is a bundled dependency and installs automatically.

Project ID setup

The SDK reads your Project ID from a settings asset at Assets/Resources/CROSSx SDK Settings.asset. Create it via the Unity menu:

CROSSx SDK → Open Settings

Enter your Project ID (obtained from the CROSSx console). The build post-processors use this value to inject the deep-link URI scheme at build time — the build will fail if the field is empty.

Prerequisites

In every example, checkoutUrl / checkoutId are values your game server obtains by calling POST /v1/payments and passes to the client.

  • Store MERCHANT_API_KEY only in server environment variables or a secret manager.
  • The game client only ever receives checkout_url and checkout_id from your server.
  • The deep-link callback result is not authoritative for order fulfillment. Confirm finalization via webhook or a server-side GET /v1/payments/{id} lookup.

Payment flow

One-line summary

Your game server creates a payment session → the SDK opens a WebView and waits for the deep-link callback → your server finalizes the order via webhook. The deep-link callback result is not the source of truth for confirmation.

Quick start

using CROSSx.Sdk.Unity.SDK;
using CROSSx.Sdk.Unity.Core.Types;
using CROSSx.Sdk.Unity.Core.Utils;

// 1. Initialize the SDK (call once, e.g. in GameManager.Start)
var config = new SDKConfig
{
ProjectId = "YOUR_PROJECT_ID",
AppName = "My Game",
};
var sdk = CROSSxSDKFactory.Create(config);
await sdk.InitializeAsync();

// 2. Your game server creates a checkout and returns these values
string checkoutUrl = await MyGameServer.CreateCheckoutAsync(order);
string checkoutId = ExtractCheckoutId(checkoutUrl); // or receive separately

// 3. SDK opens WebView and waits for the deep-link callback
CrossPayCallbackResult result = await sdk.OpenCrossPayAndWaitResultAsync(
checkoutUrl,
checkoutId
);

// 4. Inspect the envelope-level callback status (advisory only)
if (result.IsSuccess)
Debug.Log("Callback received: success — awaiting server webhook confirmation.");
else if (result.IsCancel)
Debug.Log("User dismissed the checkout.");
else
Debug.LogWarning($"Callback: {result.Status} — {result.Error}");

// 5. Confirm the actual payment state on your server before granting goods
bool paid = await MyGameServer.VerifyPaymentAsync(checkoutId);

The checkout WebView redirects back via the webkit-{ProjectId} URI scheme on mobile and via a local loopback URL on desktop. The scheme must be registered per platform.

Android

Add an intent filter in Assets/Plugins/Android/AndroidManifest.xml:

<activity
android:name="com.unity3d.player.UnityPlayerActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="webkit-YOUR_PROJECT_ID" />
</intent-filter>
</activity>

iOS

The SDK's iOSBuildPostProcessor registers the URI scheme automatically after build. To register manually, add webkit-YOUR_PROJECT_ID to Project Settings → Supported URL schemes.

Windows

A register_deeplink.reg file is generated automatically after build. Run it once to register the loopback callback handler in the Windows registry.

SDK initialization

var config = new SDKConfig
{
ProjectId = "YOUR_PROJECT_ID",
AppName = "My Game",
Environment = SDKEnvironment.Development, // resolves URLs automatically
Theme = ThemeMode.Dark,
Locale = "en",
Debug = true,
};

var sdk = CROSSxSDKFactory.Create(config);

// Restores stored wallet session if one exists
var session = await sdk.InitializeAsync();
if (session != null)
Debug.Log($"Session restored: {session.WalletAddress}");
note

SDKEnvironment.Development resolves API URLs automatically. Use SDKEnvironment.Custom to set OAuthServerUrl, AuthApiUrl, and EmbeddedWalletGatewayUrl manually.

Trigger payment

CrossPayCallbackResult result = await sdk.OpenCrossPayAndWaitResultAsync(
checkoutUrl: checkoutUrl,
checkoutId: checkoutId,
timeoutMs: 300_000 // default: 5 minutes
);

The SDK:

  1. Appends a CSRF state token and the callback endpoint to checkoutUrl.
  2. Opens the checkout page in the platform-native WebView.
  3. Waits for the payment callback to arrive — method varies by platform:
    • Android · iOS · macOS: deep-link scheme webkit-{ProjectId}://crosspay-callback
    • Windows · Editor: loopback HTTP http://localhost:{port}/crosspay-callback
  4. Validates state and returns the decoded callback envelope.
  5. Throws TimeoutException if the callback does not arrive within timeoutMs.

Handle the result

CrossPayCallbackResult carries the decoded deep-link callback envelope.

FieldTypeDescription
StatusstringEnvelope status: "success" | "cancel" | "failed" (or raw PG value)
IsSuccessbooltrue when Status == "success"
IsCancelbooltrue when Status == "cancel"
IsFailedbooltrue when Status == "failed"
DataJObject?Payment data from PG — fields vary by provider
Errorstring?Error message when IsFailed
RawParamsIReadOnlyDictionary<string, string>?All raw query params from the callback URL (e.g. checkout_id, payment_id, tx_hash)
Verify on your server

The deep-link callback result is advisory only. Always confirm the payment state with a server-side GET /v1/payments/{id} lookup or webhook before granting goods.

// Read PG-supplied fields from the callback
if (result.RawParams != null)
{
result.RawParams.TryGetValue("checkout_id", out var cbCheckoutId);
result.RawParams.TryGetValue("tx_hash", out var txHash);
}

// The Data object contains the PG payment payload (shape varies by provider)
string pgStatus = result.Data?["status"]?.ToString();

Error handling

using System;

try
{
var result = await sdk.OpenCrossPayAndWaitResultAsync(checkoutUrl, checkoutId);
// handle result
}
catch (TimeoutException)
{
// Deep-link callback did not arrive within timeoutMs.
// The user may have completed the payment without the callback reaching the app.
// Verify the payment state on your server.
bool paid = await MyGameServer.VerifyPaymentAsync(checkoutId);
}
catch (InvalidOperationException ex)
{
// SDK not initialized, or WebView failed to open.
Debug.LogError(ex.Message);
}

Idempotency

ScenarioBehavior
Retry within the same session (network failure, WebView re-open, etc.)Reuse the same checkoutId — call OpenCrossPayAndWaitResultAsync again with the same checkoutUrl and checkoutId
Payment reaches a terminal state (PAID / FAILED / EXPIRED / CANCELLED)Discard — issue a new checkout by calling POST /v1/payments on your server
New payment with a different order / amount / playerAlways a new checkout — reusing a previous checkoutId may be rejected for amount mismatch
No refunds

CROSS Pay currently does not provide a refund feature. If the same order is charged twice, it cannot be reversed. Call POST /v1/payments with the same Idempotency-Key for retry scenarios.