Skip to content

<seva-cart>

The <seva-cart> component collects registrations from <seva-event-register>, displays a cart summary, and handles checkout via Stripe Embedded Checkout. For free registrations, checkout completes instantly without Stripe.

The cart listens on document for seva:add-to-cart events. When items are added, it shows a summary with attendee names, ticket types, and prices. Clicking “Checkout” triggers a batch registration API call and, if payment is required, mounts Stripe Embedded Checkout inside the component’s Shadow DOM.

Cart contents are persisted in localStorage per tenant, so they survive page refreshes.

<script type="module" src="YOUR_COMPONENTS_URL/seva-cart.js"></script>
<seva-cart
tenant-slug="my-club"
api-url="https://api.seva.tools">
</seva-cart>

No extra wiring is needed — the cart automatically listens for seva:add-to-cart events fired by <seva-event-register>.

AttributeTypeDefaultDescription
tenant-slugstring''Required. Your organization’s tenant slug.
api-urlstring'https://api.seva.tools'Base URL of the Seva API.
theme'light' | 'dark''light'Color theme. Set via JavaScript property.
auth-tokenstring | nullnullSession token from <seva-auth>. Set this attribute when a user signs in.
EventPayloadWhen it fires
seva:cart-updated{ itemCount: number, totalCents: number }An item is added, removed, or the cart is cleared. Use this to update a cart badge elsewhere on the page.
seva:checkout-complete{ registrations: RegistrationResult[] }Checkout finishes successfully (free or paid).
seva:checkout-error{ message: string }Checkout fails (API error, Stripe error, etc.).
interface CartItem {
eventSlug: string;
eventName: string;
attendees: AttendeeInput[];
guestEmail?: string;
hostMemberId?: string;
decline?: boolean;
}
interface RegistrationResult {
id: string;
status: string;
eventId: string;
}

The component transitions through these views internally:

ViewDescription
cart-summaryDefault. Shows items, totals, “Clear” and “Checkout” buttons. If empty, shows “Your cart is empty”.
processingSpinner shown while creating registrations and setting up the Stripe session.
checkoutStripe Embedded Checkout is mounted inside the component.
status-pollingAfter Stripe completes, polls the API to confirm registration status (with exponential backoff up to 30 seconds).
confirmation”Registration Complete” success message.
errorError state with a “Back to Cart” button.
  1. Batch Register — The cart calls the API to create registrations for all items at once.
  2. Free checkout — If the total is $0, the cart skips Stripe, clears itself, and shows the confirmation view. seva:checkout-complete fires.
  3. Paid checkout — The API returns a Stripe clientSecret. The cart dynamically loads @stripe/stripe-js and mounts Stripe Embedded Checkout.
  4. Stripe completion — When Stripe signals completion, the cart clears its contents and polls the API to confirm the registrations are finalized.
  5. Confirmation — Once confirmed (or after a 30-second timeout), the confirmation view is shown and seva:checkout-complete fires.
EventBehavior
seva:add-to-cartAdds the registration to the cart store and emits seva:cart-updated.
seva:authenticatedUpdates internal auth state and refreshes cart items.
seva:signed-outClears auth state and refreshes cart items.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cart</title>
<script type="module" src="YOUR_COMPONENTS_URL/seva-cart.js"></script>
<style>
body { font-family: system-ui, sans-serif; max-width: 480px; margin: 2rem auto; padding: 0 1rem; }
#cart-badge {
display: inline-block; padding: 0.25rem 0.75rem;
background: #dbeafe; color: #1e40af;
border-radius: 999px; font-size: 0.85rem; font-weight: 600;
margin-bottom: 1rem;
}
</style>
</head>
<body>
<span id="cart-badge">Cart: 0 items</span>
<seva-cart id="cart"
tenant-slug="my-club"
api-url="https://api.seva.tools">
</seva-cart>
<script>
document.addEventListener('seva:cart-updated', (e) => {
const { itemCount, totalCents } = e.detail;
const dollars = (totalCents / 100).toFixed(2);
document.getElementById('cart-badge').textContent =
'Cart: ' + itemCount + ' item' + (itemCount !== 1 ? 's' : '') + ' ($' + dollars + ')';
});
document.addEventListener('seva:checkout-complete', () => {
alert('Registration confirmed! Check your email.');
});
</script>
</body>
</html>