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

Legacy (v1)New (v3)
MethodPOSTPOST
Path/api/v1/checkout/api/v3/orders
AuthchannelIdX-TT-API-Key header
Referencereference (basket)cartId
Price safetyNoneexpectedTotal must match the cart
Safe retryNot built inIdempotency-Key header
PaymentpaymentType rail + 3DS redirectUrlpayment.method from the set the cart advertises; no 3DS redirect

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.

1{
2 "cartId": "6aac14d3-5826-4da8-98b6-9e68c28629d6",
3 "expectedTotal": { "amount": 18500, "currency": "USD" },
4 "contact": {
5 "name": "John Smith",
6 "email": "john.smith@example.com",
7 "phone": "+12125550123"
8 },
9 "payment": { "method": "INVOICE" }
10}

cartId is the UUID returned when you created the cart.

Contact is one name field

Legacy (v1)New (v3)Notes
shopper.firstName + lastName + titlecontact.nameOne field — send the full name
shopper.emailcontact.emailSame
shopper.telephoneNumbercontact.phoneE.164 format (+12125550123)
billingAddress / deliveryAddress(not sent)v3 doesn’t collect addresses at order time
deliveryMethod / deliveryCharge(on the cart)Delivery is priced into the cart’s total

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:

methodWhat it isLegacy paymentType
INVOICESettle on account, billed later — the usual path for B2B and reseller partnersaccount
CARDA card, freshly entered or saved on filecard, amex
APPLE_PAYApple Pay wallet token
GOOGLE_PAYGoogle Pay wallet token
KLARNABuy now, pay later — the shopper approves the purchase in Klarna’s widgetklarna
NONENothing to pay — the cart total is zero (free items, or a voucher covers it all)

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:

  1. Place the order with method: KLARNA, an empty klarna object, and options.threeDSecure.returnUrl. It answers 402 with code PAYMENT_AUTHENTICATION_REQUIRED and details.action — a JSON string. Parse it and hand it to the Klarna SDK to show the widget.
  2. When the shopper approves, place the order again with the same fields plus the two values the SDK returns, in options.threeDSecure.details: authorization_token and paymentData. Send details only 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)

1{
2 "data": {
3 "id": "789",
4 "total": { "amount": 18500, "currency": "USD" },
5 "contact": { "name": "John Smith", "email": "john.smith@example.com" },
6 "items": [
7 {
8 "tickets": [
9 { "barcode": { "value": "123456789", "format": "QR", "imageUrl": null } }
10 ]
11 }
12 ],
13 "receiptLines": [ ... ]
14 }
15}

Key differences:

  • id is the order ID — a numeric string, stable and safe to store (see IDs)
  • barcode.format is a code like QR, CODE128, or PDF417 — switch on it, and treat an unknown value gracefully
  • Tickets live under items[].tickets[], each with its own barcode
  • receiptLines[] is the record of charges and credits — render it in order, don’t recompute

Migration Checklist

  1. Replace POST /api/v1/checkout with POST /api/v3/orders
  2. Send the cartId in place of the basket reference
  3. Collapse shopper.firstName / lastName / title into a single contact.name
  4. Map telephoneNumber to contact.phone in E.164 format
  5. Drop billingAddress / deliveryAddress and the delivery fields — delivery is priced into the cart
  6. Read the cart’s total and pass it through as expectedTotal, unchanged
  7. Set payment.method to one the cart advertises (INVOICE for pay-on-account); drop paymentType and the 3DS redirectUrl. paypal / alipay / wechatpay have no v3 equivalent
  8. Send an Idempotency-Key header (a UUID v4) so retries are safe
  9. Read tickets from items[].tickets[].barcode; handle a null barcode

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.