qariyb-checkout
The checkout component provides a complete purchasing experience with ticket selection, customer details collection, Stripe payment processing, and order confirmation.
<qariyb-checkout event-id="01HWXYZ123ABC456"></qariyb-checkout>
Checkout Flow
The component progresses through these steps:
- Loading - Fetches event data from Qariyb API
- Tickets - Displays available tickets with quantity controls
- Details - Customer information form (name, email, phone, custom questions)
- Payment - Stripe Payment Element (for paid events)
- Processing - Payment confirmation in progress
- Success - Order confirmation with ticket details
Attributes
Required
- Name
event-id- Type
- string
- Description
The event ULID from your Qariyb dashboard.
Optional
- Name
theme- Type
- 'light' | 'dark' | 'flat' | 'minimal'
- Description
Theme preset. Defaults to
'light'.
- Name
accent-color- Type
- string
- Description
Primary color in hex format. Defaults to
'#10b981'.
- Name
prefill-email- Type
- string
- Description
Pre-fill customer email field.
- Name
prefill-name- Type
- string
- Description
Pre-fill customer name field.
- Name
embedded- Type
- boolean attribute
- Description
Removes the component's outer border and padding so it sits flush inside a container you already style — used when rendering the checkout inside a modal. Present means on; omit the attribute to keep the default framed card.
Callback Attributes
- Name
on-ready- Type
- function
- Description
Called when component mounts. Receives
{ eventId }.
- Name
on-event-loaded- Type
- function
- Description
Called when event data is fetched. Receives
{ event }.
- Name
on-event-error- Type
- function
- Description
Called when event data fails to load. Receives
{ error, eventId }.
- Name
on-step-changed- Type
- function
- Description
Called when checkout step changes. Receives
{ from, to, eventId }.
- Name
on-cart-updated- Type
- function
- Description
Called when cart is modified. Receives
{ cart, action, ticketId?, ticketName? }.
- Name
on-details-submitted- Type
- function
- Description
Called when the customer details form is submitted. Receives
{ name, email, phone? }.
- Name
on-payment-started- Type
- function
- Description
Called when Stripe payment begins. Receives
{ cartId, amount, currency }.
- Name
on-payment-processing- Type
- function
- Description
Called while the payment is confirming. Receives
{ paymentIntentId? }.
- Name
on-discount-applied- Type
- function
- Description
Called when a discount code is accepted and the total is repriced. Receives
{ discount, previousTotal }.
- Name
on-discount-failed- Type
- function
- Description
Called when a discount code is rejected. Receives
{ code, error }.
- Name
on-success- Type
- function
- Description
Called on successful checkout. Receives
{ order, tickets, requiresApproval }.
- Name
on-error- Type
- function
- Description
Called when an error occurs. Receives
{ error, code?, step? }.
Examples
Basic Usage
<qariyb-checkout event-id="01HWXYZ123ABC456"></qariyb-checkout>
With Theme
<qariyb-checkout
event-id="01HWXYZ123ABC456"
theme="dark"
accent-color="#8b5cf6"
></qariyb-checkout>
With Prefilled Data
<qariyb-checkout
event-id="01HWXYZ123ABC456"
prefill-email="user@example.com"
prefill-name="Ali Hassan"
></qariyb-checkout>
With Callbacks
<qariyb-checkout
event-id="01HWXYZ123ABC456"
on-success="handleCheckoutSuccess"
on-error="handleCheckoutError"
on-step-changed="trackCheckoutProgress"
></qariyb-checkout>
<script>
function handleCheckoutSuccess(data) {
console.log('Order ID:', data.order.id);
console.log('Total:', data.order.total);
console.log('Tickets:', data.tickets.length);
if (data.requiresApproval) {
console.log('Registration pending approval');
}
}
function handleCheckoutError(data) {
console.error('Checkout failed:', data.error);
}
function trackCheckoutProgress(data) {
analytics.track('checkout_step', {
from: data.from,
to: data.to,
});
}
</script>
Events
qariyb:checkout:ready
Emitted when the component mounts.
interface CheckoutReadyDetail {
eventId: string;
}
qariyb:checkout:event-loaded
Emitted when event data is successfully fetched.
interface CheckoutEventLoadedDetail {
event: QariybEvent;
}
qariyb:checkout:step-changed
Emitted when the checkout step changes.
interface CheckoutStepChangedDetail {
from: CheckoutStep;
to: CheckoutStep;
eventId: string;
}
type CheckoutStep =
| 'loading'
| 'tickets'
| 'details'
| 'payment'
| 'processing'
| 'success'
| 'error';
checkout.addEventListener('qariyb:checkout:step-changed', (e) => {
const { from, to } = e.detail.data;
if (to === 'details') {
analytics.track('checkout_started');
} else if (to === 'payment') {
analytics.track('entered_payment');
}
});
qariyb:checkout:cart-updated
Emitted when cart items change.
interface CheckoutCartUpdatedDetail {
cart: Cart;
action: 'add' | 'remove' | 'clear';
ticketId?: string;
ticketName?: string;
}
interface Cart {
items: CartItem[];
totalCents: number;
totalQuantity: number;
currency: string;
}
qariyb:checkout:success
Emitted on successful checkout completion.
interface CheckoutSuccessDetail {
order: Order;
tickets: OrderTicket[];
requiresApproval: boolean;
}
interface Order {
id: string; // ULID — use as a key, not as the buyer-facing reference
number: string; // Human-readable order number, e.g. 'ORD2026E2CJD6WN'
status: OrderStatus;
subtotal: number; // Minor units, before any discount
discount_amount: number; // Minor units taken off; 0 when no code was applied
total: number; // Minor units actually charged
currency: string; // 3-letter ISO 4217, uppercase
billing_name: string;
billing_email: string;
requires_approval: boolean;
created_at: string; // ISO 8601
}
interface OrderTicket {
id: string; // ULID
number: string; // Human-readable ticket number, e.g. 'TK6K6V6NGKT2WZ'
ticket_type: string; // Name of ticket type
status: TicketStatus;
// The buyer's name on the first ticket, and the literal 'Unassigned' on any
// ticket not yet allocated to a person. Never null.
attendee_name: string;
}
type OrderStatus =
| 'pending' | 'completed' | 'cancelled'
| 'refunded' | 'failed' | 'refund_pending';
type TicketStatus =
| 'purchased' | 'used' | 'cancelled'
| 'refunded' | 'transferred' | 'pending_approval';
OrderTicket carries no QR code. Tickets are delivered by email after the
order completes; the confirmation response is a receipt, not a ticket render.
Use order.number as the confirmation reference — that is what the organizer sees on their side, so it is the one a buyer can quote. order.id is the ULID, useful as a stable key but not as a reference.
checkout.addEventListener('qariyb:checkout:success', (e) => {
const { order, tickets, requiresApproval } = e.detail.data;
// Track conversion
analytics.track('purchase', {
order_id: order.id,
revenue: order.total / 100,
currency: order.currency,
items: tickets.length,
});
if (requiresApproval) {
showMessage('Your registration is pending approval');
} else {
showConfirmation(order.id);
}
});
qariyb:checkout:discount-applied
Emitted when a discount code is accepted. By the time this fires the server has
already repriced the Stripe PaymentIntent, so discount.new_total is what the
card will actually be charged.
interface CheckoutDiscountAppliedDetail {
discount: DiscountApplication;
/** Total before the discount, in minor units */
previousTotal: number;
}
interface DiscountApplication {
code: string; // Canonical code as stored
discount_type: 1 | 2; // 1 = fixed amount, 2 = percentage
discount_type_name: string; // Readable form: 'fixed_amount' or 'percentage'
discount_value: number; // Minor units when type 1, whole percent when type 2
formatted_value: string; // Display-ready, e.g. '20%' or '£5.00'
discount_amount: number; // Amount taken off, in minor units
new_total: number; // New charge amount, in minor units
currency: string;
}
checkout.addEventListener('qariyb:checkout:discount-applied', (e) => {
const { discount, previousTotal } = e.detail.data
analytics.track('discount_applied', {
code: discount.code,
saved: (previousTotal - discount.new_total) / 100,
})
})
qariyb:checkout:discount-failed
Emitted when a discount code is rejected. error is the server's reason and is
the only place that reason exists — show it verbatim.
interface CheckoutDiscountFailedDetail {
code: string;
error: string;
}
qariyb:checkout:error
Emitted when an error occurs.
interface CheckoutErrorDetail {
error: string;
code?: string;
step?: CheckoutStep;
}
Discount codes
The checkout renders a discount input on paid events. Applying a code calls the API, which reprices the PaymentIntent before payment is confirmed.
checkout.addEventListener('qariyb:checkout:discount-applied', (e) => {
console.log('New total:', e.detail.data.discount.new_total)
})
checkout.addEventListener('qariyb:checkout:discount-failed', (e) => {
console.warn('Rejected:', e.detail.data.error)
})
There is no "remove discount" action. The server cannot un-apply a repricing, so applying a different code over the top is the supported way to change it.
The input is omitted entirely on free events, which have no PaymentIntent to reprice.
Styling
Container Sizing
The checkout adapts to its container. Set max-width for optimal display:
<div style="max-width: 500px; margin: 0 auto;">
<qariyb-checkout event-id="01HW..."></qariyb-checkout>
</div>
Theme Configuration
window.QariybConfig = {
publishableKey: 'pk_live_...',
theme: 'light',
variables: {
colorPrimary: '#7c3aed',
borderRadius: '12px',
},
rules: {
'.Button--primary': {
textTransform: 'uppercase',
},
},
};
CSS Classes
Target these classes in component rules:
.Container- Main wrapper.TicketRow,.TicketRow--selected- Ticket items.QuantityControl,.QuantityButton- Quantity +/-.OrderSummary,.SummaryTotal- Order summary.Button--primary- Primary buttons.Input,.Label- Form elements.Success,.Error- State screens
Free vs Paid Events
The checkout automatically handles free events:
- Skips Stripe payment step
- Shows "Complete Registration" instead of "Pay"
- Processes immediately without payment
// Detect free events
checkout.addEventListener('qariyb:checkout:event-loaded', (e) => {
const tickets = e.detail.data.event.ticket_types;
const allFree = tickets.every(t => t.is_free);
if (allFree) {
console.log('Free event - no payment required');
}
});
Approval Flow
Events may require approval after registration:
checkout.addEventListener('qariyb:checkout:success', (e) => {
if (e.detail.data.requiresApproval) {
showMessage('Your registration is pending organizer approval');
} else {
showMessage("You're registered! Check your email for tickets.");
}
});
Complete Example
<!DOCTYPE html>
<html>
<head>
<title>Get Tickets</title>
<script>
window.QariybConfig = {
publishableKey: 'pk_live_...',
theme: 'light',
variables: {
colorPrimary: '#4f46e5',
},
};
</script>
<script src="https://js.qariyb.com/v1/qariyb.js"></script>
<style>
.checkout-container {
max-width: 500px;
margin: 40px auto;
padding: 0 20px;
}
</style>
</head>
<body>
<div class="checkout-container">
<qariyb-checkout
event-id="01HWXYZ123ABC456"
on-success="handleSuccess"
></qariyb-checkout>
</div>
<script>
function handleSuccess(data) {
window.location.href = `/confirmation?order=${data.order.id}`;
}
</script>
</body>
</html>