JavaScript / TypeScript
@shoppable/checkout is isomorphic (browser + Node), ships ESM, CJS, and types,
and has no required dependencies (it uses the platform fetch).
Install
npm install @shoppable/checkoutpnpm add @shoppable/checkout<script src="https://developer.shoppable.com/downloads/checkout/latest/shoppable-checkout.global.js"></script>Quick example
import { ShoppableCheckout } from "@shoppable/checkout";
const checkout = new ShoppableCheckout({ token: "pk_live_…" });
const config = await checkout.getCartConfiguration(); // mints the session tokenconst products = await checkout.lookupProducts(["012345678905"]);const order = await checkout.prepareCheckout(cartContent);const { clientSecret } = await checkout.createIntent(order._id);// confirm `clientSecret` with Stripe.js in your UI, then:await checkout.completeCheckout(order._id);Constructor
new ShoppableCheckout(options: ShoppableCheckoutOptions)| Option | Type | Default | Description |
|---|---|---|---|
token | string | — | Publishable cart token (pk_live_… / pk_test_…). Safe in a browser bundle. Required unless you pass sessionToken. |
sessionToken | string | — | A session token you already hold. Skips the initial handshake. Pass token too to enable auto-refresh. |
baseUrl | string | https://ps.shoppable.com | API base. Use https://ps.staging.shoppable.com for pk_test_. |
origin | string | derived in browser | Sent as the Parent header; must be on the cart’s allowlist. Required in Node. |
onSessionToken | (token: string) => void | — | Called whenever a session token is minted or refreshed (sync it to your store). |
timeout | number | 30000 | Per-request timeout in ms. 0 disables it. |
maxRetries | number | 2 | Max retries for transient failures. |
apiVersion | string | built-in | Pinned API version, sent as Shoppable-Version. |
fetch | typeof fetch | global | Custom fetch for older runtimes. |
Methods
Each mutating method also accepts a trailing options?: RequestOptions
({ signal, timeout, idempotencyKey, headers }).
| Method | Returns | Notes |
|---|---|---|
getCartConfiguration() | Promise<CartConfiguration> | Fetches config and captures the session token. |
getSessionToken() | string | undefined | The current session token, if any. |
getCustomer() | Promise<object> | Customer context for the cart. |
lookupProducts(upcs, opts?) | Promise<ProductAvailability[]> | opts.includeVariations expands size/color variants. |
prepareCheckout(cart) | Promise<Checkout> | Starts a checkout. Carries an idempotency key. |
createIntent(checkoutId, body?) | Promise<{ clientSecret }> | Creates the Stripe PaymentIntent. |
updateCheckout(checkoutId, patch) | Promise<Checkout> | Patches an in-progress checkout. |
completeCheckout(checkoutId) | Promise<Checkout> | Finalizes after payment confirmation. |
validateCoupon(input) | Promise<CouponResult> | Normalizes the API’s { status: 0 } failure into { valid, message, raw }. |
lander.getSession(sid) | Promise<object> | AI cart lander session context. |
lander.getRecommendations(sid) | Promise<object> | Cross-sell + complementary recommendations. |
lander.recordCartItem(sid, upc) | Promise<object> | Records a lander-side add onto the shared AI cart. |
Reliability
- Retries — network errors, timeouts,
429(honoringRetry-After), and5xxare retried with exponential backoff. Only safe requests retry: reads, and mutations that carry an idempotency key. - Idempotency — every mutation sends an auto-generated
Idempotency-Key, so a retry can’t double-create an order or charge. Override per call with{ idempotencyKey }to make a user-triggered retry idempotent too. - Timeouts & cancellation —
timeoutplus a per-callAbortSignal.
const controller = new AbortController();await checkout.prepareCheckout(cart, { idempotencyKey: stableKey, signal: controller.signal,});Errors
Every API failure throws a subclass of ShoppableApiError, each carrying
status, the encrypted support ref, the server requestId, and the raw body.
| Error | When |
|---|---|
ShoppableAuthError | 401 token missing / invalid / expired |
ShoppableValidationError | 400 / 422 invalid request |
ShoppableNotFoundError | 404 |
ShoppableRateLimitError | 429 (has retryAfter ms) |
ShoppableServerError | 5xx |
ShoppableNetworkError | no response (offline, DNS, TLS) |
ShoppableTimeoutError | exceeded the timeout |
ShoppableConfigError | SDK misconfiguration (not an API response) |
import { ShoppableApiError, ShoppableRateLimitError } from "@shoppable/checkout";
try { await checkout.prepareCheckout(cart);} catch (err) { if (err instanceof ShoppableRateLimitError) { // back off and retry later } else if (err instanceof ShoppableApiError) { showError(err.message, err.ref); // quote err.ref / err.requestId to support }}Verifying downloads
Release zips are signed with Sigstore cosign (keyless):
cosign verify-blob \ --certificate checkout-0.1.0.zip.pem \ --signature checkout-0.1.0.zip.sig \ --certificate-identity-regexp 'https://github.com/72Lux/shoppable-developers/.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ checkout-0.1.0.zip