<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.
Overview
Section titled “Overview”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.
Quick Example
Section titled “Quick Example”<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>.
Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
tenant-slug | string | '' | Required. Your organization’s tenant slug. |
api-url | string | 'https://api.seva.tools' | Base URL of the Seva API. |
theme | 'light' | 'dark' | 'light' | Color theme. Set via JavaScript property. |
auth-token | string | null | null | Session token from <seva-auth>. Set this attribute when a user signs in. |
Events
Section titled “Events”| Event | Payload | When 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.). |
Key types
Section titled “Key types”interface CartItem { eventSlug: string; eventName: string; attendees: AttendeeInput[]; guestEmail?: string; hostMemberId?: string; decline?: boolean;}
interface RegistrationResult { id: string; status: string; eventId: string;}Cart views
Section titled “Cart views”The component transitions through these views internally:
| View | Description |
|---|---|
cart-summary | Default. Shows items, totals, “Clear” and “Checkout” buttons. If empty, shows “Your cart is empty”. |
processing | Spinner shown while creating registrations and setting up the Stripe session. |
checkout | Stripe Embedded Checkout is mounted inside the component. |
status-polling | After Stripe completes, polls the API to confirm registration status (with exponential backoff up to 30 seconds). |
confirmation | ”Registration Complete” success message. |
error | Error state with a “Back to Cart” button. |
Checkout flow
Section titled “Checkout flow”- Batch Register — The cart calls the API to create registrations for all items at once.
- Free checkout — If the total is $0, the cart skips Stripe, clears itself, and shows the confirmation view.
seva:checkout-completefires. - Paid checkout — The API returns a Stripe
clientSecret. The cart dynamically loads@stripe/stripe-jsand mounts Stripe Embedded Checkout. - Stripe completion — When Stripe signals completion, the cart clears its contents and polls the API to confirm the registrations are finalized.
- Confirmation — Once confirmed (or after a 30-second timeout), the confirmation view is shown and
seva:checkout-completefires.
Listening for document-level events
Section titled “Listening for document-level events”| Event | Behavior |
|---|---|
seva:add-to-cart | Adds the registration to the cart store and emits seva:cart-updated. |
seva:authenticated | Updates internal auth state and refreshes cart items. |
seva:signed-out | Clears auth state and refreshes cart items. |
Full Example
Section titled “Full Example”<!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>