JavaScript / Browser SDK
@lartech/infraio-checkout-js is the only SDK we publish today. It
runs in the browser and opens our hosted checkout. Backend SDKs
(Node, Go, Python) are on the roadmap; until then, talk to the
gateway directly — see the Quickstart
for a HMAC signing helper.
- Current version:
0.1.1-beta.17(pre-1.0; expect minor breaks) - Formats: ESM (
index.js), CJS (index.cjs), IIFE (index.global.js) - Types bundled (
index.d.ts) - Zero runtime peers — no React, jQuery, or other deps
There is no server-side entry. Signature verification helpers
for webhooks are not bundled — implement them yourself with crypto
(the signature verification page
has copy-paste code in 4 languages).
Install
npm
npm install @lartech/infraio-checkout-jsloadInfraIo(publicKey, options?)
Returns a Promise<InfraIoInstance>.
import { loadInfraIo } from "@lartech/infraio-checkout-js";
const sdk = await loadInfraIo("pk_live_yourkeyhere", {
// Optional. Override only when pointing at a non-prod environment.
checkoutUrl: "https://checkout-dev.infraio.xyz",
});| Param | Type | Required | Notes |
|---|---|---|---|
publicKey | string | ✓ | Must match pk_(live|test)_… |
options.checkoutUrl | string | — | Override base checkout URL. Default: https://checkout.infraio.xyz. The matching gateway URL is determined by the checkout page at runtime — each checkout(-dev).infraio.xyz deploy carries its compile-time NEXT_PUBLIC_API_URL, so picking the right hostname here picks the right backend automatically. There is no separate gatewayUrl option. |
sdk.checkout({ … })
Opens the hosted checkout. Returns void (use callbacks for state).
| Field | Type | Required | Notes |
|---|---|---|---|
sessionId | string | ✓ | session_key returned by POST /b2b/v1/checkout-sessions/quick |
checkoutUrl | string | — | Full URL returned by the same endpoint. If omitted, SDK constructs it from loadInfraIo()’s checkoutUrl (or default) + sessionId |
mode | "popup" | "redirect" | "embed" | — | Default "popup" |
container | string | HTMLElement | embed only | CSS selector or DOM element where the iframe mounts |
width | number | — | Popup only. Default 560. Clamped [320, 1280] |
height | number | — | Popup only. Default 780. Clamped [400, 1000] |
timeoutMs | number | — | Popup only. Iframe load timeout. Default 30000. Pass 0 to disable |
locale | string | — | BCP-47 tag forwarded as ?locale= to the checkout page (en, ja, zh-CN, zh-TW) |
hideSummary | boolean | — | Hide the order-summary column. Default false |
hideHeader | boolean | — | Hide the InfraIO header and built-in wallet-connect button. Default false. Pair with walletAddress for full white-label |
walletAddress | string | — | Pre-connect a buyer wallet. Requires onSignRequest |
walletChainId | number | — | EVM chain ID for the pre-connected wallet |
onReady | () => void | — | Fires once the iframe is interactive. Popup/embed only |
onSignRequest | (req: { method: string; params: unknown[] }) => Promise<string> | when walletAddress set | The SDK proxies wallet RPCs to your handler; return the signed hex |
onSuccess | ({ sessionId }) => void | — | Fires on successful payment. Not authoritative — webhook is |
onCancel | () => void | — | Fires when the buyer closes the popup/embed without paying |
onError | (err: InfraIoError) => void | — | Fires on iframe load failure (popup + embed modes). Invalid arguments are thrown synchronously, not delivered here. Redirect mode has no runtime error surface — failure is observed in the redirected page. |
onSuccess is not authoritative. It can fire even when the
webhook later determines the payment failed (testnet reorgs,
buyer-side timing). Use it only for UX (show “Thanks!”, redirect).
Always confirm via webhook before fulfilling.
sdk.close()
Programmatically dismiss an open popup or embed. No-op for redirect mode.
sdk.checkout({ sessionId, mode: "popup", /* … */ });
// later, e.g. when user navigates away
sdk.close();sdk.openRefundRequest({ … })
Opens the hosted refund-request form for a one-time token your
backend minted via POST /b2b/v1/merchants/{merchant_id}/refund-requests. The buyer fills in
their refund destination address + reason + (optional) metadata on
our page; your page only handles the open/close lifecycle. Returns a
close() function — call it to programmatically dismiss the
popup or detach the embed iframe. In redirect mode the returned
function is a no-op.
The form lives at https://checkout.infraio.xyz/refund-request/:token.
This method just wraps that URL in a popup / redirect / embed so the
buyer never leaves your domain (in popup / embed) or returns to it
automatically (in redirect). The endpoint your backend hits to mint
the token is POST /b2b/v1/merchants/{merchant_id}/refund-requests — HMAC-signed with your
secret key, same auth as the rest of the B2B surface. See
Concepts → Refunds.
import { loadInfraIo } from "@lartech/infraio-checkout-js";
const sdk = await loadInfraIo("pk_live_yourkeyhere");
// Mint the token server-side, then hand it to the SDK in the browser.
const { token } = await fetch("/api/mint-refund-token", { method: "POST" }).then(r => r.json());
const close = sdk.openRefundRequest({
token,
mode: "popup",
onSuccess: ({ linkToken, refundId }) => {
// Buyer submitted the form.
// linkToken → /r/:linkToken status page (share with the buyer).
// refundId → use with B2B API to approve / reject.
window.location.href = `/r/${linkToken}`;
},
onCancel: () => { /* buyer closed without submitting */ },
});
// Programmatically dismiss the popup later if needed:
// close();| Field | Type | Required | Notes |
|---|---|---|---|
token | string | ✓ | The rfqt_… token returned by POST /b2b/v1/merchants/{merchant_id}/refund-requests |
mode | "popup" | "redirect" | "embed" | — | Default "popup". Same surface semantics as sdk.checkout() — see Mode notes |
container | string | HTMLElement | embed only | CSS selector or DOM element where the iframe mounts |
locale | string | — | BCP-47 tag forwarded as ?locale= (en, ja, zh-CN, zh-TW) |
hideHeader | boolean | — | Hide the InfraIO header inside the iframe. In popup/embed the SDK draws its own modal chrome, so the page header is typically noise. Default false |
hideSummary | boolean | — | Hide the Order Summary column, showing only the refund form. Default false |
walletAddress | string | — | Pre-fill the destination wallet field (?wallet_address=). Lets a merchant that already knows the buyer’s wallet skip the manual re-type |
onSuccess | (data: { linkToken: string; refundId: string }) => void | — | Fires after the buyer submits the form. linkToken → /r/:linkToken status page to share with the buyer. refundId → use with the B2B API to approve / reject |
onCancel | () => void | — | Fires when the buyer closes the popup/embed without submitting |
onError | (err: InfraIoError) => void | — | Fires on iframe load failure or invalid args. Token-expiry / cancellation is handled by the hosted page, not via onError |
Token states surfaced via onError
If the buyer opens a stale token, the page itself handles the
display (renders an “Expired — request new link” prompt, etc.), and
the SDK does not fire onError for those cases — the buyer is
inside the form flow and your code doesn’t need to react. onError
only fires for things your code can act on (bad arguments, network
failure loading the iframe).
openRefundRequest records the refund intent — it does not move
funds. After onSuccess, the refund row is PENDING (or APPROVED
if your merchant config auto-approves customer refunds). You still
need to sign and broadcast the on-chain transfer from your merchant
wallet, then post the tx hash to POST /b2b/v1/refunds/:id/submit-tx.
See Concepts → Refunds for the full lifecycle.
Error class
import { InfraIoError } from "@lartech/infraio-checkout-js";
sdk.checkout({
sessionId,
onError: (err: InfraIoError) => {
switch (err.code) {
case "invalid_request_error": /* bad sessionId / args */ break;
case "iframe_load_error": /* iframe failed to load */ break;
case "iframe_timeout_error": /* exceeded timeoutMs */ break;
case "already_open_error": /* another checkout is already open */ break;
case "network_error": /* transient network problem talking to the checkout origin */ break;
case "api_error": /* backend returned a non-2xx for an SDK-issued call */ break;
}
},
});sdk.openRefundRequest() throws only invalid_request_error (missing
or invalid token / args), synchronously. iframe_load_error (the
refund-request iframe failed to load) is delivered asynchronously via
onError, not thrown. It does not emit iframe_timeout_error or
already_open_error — the refund-request popup has no load-timeout
and allows multiple concurrent popups.
Mode notes
Popup
- Centered overlay with a dark semi-transparent backdrop
z-index: 2147483647(max int32) — sits above everything else- Body scroll is locked while open; restored on close
- Close button gets initial focus; Tab is trapped in the popup
- Closed by: close button, Escape, click outside,
sdk.close(). All of them callonCancel - The checkout page can request resize via
postMessage— the SDK clamps withinwidth/heightlimits
Redirect
- Hard navigation via
window.location.href - Automatically appends
?return_url=<current-page>so the buyer returns where they came from. If yoursuccess_url/cancel_urlon the session already cover this, the round-trip ignoresreturn_url
Embed
- iframe with
allow="payment; clipboard-write"(HTML5 Feature Policy directives — not thesandboxattribute). The iframe is served from the checkout origin, so wallet popups and clipboard writes from the buyer side work without further opt-in. - Container width is 100%; height auto-sizes via
INFRAIO_RESIZEpostMessage, clamped[200, 2000]px - No CSS isolation beyond the iframe boundary — your parent page styles do not bleed in
- Always wire
onReadyso you can hide your own loading state when the checkout becomes interactive
TypeScript
All types are bundled. Most useful exports:
import type {
CheckoutOptions,
RefundRequestOptions,
LoadOptions,
InfraIoInstance,
InfraIoErrorCode,
} from "@lartech/infraio-checkout-js";
import { loadInfraIo, InfraIoError, VERSION } from "@lartech/infraio-checkout-js";VERSION is the SDK’s own version string — useful in bug reports.
What’s next
- React / Vue framework wrappers — planned for the post-launch
iteration once the vanilla JS surface stabilises across the pilot
merchants. The vanilla SDK works fine inside React today; the wrappers
will just remove the manual
useRef+ lifecycle plumbing. - Cross-tab session resumption — open the checkout in tab A, finish in tab B. Useful when a buyer follows a magic-link mid-flow. Tracked for the next minor release.
- Theming via CSS variables — surface a small set of design tokens (radius, accent colour) on the iframe so merchants can match their brand without forking the page.
- Server-side helpers — a tiny
@lartech/infraio-serverpackage exposingverifyWebhook()+signedRequest()so backend code doesn’t need to copy the HMAC ritual. Until it ships, the signature verification page lists drop-in implementations in TypeScript, Go, Python, and Ruby.