Skip to main content

Cart And Checkout

The Shoppex SDK manages the entire purchase flow: cart state, coupon validation, price formatting, and checkout handoff. All cart data lives in the browser (localStorage) and is scoped per store.
The SDK handles cart persistence automatically. You never need to read or write localStorage directly.

Quick Example

import shoppex from '@shoppex/sdk';

shoppex.init('my-shop');

// Add a product
shoppex.addToCart('prod_abc', 'var_123', 2);

// Check cart
const items = shoppex.getCart();       // CartItem[]
const count = shoppex.getCartItemCount(); // number

// Checkout
const result = await shoppex.checkout();
// autoRedirect is true by default — the customer is sent to the hosted checkout

Cart API

addToCart

Adds an item to the cart, or increments its quantity if it already exists.
import shoppex from '@shoppex/sdk';

// Simple — adds 1 unit
shoppex.addToCart('prod_abc', 'var_123');

// With quantity
shoppex.addToCart('prod_abc', 'var_123', 3);

// With options (addons, custom fields, price override)
shoppex.addToCart('prod_abc', 'var_123', 1, {
  addons: [{ id: 'addon_gift_wrap', quantity: 1 }],
  custom_fields: { engraving: 'Happy Birthday' },
});
ParameterTypeDefaultDescription
productIdstringrequiredProduct ID
variantIdstringrequiredVariant ID
quantitynumber1Units to add
optionsCartAddOptionsundefinedAddons, custom fields, price overrides

getCart

Returns the current cart contents as an array.
import shoppex from '@shoppex/sdk';

const items = shoppex.getCart(); // CartItem[]

items.forEach((item) => {
  console.log(item.product_id, item.variant_id, item.quantity);
});

getCartItemCount

Returns the total quantity across all cart items (sum of all quantity values). For the number of distinct line items, use getCartStats().item_count.
import shoppex from '@shoppex/sdk';

const totalQuantity: number = shoppex.getCartItemCount();
console.log(`${totalQuantity} item(s) in cart`);

// Distinct line items:
const { item_count } = shoppex.getCartStats();
console.log(`${item_count} unique product(s)`);

updateCartItem

Updates an existing cart item. You can change quantity, addons, custom fields, or price data.
import shoppex from '@shoppex/sdk';

// Change quantity to 5
shoppex.updateCartItem('prod_abc', 'var_123', { quantity: 5 });

// Add a custom field
shoppex.updateCartItem('prod_abc', 'var_123', {
  custom_fields: { engraving: 'New message' },
});
ParameterTypeDescription
productIdstringProduct ID of the item to update
variantIdstringVariant ID of the item to update
updatesPartial<Omit<CartItem, 'product_id' | 'variant_id'>>Fields to merge into the item

removeFromCart

Removes an item from the cart entirely.
import shoppex from '@shoppex/sdk';

shoppex.removeFromCart('prod_abc', 'var_123');

clearCart

Empties the entire cart.
import shoppex from '@shoppex/sdk';

shoppex.clearCart();

Types

CartItem

Each entry in the cart array is a CartItem:
FieldTypeRequiredDescription
product_idstringyesProduct ID
variant_idstringyesVariant ID
quantitynumberyesUnits of this line item
addonsCartAddon[]noAttached addons
custom_fieldsRecord<string, string>noKey-value custom data (e.g. engraving text)
price_variant_idstringnoOverride pricing variant (sent to the API during checkout)
price_data{ unit_price: number }noUI-only unit price override for theme-side calculations (not sent during checkout)

CartAddon

FieldTypeRequiredDescription
idstringyesAddon ID
quantitynumbernoAddon quantity

CartAddOptions

Passed as the fourth argument to addToCart:
FieldTypeDescription
addonsCartAddon[]Addons to attach
custom_fieldsRecord<string, string>Custom key-value data
price_variant_idstringOverride pricing variant
price_data{ unit_price: number }UI-only unit price override (does not affect invoice/checkout pricing)
During checkout, the SDK currently sends price_variant_id to the backend, but ignores price_data. So price_data can change what your cart UI displays, but it will not change the final invoice total.

Cart Storage

The SDK persists the cart in localStorage, scoped per store slug.
KeyPurpose
shoppex_cart_{storeSlug}Cart items array
shoppex_cart_meta_{storeSlug}Cart metadata (version, timestamps)
shoppex_cart_backup_{storeSlug}Backup of cart items (created before checkout)
shoppex_cart_backup_meta_{storeSlug}Backup metadata
You should never read or write these keys directly. Use the SDK methods. The key format is documented here for debugging purposes only.

Cart UI Updates

When the cart changes, your UI needs to re-render. The SDK does not emit events automatically. The recommended pattern is a thin event wrapper in your theme. Dispatch a CustomEvent after every cart mutation, then listen for it wherever your UI shows cart state.
// theme utility — e.g. src/utils/cart-events.ts
function emitCartChanged(): void {
  window.dispatchEvent(new CustomEvent('shoppex:cart-changed'));
}
shoppex:cart-changed is a theme-level pattern, not a built-in SDK feature. You own the dispatch and the listener.

Coupon Validation

Validate a coupon code before checkout. Optionally scope validation to a specific product. Use validateCoupon(code, productId) on a product page, and validateCoupon(code) when validating against the current cart.
import shoppex from '@shoppex/sdk';

shoppex.init('my-shop');

// Product page validation (no cart required)
const res = await shoppex.validateCoupon('SAVE10', 'prod_abc');

if (res.success && res.data?.valid) {
  console.log('Coupon is valid:', res.data);
} else {
  console.log('Invalid coupon');
}
ParameterTypeRequiredDescription
codestringyesCoupon code to validate
productIdstringnoScope validation to a single product
The response follows the standard SDKResponse<CouponValidation> shape: check res.success and res.data?.valid together.
If you call validateCoupon(code) without productId, the SDK validates against the current cart. If the cart is empty it returns { success: false, message: 'Cart is empty' }.

Price Formatting

The SDK provides formatPrice to format currency values using Intl.NumberFormat.
import shoppex from '@shoppex/sdk';

shoppex.formatPrice(19.99);                  // "$19.99" (depends on defaults)
shoppex.formatPrice(19.99, 'USD');           // "$19.99"
shoppex.formatPrice(19.99, 'EUR', 'de-DE'); // "19,99 €"
shoppex.formatPrice(1250, 'USD');            // "$1,250.00"
ParameterTypeDefaultDescription
amountnumber | stringrequiredPrice value in major units (e.g. 19.99, not 1999)
currencystringstore defaultISO 4217 currency code
localestringbrowser defaultBCP 47 locale tag
formatPrice passes the value directly to Intl.NumberFormat.format(). Use major units (e.g. 19.99 for $19.99), not minor units / cents.
For repeated formatting (e.g. a product list), create a reusable formatter:
import shoppex from '@shoppex/sdk';

const fmt = shoppex.createFormatter('EUR', 'de-DE');
console.log(fmt.format(19.99)); // "19,99 €"

Checkout

checkout()

Initiates the checkout flow. By default, the customer is automatically redirected to the hosted checkout page.
import shoppex from '@shoppex/sdk';

// Simplest — auto-redirect to checkout
await shoppex.checkout();

// With a coupon code (string shorthand)
await shoppex.checkout('SAVE10');

// With full options
const result = await shoppex.checkout({
  coupon: 'SAVE10',
  email: '[email protected]',
  autoRedirect: true,
});

CheckoutOptions

FieldTypeDefaultDescription
autoRedirectbooleantrueRedirect the browser to the checkout URL automatically
emailstringundefinedPre-fill the customer email on checkout
couponstringundefinedApply a coupon code
localestringundefinedReserved for future use — not currently processed by the backend

CheckoutResult

checkout() returns a CheckoutResult:
FieldTypeDescription
successbooleanWhether the checkout was created successfully
redirectUrlstring | undefinedThe hosted checkout URL
invoiceIdstring | undefinedThe created invoice ID
messagestring | undefinedError or status message

Checkout Flow

Custom Checkout Flow (No Auto-Redirect)

If you need to display a confirmation screen or track analytics before redirecting, disable auto-redirect:
import shoppex from '@shoppex/sdk';

const result = await shoppex.checkout({
  autoRedirect: false,
  coupon: 'SAVE10',
});

if (result.success && result.redirectUrl) {
  // Track analytics, show confirmation, etc.
  console.log('Invoice created:', result.invoiceId);

  // Redirect when ready
  window.location.href = result.redirectUrl;
  shoppex.clearCart();
}

buildCheckoutUrl

Builds the checkout URL without triggering redirect or clearing the cart. Useful for preview links or “open in new tab” flows.
import shoppex from '@shoppex/sdk';

const url = await shoppex.buildCheckoutUrl({ coupon: 'SAVE10' });
console.log(url); // https://checkout.shoppex.io/invoice/inv_...
Same signature as checkout() — accepts a coupon string or a CheckoutOptions object.

Cart Utilities (Advanced)

These methods are available for advanced use cases like cart recovery, migration, and diagnostics.
Returns a snapshot of current cart state and metadata.
import shoppex from '@shoppex/sdk';

const stats = shoppex.getCartStats();
console.log(stats);
CartStats:
FieldTypeDescription
item_countnumberDistinct line items
total_quantitynumberSum of all item quantities
last_modifiednumberTimestamp of last cart change
versionnumberCart version counter
has_backupbooleanWhether a backup exists
integrity_validbooleanWhether the cart passes integrity checks
total_pricenumberComputed total price
total_price_is_estimatebooleanWhether the price is an estimate (e.g. missing price data)
Manually create or restore a cart backup. The SDK automatically creates a backup before checkout, but you can also trigger this manually.
import shoppex from '@shoppex/sdk';

// Save current cart as backup
shoppex.createCartBackup();

// Later — restore from backup (returns false if no backup exists)
const restored = shoppex.restoreCartFromBackup(); // boolean
Checks that the cart data structure is consistent and valid.
import shoppex from '@shoppex/sdk';

const valid = shoppex.validateCartIntegrity(); // boolean
if (!valid) {
  console.warn('Cart data may be corrupted');
}
Merges an external array of cart items with the current cart. Useful for syncing carts across devices or migrating from another system.
import shoppex from '@shoppex/sdk';

const externalItems = [
  { product_id: 'prod_xyz', variant_id: 'var_456', quantity: 2 },
];

const merged = shoppex.mergeBaskets(externalItems); // CartItem[]
Moves a cart item from one product/variant combination to another. Useful for variant swaps.
import shoppex from '@shoppex/sdk';

shoppex.moveBasketItem('prod_abc', 'var_old', 'prod_abc', 'var_new');

Common Mistakes

All SDK methods require shoppex.init(slug) to have been called first. Without it, the SDK does not know which store’s cart to operate on.
// Wrong
shoppex.addToCart('prod_abc', 'var_123'); // SDK not initialized

// Correct
shoppex.init('my-shop');
shoppex.addToCart('prod_abc', 'var_123');
The coupon validation response has two levels of success. Both must be checked.
// Wrong — only checks outer success
const res = await shoppex.validateCoupon('SAVE10');
if (res.success) { /* coupon might still be invalid */ }

// Correct
if (res.success && res.data?.valid) {
  // Coupon is actually valid
}
The internal storage format may change between SDK versions. Always use getCart(), getCartItemCount(), and other SDK methods.
// Wrong
const raw = localStorage.getItem('shoppex_cart_my-shop');

// Correct
const items = shoppex.getCart();
The SDK does not dispatch DOM events. The shoppex:cart-changed pattern is a theme-level convention that you implement yourself. See Cart UI Updates above.
When using autoRedirect: false, the SDK does not clear the cart for you. Call clearCart() after redirecting.
// With autoRedirect: false, you must clear manually
const result = await shoppex.checkout({ autoRedirect: false });
if (result.success && result.redirectUrl) {
  window.location.href = result.redirectUrl;
  shoppex.clearCart(); // Don't forget this
}

Next Steps