Skip to the article
Aniq-UI

TicketoryOrders, payments and refunds

Orders, payments and refunds

How a hold becomes a paid order: checkout on Stripe's or PayPal's own page, the test payment, free tickets, the booking fee, the orders in the dashboard, and refunds that wait for the provider to confirm.

For the Full Stack package

From a hold to a paid order

  1. The buyer holds seats or places on the website, then gives a name and an email, chooses a payment method and accepts the terms. No account is needed.
  2. The API turns the hold into an order (a reference such as TKT-1042) in Awaiting payment, with the hold's amounts. The browser never sends a price.
  3. The buyer pays on the provider's own page. Card details are never typed on your site.
  4. The provider confirms, and the API issues one ticket per seat or place, marks the order Paid, emails the tickets and notifies the team. Every email about the order is in the language of the website page the buyer checked out on, English or Arabic, with the event's title in that language.

The website never decides that an order is paid. While the buyer waits on "Confirming your payment", the API asks the provider itself, so a payment turns into tickets even on a laptop that the provider's webhook cannot reach. A cancelled payment keeps the hold for another try; a failed one keeps the buyer's details.

Payment methods

MethodSet in the API's .envHow it works
StripeSTRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETStripe's hosted Checkout page. Stripe returns the buyer to the website, and the API confirms from the signed webhook or by asking Stripe.
PayPalPAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID, PAYPAL_ENV (sandbox or live)The buyer approves on PayPal, which returns them to the API; the API captures the payment and only then sends them to the website.
Test paymentPAYMENTS_SIMULATOR (auto by default, on or off)A page of the website that stands in for a provider, with Pay, Decline and Cancel. It moves no money.
  • Each provider is offered once its keys are set. Use test keys first (sk_test_..., the PayPal sandbox): they move no money.
  • With PAYMENTS_SIMULATOR=auto, Test payment is offered while no live key is set, so a fresh install can sell a ticket at once and a live shop never shows it.
  • The dashboard's Settings, Payments shows each provider as connected, in test mode or not configured, with the variables to set and its webhook address. Keys are never typed into the dashboard.

Webhooks

Before going live, register the webhook with each provider on your API's public address:

ProviderAddressEvents
Stripehttps://<your-api>/api/webhooks/stripecheckout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, refund.updated. Its signing secret goes in STRIPE_WEBHOOK_SECRET.
PayPalhttps://<your-api>/api/webhooks/paypalPAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED, PAYMENT.CAPTURE.REFUNDED. Its id goes in PAYPAL_WEBHOOK_ID.

Every call is checked against the provider's signature before anything else, and each event is acted on once, however often it arrives. Set STOREFRONT_URL to the website's address, where payment pages send buyers back, and PUBLIC_API_URL to the API's, for PayPal's return.

Free tickets

A ticket type priced at 0 carries no fee. When an order costs nothing, checkout shows no payment method, the total reads Free, and Get tickets confirms the order at once, with its passes, its email and the team's notification, with or without payment keys. No provider is asked, not even the test payment.

A free order shows "Free" as its provider. It has nothing to refund: its tickets are voided instead. The seed's Creative Mornings is a free event, and Meadow Days sells free tickets for children under 12.

The booking fee and tax

  • The booking fee is a percentage of each ticket's price, rounded once per ticket and added to the attendee's total at checkout: the attendee always pays it. It is set in Settings, General, from 0 to 20% (fee_bps, 300 or 3% as seeded: two $20.00 seats come to $41.20).
  • The currency is the workspace's (USD as seeded), chosen in the same tab from USD, EUR, GBP, SAR and AED, and it locks once the first ticket is paid for.
  • Ticketory charges no tax. Set up tax for your country before taking real payments. Payment-provider fees are separate.

Order statuses

StatusMeaning
Awaiting paymentCheckout opened the provider's page; nothing is paid yet.
PaidConfirmed, with tickets. A free order is paid at checkout.
Payment failedThe provider declined the payment.
ExpiredThe hold ran out without a payment.
In reviewThe amount differed, or a payment arrived after the hold ran out and its seats had gone. The API refunds such a late payment in full by itself, and the overview counts it as needing attention.
Partly refunded, RefundedSome or all of its tickets were refunded.

The orders page

  • Tiles for Paid, Pending, Refunded and Failed with their counts, a search by order reference, name or email, and filters for event, payment status (Paid, Free, Pending, Refunded, Failed, In review) and period. Newest first, ten a page, with Export CSV.
  • An order's page shows its details, the payment timeline with a link into Stripe's or PayPal's own dashboard, its tickets with Reissue, Void and Refund, and its refunds.

Refunds

  1. Refunds are for whole tickets. Select the tickets on the order's page and press Refund: the amount (each ticket's price and its fee) is worked out by the API, never typed. Choose a reason and tick that the refund revokes admission.
  2. The tickets are suspended at once, so nobody is admitted on them, and the refund is pending until the provider confirms it.
  3. Once it confirms, the tickets are voided, their seats or places go back on sale (unless the holder was already admitted), the order becomes partly refunded or refunded, and the buyer is emailed in the order's language.

If the provider refuses or never answers, the refund is failed and the tickets stay suspended until someone decides: Retry refund sends it again with the same provider key, so it can never pay out twice, and Restore admission gives the tickets back their admission with no money moved. An order paid with the test payment is refunded the same way, with no money moving; the seeded orders' refunds settle a few seconds after they are asked for.

A cancelled event emails its buyers but moves no money: refund each of its orders from its page.

Who can do what

PermissionAllowsSeeded roles
orders.viewReading orders, and being notified of paid onesOwner, Manager, Finance
refunds.createRefunding, retrying and restoring refundsOwner, Finance
tickets.manageReissuing and voiding ticketsOwner, Finance
settings.viewSeeing the payment providers' stateOwner

The routes behind them

  • GET /api/checkout/methods lists what is offered right now; POST /api/checkout needs the hold's X-Hold-Token and an Idempotency-Key and answers the order reference, the provider's page and an order access token, sent afterwards as X-Order-Token.
  • An order reference alone opens nothing: the buyer reads an order with that token or the attendee's own bearer token.
  • POST /api/admin/orders/:reference/refunds needs an Idempotency-Key and the order's version; an older version is a 409 VERSION_CONFLICT. A free order answers refund_nothing_left.

Stuck on a step?

Find a fix before you start over.

Troubleshooting

Cookie Preferences

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising.