JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 25
  • Score
    100M100P100Q79214F
  • License MIT

Commerce Engine Checkout - Embeddable checkout SDK

Package Exports

  • @commercengine/js
  • @commercengine/js/cdn
  • @commercengine/js/package.json

Readme

@commercengine/js

Embed Commerce Engine checkout in any website.

Installation

<script src="https://cdn.commercengine.com/v1.js" async></script>

npm

npm install @commercengine/js

Quick Start

<script src="https://cdn.commercengine.com/v1.js" async></script>
<script>
  window.Commercengine.onLoad = async () => {
    const checkout = await Commercengine.init({
      storeId: "store_xxx",
      apiKey: "ak_xxx",
      environment: "production", // or "staging"
    });

    document.getElementById("cart-btn").onclick = () => checkout.openCart();
  };
</script>

Configuration

CheckoutConfig

interface CheckoutConfig {
  // === Credentials (required) ===
  storeId: string;              // Your Commerce Engine Store ID
  apiKey: string;               // Your Commerce Engine API Key
  environment?: "production" | "staging";  // Default: "production"

  // === Development ===
  url?: string;                 // Direct checkout URL (for local dev)
                                // Overrides environment-based URL

  // === Theme ===
  theme?: "light" | "dark" | "system";  // Per-session override. Default: "system"
                                // Primary config via Checkout Studio (Config API)

  // === Appearance ===
  appearance?: {
    zIndex?: number;            // Overlay z-index. Default: 99999
  };

  // === Authentication ===
  authMode?: "managed" | "provided";  // Default: "managed"
  accessToken?: string;         // Initial access token (if user logged in)
  refreshToken?: string;        // Initial refresh token

  // === Quick Buy (add item on init) ===
  quickBuy?: {
    productId: string;          // Product ID (required)
    variantId: string | null;   // Variant ID (required, null for non-variant)
    quantity?: number;          // Default: 1
  };
  sessionMode?: "continue-existing" | "force-new";  // Default: "continue-existing"
  autoDetectQuickBuy?: boolean; // Auto-detect from parent URL. Default: false

  // Drawer direction, features, and login methods are configured via
  // Checkout Studio (Config API) and are not SDK options.

  // === Callbacks ===
  onReady?: () => void;
  onOpen?: () => void;
  onClose?: () => void;
  onComplete?: (order: OrderData) => void;
  onCartUpdate?: (cart: CartData) => void;
  onTokensUpdated?: (data: AuthTokensUpdatedData) => void;
  onLogin?: (data: AuthLoginData) => void;
  onLogout?: (data: AuthLogoutData) => void;
  onSessionError?: () => void;
  onError?: (error: ErrorData) => void;
}

Example: Full Configuration

const checkout = await Commercengine.init({
  storeId: "store_xxx",
  apiKey: "ak_xxx",
  environment: "production",
  theme: "dark",
  appearance: {
    zIndex: 100000,
  },
  accessToken: userSession?.accessToken,
  refreshToken: userSession?.refreshToken,
  onReady: () => {
    console.log("Checkout loaded");
  },
  onComplete: (order) => {
    window.location.href = `/thank-you?order=${order.orderNumber}`;
  },
  onCartUpdate: (cart) => {
    updateCartBadge(cart.count);
  },
  onTokensUpdated: ({ accessToken, refreshToken }) => {
    // Tokens changed - sync to your auth system (fires on every token change)
    saveUserSession(accessToken, refreshToken);
  },
  onLogin: ({ user, loginMethod }) => {
    // User logged in via checkout - update UI
    console.log("User logged in via", loginMethod, ":", user?.email || user?.phone);
  },
  onLogout: () => {
    // User logged out - update UI for anonymous state
  },
  onSessionError: () => {
    // Session corrupted - SDK cleared all tokens, user needs to re-authenticate
    clearUserSession();
  },
});

Methods

openCart()

Open the cart drawer.

checkout.openCart();

openCheckout()

Open checkout directly, bypassing cart. Use for "Buy Now" flows.

checkout.openCheckout();

close()

Close the checkout overlay.

checkout.close();

updateTokens(accessToken, refreshToken?)

Sync authentication state from parent site. Call when the user logs in/out on your site, or when an async session bootstrap completes after init().

// When user logs in on your site
checkout.updateTokens(session.accessToken, session.refreshToken);

// When user logs out
checkout.updateTokens("", "");

Pre-ready safe. Calls made before onReady fires are buffered (latest pair wins) and delivered to the iframe as soon as it announces it can receive messages — typically well before full hydration. This unlocks the parallel-init pattern, where iframe boot and async token fetch run concurrently instead of serially.

addToCart(productId, variantId, quantity?)

Add an item to cart and open the cart drawer. This is the canonical implementation of "quick buy" functionality.

// Add a product (non-variant)
checkout.addToCart("prod_123", null, 1);

// Add a product variant
checkout.addToCart("prod_123", "var_456", 2);

// Quantity defaults to 1
checkout.addToCart("prod_123", "var_456");

Parameters:

  • productId (string, required) - Product ID
  • variantId (string | null, required) - Variant ID, or null for non-variant products
  • quantity (number, optional) - Quantity to add, defaults to 1

Behavior:

  • Adds item to cart and automatically opens the cart drawer
  • If item already in cart: adds to existing quantity
  • If item not in cart: adds new item
  • If no cart exists: creates cart with the item

getCart()

Get current cart state.

const cart = checkout.getCart();
console.log(cart.count, cart.total, cart.currency);

getTokens()

Get the latest token payload seen by the SDK from auth:tokens-updated. This avoids race conditions when .on("auth:tokens-updated") is attached after init(). Returns null if no token update event has been received yet.

const checkout = await Commercengine.init({ ... });
const tokens = checkout.getTokens();
// tokens.accessToken, tokens.refreshToken

destroy()

Remove checkout iframe and cleanup event listeners.

checkout.destroy();

Properties

Property Type Description
ready boolean Whether checkout is initialized and ready
open boolean Whether checkout overlay is currently visible

Events

Subscribe via callbacks (in config) or the event emitter.

Event Types

Event Callback Data Description
ready onReady - Checkout iframe loaded
open onOpen - Drawer opened
close onClose - All drawers closed
complete onComplete OrderData Order placed successfully
cart:updated onCartUpdate CartData Cart state changed
auth:tokens-updated onTokensUpdated AuthTokensUpdatedData Tokens changed (any reason)
auth:login onLogin AuthLoginData User logged in (semantic)
auth:logout onLogout AuthLogoutData User logged out (semantic)
auth:session-error onSessionError - Session corrupted, tokens cleared
error onError ErrorData Configuration error

Event Data Types

interface OrderData {
  id: string;           // Order ID
  orderNumber: string;  // Human-readable order number
}

interface CartData {
  count: number;        // Number of items
  total: number;        // Subtotal amount
  currency: string;     // Currency code (e.g., "USD", "INR")
}

interface AuthTokensUpdatedData {
  accessToken: string;
  refreshToken?: string;
}

interface AuthLoginData {
  user?: UserInfo;
  loginMethod?: LoginMethod;
}

interface AuthLogoutData {
  user?: UserInfo;        // Anonymous user info
}

interface ErrorData {
  message: string;
}

type LoginMethod = "whatsapp" | "phone" | "email";

interface Channel {
  id: string;
  name: string;
  type: string;
}

interface UserInfo {
  id: string;
  email: string | null;
  phone: string | null;
  username: string;
  firstName: string | null;
  lastName: string | null;
  storeId: string;
  isLoggedIn: boolean;
  isAnonymous: boolean;
  customerId: string | null;
  customerGroupId: string | null;
  anonymousId: string;
  channel: Channel;
  tokenExpiry: Date;
  tokenIssuedAt: Date;
}

Event Emitter API

// Subscribe
checkout.on("cart:updated", (cart) => {
  console.log("Cart:", cart.count, cart.total);
});

// Subscribe once
checkout.once("complete", (order) => {
  console.log("Order:", order.orderNumber);
});

// Unsubscribe
const handler = (cart) => console.log(cart);
checkout.on("cart:updated", handler);
checkout.off("cart:updated", handler);

Authentication Flow

Scenario 1: User Not Logged In

User will be prompted to login within checkout (WhatsApp, Phone, or Email OTP).

const checkout = await Commercengine.init({
  storeId: "store_xxx",
  apiKey: "ak_xxx",
  onTokensUpdated: ({ accessToken, refreshToken }) => {
    // Sync tokens to your auth system (fires on login, logout, refresh)
    saveSession({ accessToken, refreshToken });
  },
  onLogin: ({ user, loginMethod }) => {
    // Update UI with user info
    console.log("Logged in via", loginMethod, ":", user?.email);
  },
});

Scenario 2: User Already Logged In

Pass tokens to skip login screen.

const checkout = await Commercengine.init({
  storeId: "store_xxx",
  apiKey: "ak_xxx",
  accessToken: currentSession.accessToken,
  refreshToken: currentSession.refreshToken,
});

Scenario 3: Sync Auth Changes

When user logs in/out on your site, sync to checkout.

// User logs in on your site
myAuth.onLogin((session) => {
  checkout.updateTokens(session.accessToken, session.refreshToken);
});

// User logs out on your site
myAuth.onLogout(() => {
  checkout.updateTokens("", "");
});

Features

Features (loyalty, coupons, collect-in-store, free shipping progress, product recommendations) are configured via Checkout Studio (Config API). They are not SDK options.

Feature Default Description
loyalty true Loyalty points redemption at checkout
coupons true Coupon and promo code input
collectInStore false Option to collect order in-store
freeShippingProgress true Progress bar showing amount needed for free shipping
productRecommendations true "You may also like" product carousel in cart

TypeScript

All types are exported:

import {
  Commercengine,
  Checkout,
  type CheckoutConfig,
  type CheckoutEventType,
  type CheckoutFeatures,
  type DrawerDirection,
  type DrawerDirectionConfig,
  type Environment,
  type CartData,
  type OrderData,
  type AuthTokensUpdatedData,
  type AuthLoginData,
  type AuthLogoutData,
  type ErrorData,
  type UserInfo,
  type Channel,
  type LoginMethod,
} from "@commercengine/js";

URL Parameters (Advanced)

For direct iframe integration without the SDK, checkout uses a subdomain-based URL scheme. The store ID and environment are encoded in the hostname — they are not URL parameters.

URL format: https://{storeId}.checkout.commercengine.com?api_key=ak_xxx Staging: https://{storeId}.staging.checkout.commercengine.com?api_key=ak_xxx

Parameter Description Example
api_key API Key (required) ak_xxx
mode Deployment mode iframe
parent_origin Parent origin for postMessage https://brand.com
theme Theme preference (per-session override) light, dark
token Access token JWT string
refresh_token Refresh token JWT string
auth_mode Auth management provided, managed
product_id Quick buy product Product ID
variant_id Quick buy variant Variant ID
qty Quick buy quantity 1
session_mode Session behavior continue-existing, force-new

Features (loyalty, coupons, etc.) and drawer direction are configured via Checkout Studio (Config API) and are not accepted as URL parameters.

Auth Modes

  • managed (default): Checkout manages token lifecycle with auto-refresh. Best for lightweight integrations (marketing pages, buy-now buttons)
  • provided (recommended for storefront apps): Parent manages tokens via storefront SDK, checkout uses in-memory only. Tokens are synced via updateTokens() and onTokensUpdated

Session Modes

  • continue-existing (default): Add quick-buy item to existing cart
  • force-new: Delete existing cart and start fresh with only the quick-buy item

Note: When quick buy params are provided (via config or auto-detected), the cart drawer automatically opens once checkout is ready.

Auto-Detect Quick Buy

For ad-driven traffic, enable autoDetectQuickBuy to automatically parse quick buy params from the parent page URL:

// Ad link: https://brand.com?product_id=prod_123&variant_id=var_456&qty=2

const checkout = await Commercengine.init({
  storeId: "store_xxx",
  apiKey: "ak_xxx",
  autoDetectQuickBuy: true,  // Reads product_id, variant_id, qty from parent URL
});

This allows embedded checkout to handle ad clicks natively without custom URL parsing.

URL Cleanup: When quick buy params are detected, they are automatically removed from the browser URL (using replaceState) to prevent duplicate adds on page refresh.

Performance

Two tuning levers for time-to-interactive on the embedded checkout. Both are optional and additive.

Preconnect to the checkout origin

The first time a user visits a page that embeds checkout, the browser pays a cold DNS + TLS handshake to a brand-new origin ({storeId}.checkout.commercengine.com). Warming that connection in <head> before the iframe is created shaves the handshake cost off the first render.

Two helpers are exported for building the origin URL without coupling to the internal subdomain convention:

import { getCheckoutOrigin, getCheckoutIframeUrl } from "@commercengine/js";

const origin = getCheckoutOrigin({
  storeId: "store_xxx",
  environment: "production", // or "staging"
});
// → "https://store_xxx.checkout.commercengine.com"

const iframeUrl = getCheckoutIframeUrl({
  storeId: "store_xxx",
  apiKey: "ak_xxx",
  environment: "production",
});
// → "https://store_xxx.checkout.commercengine.com?api_key=ak_xxx&mode=iframe"

CDN consumers can access the same helpers via the global:

<script src="https://cdn.commercengine.com/v1.js" async></script>
<script>
  const origin = Commercengine.getCheckoutOrigin({
    storeId: "store_xxx",
    environment: "production",
  });
  // …inject <link rel="preconnect" href={origin} crossorigin> into <head>
</script>

Drop the origin into your root layout (Next.js / Remix / etc.):

<link rel="preconnect" href={origin} crossOrigin="anonymous" />
<link rel="dns-prefetch" href={origin} />

For more aggressive warmup (downloads the iframe document on idle bandwidth), use getCheckoutIframeUrl(...) with <link rel="prefetch" as="document">. Tradeoff: costs bandwidth even if the user never opens cart.

Parallel init (cold visitor speedup)

When the parent app uses authMode: "provided" and does not have a cached token in storage, the typical flow is serial:

// Serial: anonymous-token fetch blocks iframe creation
const { accessToken, refreshToken } = await fetchAnonymousToken();
const checkout = await Commercengine.init({
  storeId, apiKey, authMode: "provided", accessToken, refreshToken,
});

updateTokens() being pre-ready safe lets you reorder this so the iframe boot and token fetch overlap:

// Parallel: init starts loading the iframe immediately;
// updateTokens flushes once the iframe is ready to receive.
const checkout = await Commercengine.init({
  storeId, apiKey, authMode: "provided",
  // No accessToken — iframe will wait for tokens via postMessage
});

const { accessToken, refreshToken } = await fetchAnonymousToken();
checkout.updateTokens(accessToken, refreshToken);

@commercengine/js buffers the call, and as soon as the iframe announces it's listening (an internal handshake event), the tokens flush via postMessage. The iframe then hydrates and emits ready normally.

When this helps: first-time visitors with no token in storage. The anonymous-token round-trip (~100-300ms typical) overlaps with iframe boot instead of blocking it.

When this doesn't help: returning visitors with a cached token — pass it upfront via accessToken as usual; nothing to parallelize.