> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.todaytixgroup.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.todaytixgroup.com/_mcp/server.

# Migration: Checkout to Orders

Guide for moving from the Encore API's `POST /api/v1/checkout` flow to `POST /api/v3/orders` in the TTG Storefront API v3, the newer API. The two APIs number their versions separately.

> For the full v1 checkout reference, see [Checkout](https://developers.todaytixgroup.com/reference/checkout-2) on the legacy portal.

## Endpoint Comparison

| | Legacy (v1) | New (v3) |
|---|---|---|
| **Method** | `POST` | `POST` |
| **Path** | `/api/v1/checkout` | `/api/v3/orders` |
| **Auth** | None — `channelId` in the body only picks the retailer | `X-TT-API-Key`, `X-TT-Retailer` and `X-TT-Platform` headers |
| **Reference** | `reference` (basket) | `cartId` |
| **Price safety** | None | `expectedTotal` must match the cart |
| **Safe retry** | Not built in | `Idempotency-Key` header |
| **Payment** | `paymentType` rail + 3DS `redirectUrl` | `payment.method` from the set the cart advertises; a card challenge or a Klarna or Afterpay approval comes back as `402` |

## 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.

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

`cartId` is the UUID returned when you created the cart.

### Headers

Legacy checkout needed no key. v3 needs the three headers in [Authentication & Headers](/guides/getting-started#authentication--headers) on every call.

### Contact is one name field

| Legacy (v1) | New (v3) | Notes |
|---|---|---|
| `shopper.firstName` + `lastName` + `title` | `contact.name` | One field — send the full name |
| `shopper.email` | `contact.email` | Same |
| `shopper.telephoneNumber` | `contact.phone` | E.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` |

To choose whether TTG emails the customer a confirmation, send `sendConfirmationEmail`; see [Checkout](/guides/purchase-flow#6-checkout).

### Price is locked to the cart

Legacy trusted whatever the basket held. v3 makes you confirm the price, so the customer is never charged a price they didn't see: read `total` from `GET /api/v3/carts/{cartId}` and send it unchanged as `expectedTotal`. A different amount returns `400 EXPECTED_TOTAL_MISMATCH`; a different currency, `400 INVALID_FIELD_VALUE` (see [Currency](/guides/api-conventions#currency)).

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](/guides/api-conventions#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 make for each purchase attempt, so a retry after a timeout never buys twice. See [Idempotency Keys](/guides/error-handling#idempotency-keys).

### Payment method

Legacy took a `paymentType` rail plus 3DS `redirectUrl` handling. v3 takes a `payment` object whose `method` is one of seven:

| `method` | What it is | Legacy `paymentType` |
|---|---|---|
| `INVOICE` | Settle on account, billed later — the usual path for B2B and reseller partners. Offered only to your secret key, on a retailer set up for it; see [Paying](/guides/purchase-flow#paying) | `account` |
| `CARD` | A card, freshly entered or saved on file | `card`, `amex` |
| `APPLE_PAY` | Apple Pay wallet token | — |
| `GOOGLE_PAY` | Google Pay wallet token | — |
| `KLARNA` | Buy now, pay later — the shopper approves the purchase in Klarna's widget | — |
| `AFTERPAY` | Buy now, pay later — the shopper approves the purchase on Afterpay's own page (Clearpay in the UK, Cash App Afterpay in the US) | — |
| `NONE` | Nothing to pay — the cart `total` is zero | — |

`paypal`, `alipay`, and `wechatpay` aren't exposed on v3 today.

**Don't hardcode the method.** Offer only the methods the cart lists. The rules for each, including when to send `NONE`, are in [Paying](/guides/purchase-flow#paying). `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 an order endpoint in the API Reference. A card can still need the shopper to pass their bank's check (3D Secure): the order then answers `402 PAYMENT_AUTHENTICATION_REQUIRED` with the challenge in `details.action`, and you place it again with the result under a new `Idempotency-Key` (see [Error Handling](/guides/error-handling#retry-strategy)).

**Send the billing postal code as `payment.postalCode`, whatever the method.** It is optional, but it is the one address field the order keeps, so send it whenever you have collected one — with a wallet, `KLARNA` or `AFTERPAY` payment as much as with a card. `payment.card.postalCode` is still accepted on a `CARD` payment; when both are sent, `payment.postalCode` is used.

**`KLARNA` and `AFTERPAY` appear only on a cart read with `country`, and take two calls**, because the shopper approves the payment with Klarna or Afterpay. See [Klarna and Afterpay](/guides/purchase-flow#klarna-and-afterpay).

### 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`. A `barcode` can be `null` for a while; see [Tickets and barcodes](/guides/purchase-flow#tickets-and-barcodes).

## Response Shape

### New (v3)

```json
{
  "data": {
    "id": "1000123",
    "confirmationNumber": "T123456789012",
    "status": "COMPLETED",
    "total": { "amount": 18500, "currency": "USD" },
    "contact": { "name": "John Smith", "email": "john.smith@example.com" },
    "items": [
      {
        "tickets": [
          { "barcode": { "value": "123456789", "format": "QR", "imageUrl": null } }
        ]
      }
    ],
    "receiptLines": [ ... ]
  }
}
```

Key differences:
- `id` is the order ID — stable and safe to store (see [IDs](/guides/api-conventions#ids))
- `confirmationNumber` is the reference the customer quotes to support
- `status` says whether the payment went through; `PROCESSING` means it is still settling (see [After Checkout](/guides/purchase-flow#find-out-whether-a-checkout-went-through))
- `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`, and send `X-TT-API-Key`, `X-TT-Retailer` and `X-TT-Platform`; drop `channelId`
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; keep the billing postal code, as `payment.postalCode`
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](/guides/purchase-flow). To handle failures cleanly, see [Error Handling](/guides/error-handling) — including `EXPECTED_TOTAL_MISMATCH`, `CART_EXPIRED`, and which errors are safe to retry. Then read [After Checkout](/guides/purchase-flow#7-after-checkout) to keep the cart ID, look the order up, and pick up barcodes.