Migration: Checkout to Orders
Migration: Checkout to Orders
Guide for migrating from the legacy POST /api/v1/checkout flow to the new POST /api/v3/orders endpoint.
For the full v1 checkout reference, see Checkout on the legacy portal.
Endpoint Comparison
What Changed
One cart, one contact, one payment
Legacy checkout took a wide BookingRequest — a shopper block, billingAddress and deliveryAddress, a delivery method and charge, a payment type, and 3DS redirect fields. v3 narrows it to what the order needs: the cart, the total you expect, a contact, and a payment method.
cartId is the UUID returned when you created the cart.
Contact is one name field
Price is locked to the cart
Legacy trusted whatever the basket held. v3 makes you confirm the price: expectedTotal must equal the cart’s total exactly — same amount, same currency. Read total from GET /api/v3/carts/{cartId} and pass that object straight through. A mismatch returns 400 EXPECTED_TOTAL_MISMATCH — the guard against charging a price the customer didn’t see.
The order settles in the cart’s currency. The POST /orders call can’t change it, so switch currency on the cart first with PATCH /api/v3/carts/{cartId} (see Currency).
Retries are safe
Legacy checkout had no retry protection — a network timeout could double-charge. Send an Idempotency-Key header on POST /api/v3/orders — a UUID v4 you generate per purchase attempt. Retrying with the same key returns the existing order instead of placing a second one.
Payment method
Legacy took a paymentType rail plus 3DS redirectUrl handling. v3 takes a payment object whose method is one of six:
paypal, alipay, and wechatpay aren’t exposed on v3 today.
Don’t hardcode the method. Which methods a retailer can use is set in its configuration, and the cart response lists the ones available for that cart — read them from there and offer only those. INVOICE carries no card details. CARD, APPLE_PAY, and GOOGLE_PAY carry a payment token from the gateway (Adyen today); for the exact payment sub-object each method expects, see the Place Order endpoint in the API Reference. There’s no redirectUrl to manage.
KLARNA is the one exception to reading methods from the cart. options.paymentMethods does not list it yet, so a cart response alone will never tell you Klarna is available. Until it does, ask your TodayTix contact whether Klarna is enabled for your retailer and currency, and offer it on that basis. Everything else on this page still applies.
KLARNA takes two calls, because the shopper approves the purchase in Klarna’s own widget:
- Place the order with
method: KLARNA, an emptyklarnaobject, andoptions.threeDSecure.returnUrl. It answers402with codePAYMENT_AUTHENTICATION_REQUIREDanddetails.action— a JSON string. Parse it and hand it to the Klarna SDK to show the widget. - When the shopper approves, place the order again with the same fields plus the two values the SDK returns, in
options.threeDSecure.details:authorization_tokenandpaymentData. Senddetailsonly on this second call — omit it entirely on the first.
If the second call never arrives, the authorization Klarna is holding is released, so an abandoned checkout doesn’t leave the shopper on the hook.
Tickets and barcodes
On success the order carries the tickets. Each item in items[] has a tickets[] array, and each ticket has a barcode with its value, format, and an optional imageUrl. barcode can be null at delayed-delivery venues — handle that explicitly (see Nullability).
Response Shape
New (v3)
Key differences:
idis the order ID — a numeric string, stable and safe to store (see IDs)barcode.formatis a code likeQR,CODE128, orPDF417— switch on it, and treat an unknown value gracefully- Tickets live under
items[].tickets[], each with its ownbarcode receiptLines[]is the record of charges and credits — render it in order, don’t recompute
Migration Checklist
- Replace
POST /api/v1/checkoutwithPOST /api/v3/orders - Send the
cartIdin place of the basketreference - Collapse
shopper.firstName/lastName/titleinto a singlecontact.name - Map
telephoneNumbertocontact.phonein E.164 format - Drop
billingAddress/deliveryAddressand the delivery fields — delivery is priced into the cart - Read the cart’s
totaland pass it through asexpectedTotal, unchanged - Set
payment.methodto one the cart advertises (INVOICEfor pay-on-account); droppaymentTypeand the 3DSredirectUrl.paypal/alipay/wechatpayhave no v3 equivalent - Send an
Idempotency-Keyheader (a UUID v4) so retries are safe - Read tickets from
items[].tickets[].barcode; handle anullbarcode
Next Steps
This is the last step of the Purchase Flow. To handle failures cleanly, see Error Handling — including EXPECTED_TOTAL_MISMATCH, CART_EXPIRED, and which errors are safe to retry.