JavaScript SDK reference
The Aghanim JavaScript SDK lets you use the Checkout within your web game.
Integration
To integrate the SDK, see its prerequisites and the detailed instruction on Integrate → JavaScript.
Method reference
Initialize SDK
Creates the SDK instance. Call it once, when your game boots.
import { Aghanim } from "@aghanim-sdk/checkout";
const aghanim = Aghanim.init({ apiKey: "sdk_..." });
| Option | Description |
|---|---|
apiKey | Your public SDK key. Safe to expose in the game client. The key itself determines the environment the SDK talks to. |
playerId | Starting player ID. Most integrations call Set player ID instead. |
locale | Default locale for Orders created through the SDK. |
preflight | Defaults to true: Open Checkout verifies the Order is still payable before presenting anything, so a dead Order rejects instead of framing an error screen. Set false to opt every call site out. |
mock | Runs the SDK against in-memory transports. See Testing with mock mode. |
Set player ID
Sets the player ID once for the current SDK instance. The SDK uses it in all following method calls, so the player-scoped methods below take no player argument.
aghanim.setPlayerId("player_1");
Calling a player-scoped method with no ID set throws invalid_argument.
Clear player ID
Drops the player ID from the instance. Call it when the player signs out, so nothing that follows runs against the previous account.
aghanim.clearPlayerId();
Create Order
Creates an Order for the current player and returns it, including its id and checkout_url.
const order = await aghanim.orders.create({
items: [{ sku: "gems-100" }],
metadata: { level: "12" },
});
| Field | Description |
|---|---|
items | Required. The SKUs to charge for. |
player_id | Defaults to the ID from Set player ID. Pass it to create an Order for someone else. |
locale | Defaults to the locale passed to init(). |
user_agent | Defaults to the browser's navigator.userAgent. |
back_to_game_url | Where the player lands after leaving the Checkout. Supports an {order_id} placeholder. |
metadata | String map stored with the Order. |
Every other field of the Create Order request body is accepted as-is.
To create Orders on your backend instead, call that endpoint with your S2S key and pass the resulting checkout_url to Open Checkout.
Get unconsumed Orders
Returns the current player's Orders that are paid but not yet acknowledged by your game.
const orders = await aghanim.orders.unconsumedDetails();
Consume paid Order
Acknowledges an Order and removes it from the unconsumed list. Consuming the same Order twice fails, so grant the items before you consume.
await aghanim.orders.consume(order.id);
Open Checkout
Presents the Checkout and returns a controller you subscribe to. Accepts an Order from Create Order, a bare Order ID, or an opaque checkout_url string from your backend.
const checkout = await aghanim.openCheckout(order, { mode: "embedded", container: "#store-panel" });
| Option | Description |
|---|---|
mode | overlay (default), embedded, external, or auto. See Presentation modes. |
container | Element or selector to mount into. Required for embedded. |
theme | "light", "dark", or "auto" (default). Forces the chrome the SDK draws around the Checkout. |
closeConfirmation | "dismiss" (default) closes on a backdrop click or ESC, "confirm" asks first, "disabled" ignores both gestures. |
analyticsEvents | true (all, default), false (off), or an allowlist such as ["pay_button", "payment_status"]. |
readyTimeoutMs | Fires error with code timeout when the Checkout never reports ready. Off when omitted. |
preflight | Defaults to the value set in init(). Set false to skip the payability check for this call. |
locale | Locale segment for the Checkout URL. Applies only when you pass a bare Order ID, since an Order and a checkout_url already carry theirs. |
Every event listener is also accepted here as an option, under the same name it has on the controller. See Subscribe to events.
Passing a target the SDK cannot resolve to an Order ID throws invalid_argument.
Subscribe to events
The controller is an emitter with three interchangeable subscription styles: the generic on(), the chainable per-event methods such as onPaid(), and the callbacks you pass to openCheckout(). All three feed from the same emitter.
checkout
.onPaymentStatus(({ status, failReason }) => { ... })
.onPaid(({ orderId }) => showSuccessUI())
.onClosed(({ reason }) => game.resume())
.onError((err) => { if (err.code === "popup_blocked") showOpenPaymentPageButton(); });
| Method | Description |
|---|---|
on(event, listener) | Subscribes and returns an unsubscribe function. |
off(event, listener) | Removes a listener. |
once(event) | Resolves a Promise on the next occurrence of the event. |
on<Event>(listener) | Per-event sugar over on(), one method per event. Returns the controller, so calls chain. Every name is in the table below. |
Every event, with the listener name that subscribes to it. The same name works as a controller method and as an openCheckout() option:
| Event | Listener | Payload | Fires when |
|---|---|---|---|
ready | onReady | { orderId } | The Checkout page has loaded and rendered |
payment_status | onPaymentStatus | { orderId, paymentId?, status, failReason?, retryable? } | Any payment or subscription status change |
paid | onPaid | { orderId, paymentId? } | The payment succeeded. See Security before granting on it |
failed | onFailed | { orderId, paymentId?, status, reason?, retryable } | An attempt reached a non-success status. retryable: true keeps the Checkout open |
next_action | onNextAction | NextAction | The provider needs a step performed. See NextAction |
open_external | onOpenExternal | { url, method } | The flow left the iframe. method is popup or redirect |
delivered | onDelivered | { orderId, deliveredAt } | The Order was delivered to the player |
resize | — | { height } | Embedded mode: the Checkout content changed height |
closed | onClosed | { reason } | The Checkout closed, with reason user, completed, error, or programmatic |
error | onError | AghanimError | An SDK-level failure, such as a blocked popup or a framing timeout |
analytics_event | onAnalyticsEvent | { action, type?, data?, orderId? } | The Checkout reported a product-analytics action. See Analytics |
resize has no listener of its own. Subscribe to it with on("resize", ...).
Failed attempts vs dead orders. A declined or canceled attempt fires failed with retryable: true and the Checkout stays open showing its error screen, so the player can try another card or method. Don't treat it as the end of the purchase. The frame closes only on success (closed: completed), on a non-retryable terminal status like an expired Order (closed: error), or when the player leaves (back-to-game relay or your close()).
Each event fires once, so you never need your own bookkeeping to suppress repeats.
Control the Checkout
The controller carries what it is presenting:
| Property | Description |
|---|---|
orderId | ID of the Order being presented. |
url | The Checkout URL in use. Opaque. Never parse or modify it. |
mode | The mode that was resolved: overlay, embedded, or external. auto never appears here. |
And how it is presented:
| Method | Description |
|---|---|
open() | Presents the Checkout again after a close. In embedded mode it needs a container from the open options, or a mount() first. |
mount(container) | Embedded mode: mounts the iframe into an element or selector. Throws invalid_argument in external mode. |
close(reason?) | Closes the Checkout and fires closed. The reason defaults to programmatic. |
destroy() | Closes, then tears down the DOM, listeners, and event channels. Every later call throws invalid_argument. |
Close the Checkout from your game when the player exits to a menu, for example. The Checkout cancels any in-flight payment and the controller fires closed.
checkout.close();
The SDK also closes on its own once the Order reaches a terminal state: reason completed after a success, reason error after a failure the player cannot retry. Call destroy() when you are done with the Checkout for good. close() alone leaves it ready for a later open().
Presentation modes
| Mode | What happens | When to use |
|---|---|---|
overlay (default) | Modal iframe over the game | Most desktop and mobile web games |
embedded | Iframe inside your container, with auto-height via resize events | Custom store UIs |
external | Popup window, falling back to a full redirect if blocked | WebViews, in-app browsers, strict-CSP hosts |
auto | Capability detection picks overlay or external | When you don't want to decide |
Overlay theme: the chrome the SDK draws around the Checkout (the loading placeholder) is themed to match the Checkout itself: the Order's ui_settings.theme when you pass an Order, otherwise the player's prefers-color-scheme, which is the Checkout page's own default. Force it with the theme option.
CSP note: if your page sets a Content-Security-Policy, allow frame-src and child-src for the Checkout origin (https://pay.aghanim.com or your sandbox origin). Popup escalation also needs window.open unblocked. A framing block (CSP or X-Frame-Options) is invisible to the parent page, so pass readyTimeoutMs to get an error when the Checkout never reports ready.
Analytics
The Checkout's product-analytics stream (pageviews, clicks, and every other action the player takes) reaches your game as one analytics_event per action.
const checkout = await aghanim.openCheckout(order, {
analyticsEvents: ["pay_button", "payment_method", "payment_status"],
});
checkout.onAnalyticsEvent(({ action, type, data, orderId }) => {
telemetry.track(`checkout_${action}`, { type, data, orderId });
});
action is what the player acted on (pageview, pay_button, payment_method, submit_payment_form, payment_status, and so on), type is how (click, change, open, submit, …), and data carries extra context for the actions that have any.
The analyticsEvents option filters in your page, so it quiets your own listeners rather than reducing traffic: true (the default) passes everything through, false drops the stream, an array keeps only those actions.
Analytics events are not delivered in external mode, where the Checkout runs in a window your page cannot listen to.
External payment windows
Some payment methods (PayPal, certain 3-D Secure and local methods) cannot run inside an iframe. When the Checkout reaches such a step it emits a next_action of type open_external, then opens the URL in a separate window and emits open_external { url, method }. The result arrives out of band rather than from that window, so pause your game on open_external and resume on payment_status or closed.
If the popup is blocked you get error with code popup_blocked. Render your own "Open payment page" button (a direct user gesture) with the URL from the error details.
NextAction
NextAction is a discriminated union. Handle what you care about and ignore the rest.
type NextAction =
| { type: "completed"; status: CheckoutStatus }
| { type: "show_3ds"; url: string }
| { type: "open_external"; url: string; reason: "payment_method" | "redirect" }
| { type: "await_confirmation"; pollAfterMs?: number }
| { type: "show_error"; error: AghanimError };
Testing with mock mode
Pass mock to init() and the SDK swaps its network and iframe transports for in-memory ones, so your tests can drive a full purchase without a network or a real payment:
const aghanim = Aghanim.init({
apiKey: "sdk_sandbox_...",
mock: { scenario: "instant_success", latencyMs: 100 },
});
Every scenario fires the same events your production code already subscribes to:
| Scenario | Exercises |
|---|---|
instant_success | the happy path, through to paid |
open_external | escalation to a popup, with the result arriving out of band |
three_ds | a show_3ds next action, then success |
failure | a decline (failed with retryable: true; the Checkout stays open) |
poll_fallback | the result still arrives when live updates are unavailable |
unconsumed | pre-seeded paid Orders for the consume loop |
Security
- Never grant value from the
paidevent. A modified client can fake it, so show your success screen onpaidand grant on the signeditem.addwebhook. - Never embed the S2S key in the client.
- Don't write your own message listener. The SDK accepts messages only from the Checkout it opened, in sandbox and production alike, with nothing for you to configure.
Error reference
Everything the SDK throws or hands to an error listener is an AghanimError, an Error with a code from the table below, a retryable flag, status when it came from an API response, and details for the codes that carry extra context.
@aghanim-sdk/checkout/protocol exports the status unions, NextAction, and error codes as standalone types with no runtime dependencies, for typing your own handlers and test fixtures.
| Error code | Meaning |
|---|---|
invalid_api_key | The SDK key was rejected. |
api_error | The SDK API returned an error. |
network_error | The request never reached the API. |
order_not_found | No Order with that ID. |
order_not_payable | The preflight check found the Order past its lifecycle: paid, canceled, expired, or refunded. details carries { orderId, status }. |
popup_blocked | The browser blocked window.open. Offer a button the player can click. |
container_not_found | The container for embedded mode does not resolve to an element. |
invalid_message_origin | A message arrived from an origin the SDK does not trust, and was dropped. Informational: you do not need to handle it. |
websocket_error | The live-events connection failed. Not fatal: the SDK keeps tracking the Order without it. |
timeout | The Checkout never reported ready within readyTimeoutMs. |
not_initialized | A call was made before Aghanim.init(). |
invalid_argument | A method was called with arguments it cannot use. |
Statuses
The status on payment_status and failed falls into three groups, and the group is what your handler should branch on:
| Group | Statuses | What it means |
|---|---|---|
| Success | successful, active, trial | The purchase went through. |
| Retryable failure | canceled, rejected, abandoned, expired | The attempt failed, the Order did not. The Checkout stays open so the player can try another method. |
| Final | everything else | Nothing further will arrive for this Order. |
An Order itself is created, paid, canceled, expired, or refunded. The last four are what preflight rejects as order_not_payable.
Post-purchase outcomes such as refunded, chargeback, and dispute reach your backend as webhooks, which is where your game should act on them.
도움이 필요하세요?
통합팀에 문의하십시오 integration@aghanim.com