@behindthescenes/cart
v0.0.16
Published
Browser cart SDK for BTS external-site checkout links.
Readme
@behindthescenes/cart
Browser SDK for BTS external-site cart state and checkout-link handoff. Use it to persist selected products, validate availability with the BTS API, and send shoppers to static or dynamic BTS checkout links.
Overview
The BTS Cart SDK provides:
- Persistent cart state - Selected products and quantities are stored in browser
localStorageby default. - Dynamic checkout links - Generates BTS
/c/:checkoutLinkId?cart=...URLs from{ lineItemId, quantity, discount }cart items. - Static checkout links - Supports one-click static checkout links through the same SDK API.
- Multiple checkout links - Configure named checkout links and choose which one to use at checkout.
- Site-key protection - Cart URL generation is validated by the BTS website cart API using the same public site key and verified-domain model as
@behindthescenes/analytics. - Availability validation - The API validates checkout link status, products/access tiers, requested quantity, and discounts before returning a checkout URL.
- Quantity - The SDK only normalizes quantities to integers ≥ 1. It does not cap counts to per-line inventory or API max; those rules apply on
checkout-urlonly.
Installation
npm/yarn/pnpm/bun
npm install @behindthescenes/cartpnpm add @behindthescenes/cartyarn add @behindthescenes/cartbun add @behindthescenes/cartBrowser Bundle (Hosted)
<script type="module">
import { createBTSCart } from "https://behindthescenes.com/sdk/@behindthescenes/cart/latest/browser/browser.js";
window.btsCart = createBTSCart({
siteKey: "your-public-site-key",
checkoutLinks: {
tickets: {
checkoutLinkId: "your-checkout-link-id",
mode: "dynamic"
}
},
defaultCheckoutLinkKey: "tickets"
});
</script>Quick Start
import { createBTSCart } from "@behindthescenes/cart";
const cart = createBTSCart({
siteKey: "your-public-site-key",
checkoutLinks: {
tickets: {
checkoutLinkId: "https://dev.behindthescenes.com/c/1a9d4f91-c1dd-455d-aa4c-851b296aff75",
mode: "dynamic",
},
},
defaultCheckoutLinkKey: "tickets",
});
cart.addItem({
id: "garnet",
accessTierId: "2dd3b989-616e-4e2f-9c47-21e1288d3823",
checkoutLinkKey: "tickets",
name: "Garnet",
unitPrice: 395,
fullPrice: 550,
quantity: 1,
discount: "GARNET-EARLYBIRD",
});
await cart.checkout("tickets");Setup in BTS
Before using the SDK, configure the destination site in BTS:
- In BTS, go to the relevant space.
- Configure the external website integration and copy the public site key.
- Verify the external site domain for production use.
- Create a checkout link.
- For dynamic checkout links, use either the checkout link ID or the full checkout link URL plus the relevant product/access-tier line item IDs.
Local development can use localhost origins without verified-domain matching when the API runs in a local/development/test environment.
Dynamic Checkout Links
Dynamic checkout links use the BTS checkout cart query parameter:
encodeURIComponent(
JSON.stringify([
{
lineItemId: "2dd3b989-616e-4e2f-9c47-21e1288d3823",
quantity: 1,
discount: "c16cfeea-c604-4009-851c-05e6dcdfbcea",
},
]),
);The SDK sends that cart payload to the BTS API for validation and receives a final checkout URL. The returned URL is equivalent to:
https://behindthescenes.com/c/<checkoutLinkId>?cart=<encoded-json>For dynamic checkout links, set accessTierId or productId to the BTS catalog UUID (recommended). The SDK sends that value as lineItemId on checkout-url, matching how BTS checkout and webhooks resolve lines.
When the link has no saved checkout-link line items, BTS synthesizes the catalog from the space’s public products and tiers — use the tier or product UUID, not a site-local slug in id.
You may still pass lineItemId alone (persisted spaces_checkout_link_items.id, or tier/product UUID). If both a slug and a catalog UUID are present, the SDK prefers accessTierId / productId for checkout.
discount can be either the internal promo-code ID or the customer-facing promo code. The cart API resolves customer-facing codes to the internal IDs required by the BTS checkout page before returning the final URL.
Checkout link config also accepts full BTS checkout URLs:
const cart = createBTSCart({
siteKey: "your-public-site-key",
checkoutLinks: {
tickets: {
checkoutLinkId: "https://behindthescenes.com/c/1a9d4f91-c1dd-455d-aa4c-851b296aff75?discountCode=EARLYBIRD",
mode: "dynamic",
},
},
});The SDK extracts the checkout link ID and default discountCode from the URL.
Static Checkout Links
const cart = createBTSCart({
siteKey: "your-public-site-key",
checkoutLinks: {
vip: {
checkoutLinkId: "static-checkout-link-id",
mode: "static",
discountCode: "VIP-EARLYBIRD",
},
},
});
await cart.checkout("vip");Static links ignore cart items and validate the configured checkout link before redirecting.
Multiple Checkout Links
const cart = createBTSCart({
siteKey: "your-public-site-key",
checkoutLinks: {
tickets: {
checkoutLinkId: "dynamic-ticket-link-id",
mode: "dynamic",
},
merch: {
checkoutLinkId: "dynamic-merch-link-id",
mode: "dynamic",
},
},
defaultCheckoutLinkKey: "tickets",
});
cart.addItem({
id: "shirt",
lineItemId: "product-line-item-id",
checkoutLinkKey: "merch",
name: "Event Shirt",
unitPrice: 49,
});
await cart.checkout("merch");Persistence
The SDK persists cart state to localStorage by default.
const cart = createBTSCart({
siteKey: "your-public-site-key",
storageKey: "my-site-cart",
persist: true,
});Disable persistence:
const cart = createBTSCart({
siteKey: "your-public-site-key",
persist: false,
});Provide a custom storage adapter:
const cart = createBTSCart({
siteKey: "your-public-site-key",
storage: {
getItem: (key) => sessionStorage.getItem(key),
setItem: (key, value) => sessionStorage.setItem(key, value),
removeItem: (key) => sessionStorage.removeItem(key),
},
});State API
cart.addItem(item);
cart.removeItem("garnet");
cart.setQuantity("garnet", 2);
cart.increment("garnet");
cart.decrement("garnet");
cart.clear();
const items = cart.getItems();
const itemCount = cart.getItemCount();
const subtotal = cart.getSubtotal();
const total = cart.getTotal();Subscribe to changes:
const unsubscribe = cart.subscribe((state) => {
console.log(state.items);
});
unsubscribe();Checkout API
Return a checkout URL without redirecting:
const url = await cart.resolveCheckoutUrl("tickets");Validate and redirect:
await cart.checkout("tickets");React Integration
import { createBTSCart, type BTSCart, type BTSCartItem } from "@behindthescenes/cart";
import { createContext, useContext, useEffect, useMemo, useState } from "react";
const CartContext = createContext<BTSCart | null>(null);
export function CartProvider({ children }: { children: React.ReactNode }) {
const cart = useMemo(
() =>
createBTSCart({
siteKey: "your-public-site-key",
checkoutLinks: {
tickets: {
checkoutLinkId: "dynamic-ticket-link-id",
mode: "dynamic",
},
},
defaultCheckoutLinkKey: "tickets",
}),
[],
);
return <CartContext.Provider value={cart}>{children}</CartContext.Provider>;
}
export function useCartItems(): BTSCartItem[] {
const cart = useContext(CartContext);
if (!cart) throw new Error("useCartItems must be used inside CartProvider");
const [items, setItems] = useState(() => cart.getItems());
useEffect(() => cart.subscribe((state) => setItems(state.items)), [cart]);
return items;
}Configuration
type BTSCartInit = {
siteKey: string;
endpoint?: string;
checkoutLinks?: Record<string, BTSCartCheckoutLinkConfig>;
defaultCheckoutLinkKey?: string;
debug?: boolean;
persist?: boolean;
storageKey?: string;
storage?: BTSCartStorage;
requestHeaders?: HeadersInit | ((request: BTSCartRequestContext) => HeadersInit | Promise<HeadersInit>);
};Defaults:
endpoint:https://api.bts.it.com/v2/website/cartpersist:truestorageKey:bts-cartdefaultCheckoutLinkKey: first configured checkout link key, ordefault
Browser Global API
The hosted browser bundle installs:
window.BTSCartwindow.createBTSCartwindow.btsCartwindow.btsCartDataLayerwindow.btsCartCommand
Queued command example:
<script>
window.btsCartDataLayer = window.btsCartDataLayer || [];
function btsCartCommand(){window.btsCartDataLayer.push(arguments);}
btsCartCommand("config", {
siteKey: "your-public-site-key",
checkoutLinks: {
tickets: {
checkoutLinkId: "dynamic-ticket-link-id",
mode: "dynamic"
}
}
});
</script>Development
bun install
bun run type-check
bun test
bun run buildBuild artifacts:
dist/esm/index.jsdist/cjs/index.jsdist/browser/browser.jsdist/types/index.d.tsdist/manifest.json
Hosted Bundle
Sync the hosted bundle into apps/bts-web/public/sdk/@behindthescenes/cart:
bun run build:hostedForce a rebuild before syncing:
bun run build:hosted:forceHosted layout matches @behindthescenes/analytics:
latest- one major-version directory, for example
0or1
Release
The cart SDK uses the same release pipeline as @behindthescenes/analytics.
Manual release from GitHub Actions:
- Open Cart SDK Release.
- Choose
patch,minor, ormajor. - Run the workflow from
main.
The workflow:
- installs dependencies with Bun
- bumps the package version
- runs
bun run release:check - publishes with
npm publish --provenance --access public - commits the version bump back to
main
Tag release:
git tag cart-sdk-v0.0.2
git push origin cart-sdk-v0.0.2The tag version is applied to packages/cart/package.json before publishing.
