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:

  1. Loading - Fetches event data from Qariyb API
  2. Tickets - Displays available tickets with quantity controls
  3. Details - Customer information form (name, email, phone, custom questions)
  4. Payment - Stripe Payment Element (for paid events)
  5. Processing - Payment confirmation in progress
  6. 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';
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)
})

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>

Was this page helpful?