Build my booking setup

Checkout & Payments

Held while they decide. Released the moment they don't.

A pending payment never locks a seat forever, and never lets go too soon. If the payment never arrives, it releases on its own, through three separate paths at once, so it never depends on just one of them working.

  • 3 mechanisms A job scheduled for the exact moment, a sweep every minute, and cleanup if payment initiation itself fails
  • 2 layers of idempotency A duplicate Stripe event never charges twice, issues tickets twice, or corrupts the order
  • Automatic refund If a payment arrives late and the seat already went to someone else, it gets charged, caught and refunded on its own

Four pieces of checkout.

Each one solves a different way of getting paid, without duplicating business rules.

01

Temporary hold

Inventory locks for exactly as long as configured per product while the customer completes payment on Stripe, without blocking inventory over carts that never make it that far.

02

Optional hold per product

Some products accept a small overselling risk in exchange for simplicity, with no temporary hold at all. A single order can freely mix lines with and without a hold.

03

Checkout without an account

An anonymous buyer can pay directly when the organization allows it, or the channel can be required to route the call through its own server with a key.

04

Manual payment links

An agent can generate a Stripe link from the dashboard and send it over WhatsApp or email, for phone or social sales, with no checkout of its own.

How it resolves

How a payment gets protected, step by step

Five steps, from the moment a seat gets held to the moment the order is paid or refunded.

  1. 1

    The seat gets held

    The order is born pending payment, with its own expiry, and holds capacity or stock before a Stripe session is ever created. Nothing is confirmed yet.

  2. 2

    The payment session gets created

    The call to Stripe happens outside the database transaction: an external API must never keep a row lock open while it waits for a response.

  3. 3

    The webhook confirms, under lock

    When Stripe reports success, the order row gets locked and re-checked for whether it is still pending, closing the window where two deliveries of the same event could otherwise race each other.

  4. 4

    The hold becomes a sale

    If the order is still pending, the hold gets confirmed, tickets get issued, and the order moves to paid, all inside the same transaction.

  5. 5

    If it arrived late, it gets refunded

    If the order was no longer pending (expired, cancelled, or the seat no longer exists), the payment gets refunded automatically, with the reason logged for support to review.

The API, as it is

What checkout actually returns

The order with its status, its expiry, and the already-calculated total, alongside the Stripe payment URL. The customer never sees a half-built checkout of our own.

POST /api/v1/checkout
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
{
  "order": {
    "code": "01J8X8QK6Z9K2R3W4M5N6P7Q8R",
    "status": "pending_payment",
    "hold_expires_at": "2026-08-14T18:15:00Z",
    "currency": "EUR",
    "grand_total_amount": 53.24
  },
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_..."
}
200 Payment session created

The detail almost nobody explains

A late payment always finds things already closed

Releasing an expired hold also cancels any payment transaction that was still open for that order. So when a successful payment webhook arrives after the seat has expired, it never finds a "pending" transaction waiting for it: it finds one already cancelled, and the automatic refund starts from exactly there, as the normal path, not a rare exception.

Frequently asked questions

Nothing gets duplicated. There's a cheap early check for already-processed events, and a second check inside the same order-row lock, closing the window where two genuinely simultaneous deliveries of the same event could otherwise race each other. Only one ever wins.

It gets charged, the conflict gets detected, and it gets refunded automatically, with the reason logged for support to review. A ticket is never issued for a seat that already went to someone else.

No. A product can accept a small overselling risk in exchange for never blocking a seat while someone decides whether to pay. A single order can mix lines with and without a hold with no conflict.

Yes, if the organization allows it for its storefront. The call can also be required to route through the organization's own server with a key, to avoid exposing checkout directly to the browser.

Through three paths at once: a job scheduled for the exact moment of expiry, a backup sweep that checks every expired order every minute, and automatic cleanup if something fails while starting the payment. Any one of the three is enough, and none of them can ever release the same seat twice.

More bookings. More channels. The same calm.

Tiqory keeps availability, pricing, payments and access working together as your operation grows. Tell us where you are headed; we’ll show you how to get there without starting over.