Skip to main content

Cocos Creator SDK

The official CROSS Pay SDK for Cocos Creator. 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
Cocos Creator3.8.8+
PlatformsAndroid · iOS · Windows · Web
LanguageTypeScript
Packagecom.crossx.sdk.cocos (npm)
Module formatsESM · CJS
Node.js18+

Installation

The SDK is published to npmjs.org and uses a postinstall sync script to mirror itself into the Cocos project structure (both extensions/ for build hooks and assets/ for runtime code).

1. Add the dependency to package.json

{
"scripts": {
"postinstall": "node scripts/sync-crossx-sdk.js",
"sync-sdk": "node scripts/sync-crossx-sdk.js",
"clean-sdk": "node scripts/sync-crossx-sdk.js --clean"
},
"dependencies": {
"com.crossx.sdk.cocos": "0.0.0-beta.15"
}
}

Update the version to the latest release.

2. Copy the sync script

Copy scripts/sync-crossx-sdk.js from the sample project into your project's scripts/ directory. This script mirrors the SDK into extensions/ and assets/ after each npm install.

3. Run the installer

npm install

The postinstall hook runs automatically and syncs the SDK. Restart Cocos Creator to load the new extensions.

To upgrade to a newer version, update the version in package.json and run npm install again. No manual file cleanup is needed.

Troubleshooting

If extensions don't load or imports aren't recognized, run npm run clean-sdk && npm install then fully close and reopen Cocos Creator.

Project ID setup

1. Create the settings file

Copy settings/crossx-sdk.example.json from the sample project to settings/crossx-sdk.json and fill in your Project ID:

{
"activeEnvironment": "prod",
"prodProjectId": "YOUR_PROJECT_ID",
"prodOnly": true
}

Obtain your Project ID from the CROSSx console. Add settings/crossx-sdk.json to .gitignore — it contains your project credentials.

2. Read at runtime

import { CROSSxCocosSDKFactory } from '../CROSSx.Sdk.Cocos/assets/index';

// Read from settings (or hard-code for a simple start)
const sdk = CROSSxCocosSDKFactory.create({
projectId: 'YOUR_PROJECT_ID',
});

The build hook reads settings/crossx-sdk.json at build time to inject the deep-link URI scheme into the native project. The build will fail if prodProjectId 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

import { CROSSxCocosSDKFactory, CocosSdkModalUI, type CrossPayCallback } from '../CROSSx.Sdk.Cocos/assets/index';

// 1. Create SDK instance (call once, e.g. in a manager component's onLoad)
const sdk = CROSSxCocosSDKFactory.create({
projectId: 'YOUR_PROJECT_ID',
appName: 'My Game',
debug: true,
});

// Required for SDK-owned UI modals (sign confirmation, WebView, etc.)
sdk.setUIProvider(new CocosSdkModalUI(this.node));

await sdk.initialize();

// 2. Your game server creates a checkout and returns these values
const { checkoutUrl, checkoutId } = await myGameServer.createCheckout(order);

// 3. SDK opens WebView and waits for the deep-link callback
const callback: CrossPayCallback = await sdk.openCrossPayAndWaitResult(
checkoutUrl,
checkoutId,
{ timeoutMs: 300_000 }
);

// 4. Inspect the envelope-level callback status (advisory only)
if (callback.status === 'success')
console.log('Callback received: success — awaiting server webhook confirmation.');
else if (callback.status === 'cancel')
console.log('User dismissed the checkout.');
else
console.warn('Callback:', callback.status, callback.error);

// 5. Confirm the actual payment state on your server before granting goods
const paid = await myGameServer.verifyPayment(checkoutId);

The build hook automatically injects the webkit-{projectId} URI scheme into the native project when building for Android and iOS, reading prodProjectId from settings/crossx-sdk.json.

No manual edits to AndroidManifest.xml or Info.plist are required.

For reference, the hook injects the following:

Android

Intent filter added to AppActivity in AndroidManifest.xml:

<activity android:name="com.cocos.game.AppActivity"
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

webkit-YOUR_PROJECT_ID added to Info.plist under URL Types.

Windows

The SDK handles the loopback callback handler automatically via a local HTTP listener.

SDK initialization

import { CROSSxCocosSDKFactory } from '../CROSSx.Sdk.Cocos/assets/index';

const sdk = CROSSxCocosSDKFactory.create({
projectId: 'YOUR_PROJECT_ID',
appName: 'My Game',
defaultChainId: 'eip155:612044',
theme: 'dark',
locale: 'en',
debug: true,
});

// Restores stored wallet session if one exists
const session = await sdk.initialize();
if (session?.success)
console.log('Session restored:', session.address);

Trigger payment

const callback = await sdk.openCrossPayAndWaitResult(
checkoutUrl,
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 an error if the callback does not arrive within timeoutMs.

Handle the result

CrossPayCallback carries the decoded deep-link callback envelope.

FieldTypeDescription
statusstringEnvelope status: 'success' | 'cancel' | 'failed' (or raw PG value)
statestring?Echoed CSRF state
dataCrossPayPayment?Payment data from PG — fields vary by provider
errorstring?Error message when status === 'failed'
rawParamsRecord<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
const checkoutId = callback.data?.checkout_id ?? callback.rawParams?.checkout_id;
const txHash = callback.rawParams?.tx_hash;

// The data object contains the PG payment payload (shape varies by provider)
console.log('PG status:', callback.data?.status);

Error handling

import { CROSSxCocosError } from '../CROSSx.Sdk.Cocos/assets/index';

try {
const callback = await sdk.openCrossPayAndWaitResult(checkoutUrl, checkoutId);
// handle callback
} catch (error) {
if (error instanceof Error && error.message.includes('timed out')) {
// 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.
const paid = await myGameServer.verifyPayment(checkoutId);
} else if (error instanceof CROSSxCocosError) {
console.error(error.code, error.message);
} else {
throw error;
}
}

Subscribe to all deep-link events if you need custom routing:

const unsubscribe = sdk.onDeepLink((event) => {
console.log('Deep link received:', event.url, event.callbackType);
// callbackType: 'oauth' | 'crosspay' | 'webkit' | 'unknown'
});

// Unsubscribe when no longer needed
unsubscribe();

For Web platform, forward the OAuth callback URL manually:

sdk.handleOAuthCallback(window.location.href);

Idempotency

ScenarioBehavior
Retry within the same session (network failure, WebView re-open, etc.)Reuse the same checkoutId — call openCrossPayAndWaitResult 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.