tf-checkout-react 2.0.0-beta.23 → 2.0.0-beta.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +831 -279
  2. package/docs/API.md +540 -0
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,326 +1,878 @@
1
1
  # tf-checkout-react
2
2
 
3
- The Ticket Fairy checkout for the web. Use the embedded flow or compose its
4
- ticket selection, add-ons, buyer details, payment and confirmation sections.
5
- Available features depend on the event, backend and payment provider.
6
-
7
- The 2.x source is maintained in `tf-shared-libraries/packages/tf-checkout-react`;
8
- the source repository requires access.
9
- The shared core is private and bundled into both platform packages. Use React
10
- 18+, React DOM 18+ and React Query 5 on web.
11
- Never put a confidential OAuth client secret in browser or mobile code. Public
12
- checkout integrations use registered SDK client IDs and owned buyer sessions.
13
-
14
- ## Install
15
-
16
- This README describes `2.0.0-beta.23`, the paired 2.x prerelease. Pin it
17
- explicitly to use this version. `beta` and `latest` are mutable npm tags, not
18
- guarantees about the highest version; an unqualified install can still resolve
19
- a stable 1.x package. Check the current dist-tags when choosing a version.
20
- Publication does not by itself establish backend rollout, device compatibility
21
- or provider/payment acceptance.
3
+ Ticket Fairy's React checkout for the web. Render the complete checkout, compose
4
+ its sections, or build your own UI against the checkout engine. The package also
5
+ exports authentication, confirmation, rewards, ticket-management and account
6
+ components, plus the shared API and React Query hooks. Which paths a buyer can
7
+ actually use depend on the event, the deployed API, the buyer's session and the
8
+ payment provider.
22
9
 
23
- ```bash
24
- npm view tf-checkout-react dist-tags
25
- ```
26
-
27
- This version preserves quantities across rotating encrypted ticket IDs using
28
- the API's optional canonical `selectionKey`, while quantity/change APIs and cart
29
- requests retain the latest encrypted IDs. Deploy and verify the canonical
30
- base/dated key and dated numeric context before relying on that behavior:
31
- `display_price`, `price_with_fees`, `fee_included`, `price_currency` and
32
- `price_symbol`. Missing optional keys retain opaque-ID compatibility without a
33
- rotation-retention claim. The deployed API must provide and validate that data.
34
-
35
- Post-purchase upgrade waiting is a separate server-owned contract. Only valid
36
- change-options targets can expose Join or Leave; malformed target IDs, labels
37
- or existing entries are ignored. A sold-out row does not enable it. The backend
38
- must explicitly offer that organizer-configured flow; local receiver validation
39
- does not prove its deployed availability or a completed join.
10
+ This reference describes **2.0.0-beta.24**. It is a prerelease with a different
11
+ API from 1.x. Pin the version while evaluating it. npm's `latest` and `beta`
12
+ tags can move independently and an unqualified install may select 1.x:
40
13
 
41
14
  ```bash
42
- npm install tf-checkout-react@2.0.0-beta.23 @tanstack/react-query react react-dom
15
+ npm view tf-checkout-react dist-tags
16
+ npm install tf-checkout-react@2.0.0-beta.24 @tanstack/react-query react react-dom
43
17
  ```
44
18
 
45
- `@tanstack/react-query`, `react`, and `react-dom` are peer dependencies. Install
46
- compatible versions in the host and ensure each context provider uses the same
47
- React and React Query instances as the SDK. The private shared core and seat
48
- renderer are bundled; npm installs the package's other declared dependencies.
49
- Payment-provider scripts and account configuration are separate requirements.
50
-
51
- The entry imports its stylesheet, so the host bundler must support CSS imports.
52
- The stylesheet is also exported as `tf-checkout-react/checkout.css` for hosts
53
- that need to import it explicitly.
54
-
55
- ## Use it
19
+ The [API reference](https://unpkg.com/tf-checkout-react@2.0.0-beta.24/docs/API.md)
20
+ is included as `docs/API.md` in this package.
21
+
22
+ Contents: [Requirements](#requirements) · [Quick start](#quick-start) ·
23
+ [Configuration](#configuration-and-sessions) · [Embedded checkout](#embeddedcheckout)
24
+ · [Composition](#compose-sections) · [Checkout lifecycle](#checkout-lifecycle)
25
+ · [Payments](#payments) · [Authentication](#authentication) ·
26
+ [Reference](#component-reference) · [Headless APIs](#headless-apis) ·
27
+ [Styling](#styling-and-localization) · [Migration](#migrating-from-1x) ·
28
+ [Testing and troubleshooting](#testing-and-troubleshooting)
29
+
30
+ ## Requirements
31
+
32
+ - Node.js 18 or newer for tooling; a React browser application and a bundler
33
+ that handles JavaScript and CSS imports.
34
+ - Peer dependencies: `react >=18`, `react-dom >=18` and
35
+ `@tanstack/react-query >=5`. Supply one compatible copy of each in the host.
36
+ Query-based components and hooks need the host's `QueryClientProvider`.
37
+ - A Ticket Fairy event ID and a **public SDK client ID** registered for this
38
+ integration, together with compatible API and provider configuration. Ask
39
+ your Ticket Fairy integration contact for the appropriate environment and
40
+ event setup. Never put a confidential OAuth client secret in browser code.
41
+ - The shared core and seat renderer are bundled; npm installs the package's
42
+ declared runtime dependencies. No separate shared-core install is required.
43
+ Browser and provider support must be tested for the actual event and device.
44
+
45
+ The package entry imports `react-international-phone/style.css` and its own
46
+ checkout stylesheet. Your bundler must process both those imports and CSS
47
+ `@import` rules. The latter is also exported at
48
+ `tf-checkout-react/checkout.css` for build setups that need an explicit import.
49
+ Its `dist/styles` files and referenced assets are part of the npm package.
50
+
51
+ ## Quick start
52
+
53
+ Configure once before mounting the SDK. Create the query client once rather
54
+ than on every render. Replace the placeholders with an event and a registered
55
+ public client ID in the environment you are using.
56
56
 
57
57
  ```tsx
58
+ import { useState } from 'react'
58
59
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
59
60
  import { configureCheckout, EmbeddedCheckout } from 'tf-checkout-react'
60
61
 
61
- configureCheckout({ env: 'PROD', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret: '', cookieAuth: true })
62
+ configureCheckout({
63
+ env: 'PROD',
64
+ clientId: 'PUBLIC_SDK_CLIENT_ID',
65
+ clientSecret: '',
66
+ cookieAuth: true,
67
+ })
62
68
 
63
69
  const queryClient = new QueryClient()
64
70
 
65
- export const App = () => (
66
- <QueryClientProvider client={queryClient}>
67
- <EmbeddedCheckout eventId="14304" onComplete={(order) => console.log(order)} />
68
- </QueryClientProvider>
69
- )
70
- ```
71
-
72
- `configureCheckout` runs once at app start and
73
- defaults token storage to `localStorage` (SSR-safe — it no-ops on the server).
74
-
75
- ### Environments
76
-
77
- ```ts
78
- configureCheckout({ env: 'STAG', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret: '', cookieAuth: true })
71
+ export function App() {
72
+ const [notice, setNotice] = useState('')
73
+ return (
74
+ <QueryClientProvider client={queryClient}>
75
+ <EmbeddedCheckout
76
+ eventId="EVENT_ID"
77
+ onComplete={(order) => {
78
+ setNotice(order.pending ? 'Payment pending' : 'Order submitted')
79
+ }}
80
+ />
81
+ <p role="status">{notice}</p>
82
+ </QueryClientProvider>
83
+ )
84
+ }
79
85
  ```
80
86
 
81
- `PROD` → `www.ticketfairy.com`, `STAG` → `test.ticketfairy.com`, `DEV` →
82
- `sandbox.ticketfairy.com`. Pass `baseUrl` instead to point somewhere else;
83
- `baseUrl: ''` means same-origin, which is what you want behind a dev proxy.
84
-
85
- ## `<EmbeddedCheckout />`
86
-
87
- | Prop | Type | Default | Does |
88
- | ----------------------------------- | ----------------------------------- | ----------- | ----------------------------------------------------------------------- |
89
- | `eventId` | `string` | — | The event to sell. Required. |
90
- | `onComplete` | `(order: PlaceOrderResult) => void` | — | Receives the order result, including a possible pending payment. |
91
- | `confirmationMode` | `'inline' \| 'redirect'` | `'inline'` | `redirect` leaves routing to `onComplete`. |
92
- | `initialView` | `'tickets' \| 'seats'` | `'tickets'` | Opening screen — preserves an existing `/seat-map` route. |
93
- | `onViewChange` | `(view) => void` | — | Reports the visible view so hosts can coordinate UI. |
94
- | `autoScroll` | `boolean` | `true` | Smooth-scroll to buyer details once the cart is reserved. |
95
- | `requireEmailConfirmation` | `boolean` | `true` | Make guests repeat their email. |
96
- | `collectTicketHolders` | `boolean` | `true` | One named holder per ticket. |
97
- | `collectHolderContact` | `boolean` | `true` | Also collect holder email/phone. |
98
- | `skipBillingWhenProfileComplete` | `boolean` | `false` | Skip billing for complete signed-in profiles. Payment is never skipped. |
99
- | `auth` | `false \| EmbeddedCheckoutAuthOptions` | built-in | Configure or remove the login modal. |
100
- | `onLogin` | `(email?: string) => void` | — | Open your own login UI instead of the built-in modal. |
101
- | `privacyPolicy` | `CheckoutPrivacyPolicy` | — | Your privacy policy link and brand name. |
102
- | `additionalConditions` | `EventCondition[]` | — | Extra required/optional acknowledgements. |
103
- | `ttfOptInLabel` / `brandOptInLabel` | `string` | — | Override the marketing opt-in copy. |
104
- | `initialPromoCode` | `string` | — | Apply a promo code on load. |
105
- | `referrer` | `string \| number` | — | Attribute the sale. |
106
- | `readAttributionFromUrl` | `boolean` | `true` | Read supported promo/referrer parameters from the query string. |
107
- | `showTestCardHint` | `boolean` | `false` | Show sandbox card copy. Demos only. |
108
- | `theme` | `CheckoutTheme` | — | Recolor via design tokens. |
109
- | `className` | `string` | — | Class on the root element. |
110
-
111
- ### Resuming a checkout
112
-
113
- `<ResumedCheckout />` attaches to a cart created elsewhere — an accepted resale
114
- invitation, or a session restored after a redirect.
115
-
116
- ### Link attribution and conditional fields
117
-
118
- The web ticket picker reads `promo` or `promo_code`, and `ttf_r` or
119
- `referrer_id`, unless URL attribution is disabled. Explicit props take
120
- precedence. It does not classify an ambiguous numeric `?r=` link. A numeric
121
- value can represent a promotion or referral; do not pass it as `referrer`
122
- without authoritative classification. Native hosts must supply explicit
123
- attribution themselves.
124
-
125
- This beta leaves conditional custom-field requiredness to the backend and does
126
- not reproduce all hosted-checkout conditional visibility. Verify the event's
127
- field behavior against the deployed checkout before relying on parity.
128
-
129
- ## Theming
130
-
131
- Pass design tokens; the component maps them to CSS variables.
87
+ Use a test event and the corresponding test client ID with `env: 'STAG'` for
88
+ integration testing. `onComplete` receives a result that may be pending; do not
89
+ fulfil an order merely because this callback fired. See
90
+ [Payments](#payments) for reconciliation and retry guidance.
91
+
92
+ The inline confirmation also offers a generic share link using the current
93
+ page URL without checking referral eligibility. That URL can contain query
94
+ parameters or lack the buyer's referral. Review the
95
+ [confirmation limitations](#checkout-sections) before using the default flow.
96
+
97
+ ## Configuration and sessions
98
+
99
+ `configureCheckout(config: TtfConfig)` wraps the shared `configure` function,
100
+ adds a browser `localStorage` adapter and a browser checkout-auth popup, and then
101
+ applies your supplied options. Call it once at client startup, before any SDK
102
+ hook or `ttf` call. Calling it again replaces the global configuration and can
103
+ retire existing session state. Do not reconfigure from a component render.
104
+
105
+ | Option | Meaning |
106
+ | --- | --- |
107
+ | `clientId`, `clientSecret` | Required strings. For a public browser integration use its registered SDK client ID and an empty `clientSecret`; a confidential secret belongs on a trusted server only. |
108
+ | `env` | `PROD` (default), `STAG`, or `DEV`, selecting `https://www.ticketfairy.com`, `https://test.ticketfairy.com`, or `https://sandbox.ticketfairy.com`. |
109
+ | `baseUrl` | Explicit host overrides `env`, including `''` for same origin behind a proxy. A trailing `/api` is normalized; the SDK appends `/api` for API calls. |
110
+ | `sourceOrigin` | Optional value of the `X-Source-Origin` request header when required for your integration. |
111
+ | `storage` | Synchronous `StorageAdapter` overriding the browser default. `getItem(key)`, `setItem(key, value)`, and `removeItem(key)` are required; optional `flush()` returns `Promise<void>` for a synchronous mirror with asynchronous persistence. |
112
+ | `cookieAuth` | Enables the header-backed Ticket Fairy session path. Use when the integration is set up for a shared session or anonymous cookie cart; browser cookie and CORS policies still apply. |
113
+ | `withCredentials` | Controls automatic HTTP cookie handling; defaults to true in browsers. Cross-origin cookies also require correct server CORS/cookie configuration and browser permission. |
114
+ | `auth` | Function returning additional headers or `undefined`; called for each request. Headers normally merge after SDK headers, but protected requests enforce session isolation: guest bootstrap strips sensitive buyer/session headers after that merge. Use only credentials appropriate to the browser. |
115
+ | `accountAuth` | Opts into first-party account auth routes; default is off. Requires the corresponding backend journey and account authority. |
116
+ | `sdkCheckoutPhoneAuth` | Opts into the first-party phone checkout journey only when its matching backend is deployed. |
117
+ | `browserAuth`, `sdkCheckoutBrowserAuth` | Browser authorization ports for specialized integrations. The web wrapper supplies the checkout popup port when supported; an explicit config value overrides it. |
118
+
119
+ `localStorageAdapter` no-ops outside a browser; `createMemoryStorage()` is an
120
+ exported non-persistent adapter useful for tests. Browser local storage is
121
+ accessible to scripts in the origin. Protect against XSS, avoid logging tokens,
122
+ order links or personal information, and pick an adapter and session lifetime
123
+ appropriate to your application. Logout/session changes need to be coordinated
124
+ with the host: stale UI or a cached profile is not proof of authorization.
125
+
126
+ The public checkout sign-in path establishes an event-scoped buyer identity. It
127
+ does **not** grant a full account session for private tickets or account
128
+ preferences. A full account experience needs the corresponding authorized
129
+ session and backend routes. Two-factor and other proof steps must finish before
130
+ an authentication flow is complete. Do not treat a checkout profile callback as
131
+ permission to read private account data.
132
+
133
+ ### Server rendering
134
+
135
+ The default storage adapter is server-safe, and the checkout hook supplies an
136
+ idle server snapshot. Payment elements, popups, DOM effects and browser storage
137
+ need a browser. In SSR frameworks, mount the interactive checkout in a client
138
+ component; do not run a browser checkout on the server or share a configured
139
+ module/session singleton across server requests. Configure the browser before
140
+ rendering that client tree. Confirm that the framework processes the package's
141
+ CSS imports. Static import/SSR compatibility of a particular framework and all
142
+ third-party dependencies should be tested in that framework.
143
+
144
+ ## EmbeddedCheckout
145
+
146
+ `EmbeddedCheckout` owns a `CheckoutProvider` and presents ticket/bundle or
147
+ seat selection, add-ons, billing, waivers, payment and inline completion as the
148
+ flow requires. It must be under `QueryClientProvider`.
149
+
150
+ Only `eventId` is required. Callbacks and options are optional.
151
+
152
+ | Prop | Type / default | Description |
153
+ | --- | --- | --- |
154
+ | `eventId` | `string` | Event to sell. |
155
+ | `onComplete` | `(order: PlaceOrderResult) => void` | Result of placement, including an asynchronously pending payment. |
156
+ | `confirmationMode` | `'inline' \| 'redirect'`; `inline` | Inline renders verification/confirmation. Redirect shows a transition; use `onComplete` to navigate. |
157
+ | `initialView` | `'tickets' \| 'seats'`; `tickets` | Initial selection view. |
158
+ | `onViewChange` | `(view: 'tickets' \| 'seats' \| 'checkout') => void` | Reports the visible flow view. |
159
+ | `autoScroll` | `boolean`; `true` | Scroll to buyer details after reservation. |
160
+ | `requireEmailConfirmation` | `boolean`; `true` | Require guests to repeat their email. |
161
+ | `collectTicketHolders` | `boolean`; `true` | Collect one named holder per ticket. |
162
+ | `collectHolderContact` | `boolean`; `true` | Collect holder contact details when holders are collected. |
163
+ | `skipBillingWhenProfileComplete` | `boolean`; `false` | Skip billing only for an eligible complete signed-in profile and compatible holder/custom-field requirements. Payment is not skipped. See the privacy and opt-in limitations below. |
164
+ | `onLogin` | `(email?: string) => void` | Opens host login instead of the built-in modal; the email may be an existing-account match. |
165
+ | `auth` | `false \| EmbeddedCheckoutAuthOptions` | Disable or configure the built-in modal; ignored when `onLogin` is supplied. |
166
+ | `privacyPolicy` | `CheckoutPrivacyPolicy` | `{ url, brandName, linkLabel? }`; adds the host's required privacy acknowledgement. |
167
+ | `additionalConditions` | `EventCondition[]` | Host acknowledgements `{ id, name, content, isRequired }`; required ones gate payment. Content is rendered as HTML: supply trusted or sanitized HTML. |
168
+ | `ttfOptInLabel`, `brandOptInLabel` | `string` | Override the Ticket Fairy opt-in label or add a brand opt-in. The privacy-policy option changes the Ticket Fairy control as described below. |
169
+ | `initialPromoCode`, `referrer`, `readAttributionFromUrl` | `string`, `string \| number`, `boolean` | Apply a code and attribution; URL reading defaults to `true`. |
170
+ | `showTestCardHint` | `boolean`; `false` | Demo-only sandbox card copy. |
171
+ | `theme`, `locale`, `messages`, `className` | `CheckoutTheme`, `LocaleCode`, `Partial<Messages>`, `string` | Appearance, copy and root class. |
172
+
173
+ `EmbeddedCheckoutAuthOptions` accepts `allowRegistration`,
174
+ `allowPasswordReset`, `requireRegistrationAddress`, `logoUrl`,
175
+ `onCheckoutAuthenticated(profile: CheckoutBuyerProfile)` and
176
+ `onAuthenticated(profile: TfProfile)`. The two callbacks represent different
177
+ session authorities; use the one appropriate to your configured journey.
178
+ `requireRegistrationAddress={false}` does not remove the address requirement
179
+ for the public checkout registration or account-completion journey; those
180
+ paths force it on in this version.
181
+
182
+ ### Resume a server cart
183
+
184
+ `ResumedCheckout` calls `resumeCart` for an existing cart in the current
185
+ session. It does not create that cart or accept a cart ID as a prop. A resale
186
+ acceptance flow, for example, must create the cart first. The component shows
187
+ billing, waivers and payment and requires the same query provider and
188
+ configuration as the embedded flow.
132
189
 
133
190
  ```tsx
134
- <EmbeddedCheckout
135
- eventId="14304"
136
- theme={{
137
- colorScheme: 'dark',
138
- accent: '#ff2d55',
139
- radiusCard: '16px',
140
- }}
141
- />
191
+ import { useState } from 'react'
192
+ import { ResumedCheckout } from 'tf-checkout-react'
193
+
194
+ export function ResumePage() {
195
+ const [notice, setNotice] = useState('')
196
+ return (
197
+ <>
198
+ <ResumedCheckout
199
+ eventId="EVENT_ID"
200
+ isFromResale
201
+ confirmationMode="inline"
202
+ onComplete={(order) => setNotice(order.pending ? 'Payment pending' : 'Order submitted')}
203
+ />
204
+ <p role="status">{notice}</p>
205
+ </>
206
+ )
207
+ }
142
208
  ```
143
209
 
144
- Tokens cover color (`accent`, `accentHover`, `accentContrast`, `surface`,
145
- `surfaceMuted`, `text`, `muted`, `border`, `error`, `success`), shape
146
- (`radiusCard`, `radiusControl`), and typography.
147
-
148
- Checkout section styles use `.tf-checkout` and `tf-checkout-*` classes. Review
149
- the bundled third-party phone and seat-map styles and standalone component
150
- styles when integrating them into an existing page.
210
+ Props are `eventId` (required), `isFromResale` (default `false`),
211
+ `onComplete`, `confirmationMode` (default **`redirect`**),
212
+ `requireEmailConfirmation`, `collectTicketHolders`, `collectHolderContact`
213
+ (each default `true`), `privacyPolicy`, `brandOptInLabel`,
214
+ `additionalConditions`, `theme` and `className`. With the default redirect
215
+ mode, route in `onComplete`; otherwise a transition remains visible. Unlike
216
+ `EmbeddedCheckout`, this component does not accept `locale`, `messages`,
217
+ `auth` or `onLogin` props in this version.
151
218
 
152
- ## Composing your own layout
219
+ ## Compose sections
153
220
 
154
- Every section reads the same `useCheckout()` store from the provider, so you can
155
- arrange them however you like. Mount this under a `QueryClientProvider` as in
156
- the quickstart.
221
+ For a different layout, render sections beneath `CheckoutProvider` and the
222
+ host's `QueryClientProvider`. The provider creates a web-wired `useCheckout`
223
+ instance unless `value` is supplied. Sections share its context. The app-wide
224
+ checkout store is shared by default, so separate `useCheckout()` calls are not
225
+ independent carts. Avoid mounting competing checkout flows for different events
226
+ at once; coordinate completion/reset and session transitions in the host.
157
227
 
158
228
  ```tsx
229
+ import { useState } from 'react'
159
230
  import {
160
- CheckoutProvider,
161
- Tickets,
162
- AddOns,
163
- BillingInfo,
164
- Payment,
165
- OrderSummary,
231
+ AddOns, BillingInfo, CheckoutProvider, OrderSummary, Payment,
232
+ Tickets, WaiverSigning,
166
233
  } from 'tf-checkout-react'
167
234
 
168
- const MyCheckout = () => (
169
- <CheckoutProvider theme={{ colorScheme: 'dark' }}>
170
- <Tickets eventId="14304" />
171
- <AddOns eventId="14304" />
172
- <div className="my-two-column">
173
- <BillingInfo />
174
- <OrderSummary />
175
- </div>
176
- <Payment />
177
- </CheckoutProvider>
178
- )
179
- ```
180
-
181
- **Sections** — `Tickets`, `SeatMap`, `AddOns`, `BillingInfo`, `Payment`,
182
- `OrderSummary`, `IDVerification`, `Confirmation`, `OrderConfirmation`,
183
- `OrderIDVerification`, `Rewards`.
184
-
185
- **UI primitives** — `Card`, `SectionHeader`, `Pill`, `Button`, `Field`,
186
- `FieldRow`, `Input`, `Textarea`, `Select`, `Checkbox`, `PaymentMethods`,
187
- `QuantitySelect`, `PriceLine`, `Stepper`, `Timer`, `TimerBanner`, `Loader`.
188
-
189
- **Auth** — `AuthModal`, `LoginForm`, `RegisterForm`, `ForgotPasswordForm`,
190
- `ResetPasswordForm`.
191
-
192
- ## Rewards
193
-
194
- After a buyer pays, the Rewards section shows where they stand on the event's
195
- leaderboard, who their link has brought in, what they can still win, their pack,
196
- and the card they share.
197
-
198
- `<OrderConfirmation/>` renders it for you whenever the confirmation response
199
- carries a `rewards` object. The host supplies `hash` in these examples:
200
-
201
- ```tsx
202
- <OrderConfirmation orderHash={hash} /> // rewards included
203
- <OrderConfirmation orderHash={hash} showRewards={false} />
204
- ```
205
-
206
- Or render it anywhere on its own:
207
-
208
- ```tsx
209
- import { Rewards } from 'tf-checkout-react'
210
- ;<Rewards orderHash={hash} />
235
+ export function ComposedCheckout() {
236
+ const eventId = 'EVENT_ID'
237
+ const [notice, setNotice] = useState('')
238
+ return (
239
+ <CheckoutProvider theme={{ colorScheme: 'light', accent: '#2458d3' }}>
240
+ <Tickets eventId={eventId} />
241
+ <AddOns eventId={eventId} />
242
+ <BillingInfo eventId={eventId} requireEmailConfirmation />
243
+ <WaiverSigning eventId={eventId} />
244
+ <OrderSummary eventId={eventId} />
245
+ <Payment
246
+ eventId={eventId}
247
+ requireEmailConfirmation
248
+ onComplete={(order) => setNotice(order.pending ? 'Payment pending' : 'Order submitted')}
249
+ />
250
+ <p role="status">{notice}</p>
251
+ </CheckoutProvider>
252
+ )
253
+ }
211
254
  ```
212
255
 
213
- It fetches through `ttf` rather than react-query, so it works on a confirmation
214
- page with no `<QueryClientProvider>`. If your app already has one, fetch with
215
- `hooks.useOrderRewards(hash)` and pass the query's `data` payload to `Rewards`.
216
-
217
- **Sharing.** The buttons are the channels the organiser enabled, filtered to
218
- what this browser can do. On a phone the OS share sheet is attempted and may
219
- include the share card as a file. Other options use link intents or copy; the
220
- browser or user can deny an individual share or clipboard operation.
221
-
222
- **Snapchat is opt-in.** It loads a third-party script (`sdk.snapkit.com`) that
223
- your page's content policy has to allow, so it renders only when you ask:
256
+ This is a basic general-admission layout. Use section callbacks to reveal and
257
+ navigate between steps as needed. For seated events wire `Tickets.onSelectSeats`
258
+ to a `SeatMap` view; without the callback its seat-selection CTA is hidden.
259
+ Keep `BillingInfo`'s email/holder rules aligned with `Payment`'s corresponding
260
+ rules. If using a host privacy policy, pass the policy to `BillingInfo`, pass
261
+ `requirePrivacyPolicy` to `Payment`, and use `onPrivacyRequired` to reveal the
262
+ billing error. Render the event's required conditions and waiver, not just a
263
+ card input. Read the consent limitations below before using `privacyPolicy` or
264
+ the billing-skip option. Do not skip add-ons when one is required.
265
+
266
+ `CheckoutProvider` props: `children` (required), `value?: UseCheckout`,
267
+ `theme?: CheckoutTheme`, `locale?: LocaleCode`,
268
+ `messages?: Partial<Messages>`, and `className?: string`. Use
269
+ `useCheckoutContext()` inside the provider. `useCheckoutValidation()` exposes
270
+ `{ attempt, requestValidation }` for coordinating validation between sections;
271
+ both context hooks require a provider. The provider's default checkout includes
272
+ the installment picker. If supplying your own `value`, wire any custom payment
273
+ ports you need when creating that checkout.
274
+
275
+ ## Checkout lifecycle
276
+
277
+ 1. Load the current event catalog and reserve current selections with
278
+ `setTickets(eventId, selections)`, or reserve seats and call `setSeats` /
279
+ `setSeatsIo`. Alternatively `resumeCart` attaches to a cart already owned by
280
+ this browser session. The server owns inventory, expiry and final prices.
281
+ 2. Read `customFields`, `buyerFields`, conditions, required add-ons, waiver,
282
+ pricing and payment availability from the primed snapshot. Collect the
283
+ required buyer/holder answers and consent. Save them through the corresponding
284
+ setters. Reprice after add-on or currency changes.
285
+ 3. Render only the methods offered for this event and compatible with the host.
286
+ Validate required information, then `await placeOrder(method)` once per buyer
287
+ action. The engine checks preconditions and coordinates concurrent placement.
288
+ 4. Handle the returned `PlaceOrderResult`, including `pending`, and any ID
289
+ verification. A result, a redirect, or a closed popup alone does not prove
290
+ that money settled. Reconcile authoritative order and provider status before
291
+ fulfilling or starting a new payment when an outcome is uncertain.
292
+
293
+ `status` can be `idle`, `priming`, `ready`, `repricing`, `placing`, `succeeded`,
294
+ `expired` or `error`. `isBusy` includes cart/order operations and an outstanding
295
+ waiver save. `secondsLeft` is an optional, whole-second display countdown derived
296
+ from `expiresAt`. When it reaches zero the hook expires the cart unless an order
297
+ is being placed or has succeeded. An expired reservation requires the buyer to
298
+ start again; a displayed countdown does not extend a server reservation.
299
+
300
+ `reset()` starts a reset and returns `void`; `await resetWhenSettled()` returns
301
+ `true` only when reset completed. While a payment is in flight, reset waits for
302
+ it. It returns `false` if an order must be retained because money may have moved,
303
+ or the checkout is retired. A `false` result does not identify which reason
304
+ applies. Show neutral guidance and check the current session and authoritative
305
+ order/provider status before choosing recovery. `PAID_ORDER_PENDING_MESSAGE`
306
+ contains an instruction to press Pay again; do not show it for every `false`
307
+ result or use it as proof that a retry is safe. Do not assume a timeout, network
308
+ error or closed provider page means the charge failed. A custom provider page must
309
+ settle with `{ event: 'dismiss' }` on close/unmount, and other custom payment
310
+ ports must settle, or a waiting reset cannot finish.
311
+
312
+ ### Tickets, codes, seats and waiting lists
313
+
314
+ `Tickets` reads the catalog, presents the event's promo/access-code controls and
315
+ waiting-list state, and reserves selected ticket lines. `initialPromoCode`
316
+ applies once on mount. The web picker reads `promo` / `promo_code` and `ttf_r` /
317
+ `referrer_id` from the URL by default; explicit props take precedence. Set
318
+ `readAttributionFromUrl={false}` to disable this. An ambiguous numeric `?r=`
319
+ can mean a promotion or referral and is not classified by this SDK. Pass a
320
+ referrer only when its meaning is known.
321
+
322
+ For headless catalogs, `hooks.useTicketsWithMeta(eventId)` exposes `tickets`,
323
+ `promo`, `waitingList` and `bundles`. After `applyPromoCode`, refetch the
324
+ catalog to display changed prices or unlocked tickets. Private-event access is
325
+ a separate server operation, `ttf.events.authenticate(eventId, password)`;
326
+ refetch after success. Do not treat a code or an old catalog as proof inventory
327
+ is reserved. The versioned waiting-list definition controls eligibility and
328
+ choices; `waitingList.state` can be `open`, `closed`, `hidden` or `unsupported`.
329
+ Use an authoritative offered choice for `joinWaitingList`, or general interest
330
+ where allowed. Unsupported/custom forms may provide an event continuation URL.
331
+
332
+ Ticket quantities can survive rotating encrypted IDs **when the API supplies a
333
+ stable canonical `selectionKey`**. Cart submissions still use the latest
334
+ opaque encrypted IDs. For dated tickets, deployed canonical keys and numeric
335
+ price context (`display_price`, `price_with_fees`, `fee_included`,
336
+ `price_currency`, `price_symbol`) must be provided and validated by the API.
337
+ When the optional key is absent, do not assume retention across ID rotation.
338
+
339
+ `SeatMap` uses the event's configured seat map and server reservation. Let the
340
+ buyer select seats before continuing; availability can change. Test the actual
341
+ map, chart and reservation expiry on the target domain and browser. For a
342
+ headless integration, `SeatLine` uses the server seat reservation key, tier,
343
+ ticket type, option and price; do not manufacture those values from display
344
+ labels. `setSeatsIo` is a separate path for a seats.io selection.
345
+
346
+ ### Buyer fields, consent and waivers
347
+
348
+ The server's checkout configuration determines required buyer fields, ticket
349
+ holders, custom captures, add-ons and waiver. `buyerFields` identifies gated
350
+ nationality, gender and Instagram inputs; `customFields` contains order,
351
+ ticket and add-on groups. Store order answers in `billing.dataCapture`, each
352
+ holder's answers in `ticketDataCapture`, and add-on answers via
353
+ `setAddonDataCapture`. Holder ticket type IDs come from the priced cart, not
354
+ the opaque catalog ID. Use backend country/state IDs for `countryId` and
355
+ `stateId`; `countryCode` / `stateCode` are payment billing codes.
356
+
357
+ In beta.24 conditional custom-field requiredness is left to the backend and
358
+ not all hosted-checkout conditional visibility is reproduced. Verify the
359
+ actual event against the deployed checkout, including blanks, images, holder
360
+ scoping and validation errors; do not claim parity from the presence of a
361
+ field alone. Terms and `additionalConditions` are client-side acceptance gates;
362
+ respect the server's own requirements. `Payment` renders condition `content`,
363
+ or `name` as a fallback, as HTML. Only supply trusted or appropriately sanitized
364
+ content, and verify the source and handling of server-provided conditions too.
365
+ Failed waiver reads other than a 404 remain unknown and block placement; use
366
+ `reloadCheckoutDetails()` to retry rather than silently skipping it.
367
+ `WaiverSigning` handles the SDK's signing flow.
368
+
369
+ **Consent limitation in beta.24:** when `privacyPolicy` is supplied, the
370
+ stock `BillingInfo` replaces the Ticket Fairy marketing checkbox with the
371
+ required privacy checkbox, but both use `consent.ttfOptIn`; the order sends
372
+ that value as `ttf_opt_in`. The privacy acknowledgement and Ticket Fairy
373
+ marketing choice are therefore not independently captured by this stock path.
374
+ `brandOptIn` is a separate value. If your integration requires independent
375
+ privacy and marketing choices, do not rely on the stock `privacyPolicy` prop
376
+ for that requirement; use an alternative whose UI, payload and backend behavior
377
+ you have verified.
378
+
379
+ Do not combine stock required `privacyPolicy` with
380
+ `skipBillingWhenProfileComplete` / `skipWhenProfileComplete`. When skipping is
381
+ eligible, the billing card and its consent controls disappear while `Payment`
382
+ can still require the privacy value. The same skip can hide marketing opt-ins;
383
+ do not assume they remain visible. Keep the billing card available and test
384
+ both signed-in and guest flows.
385
+
386
+ ## Payments
387
+
388
+ `Payment` selects available methods from event/backend configuration and uses
389
+ the web platform's Stripe, popup and Xendit ports. Supported request shapes in
390
+ the engine include card, Apple Pay, Google Pay, Stripe redirect methods, OXXO,
391
+ Xendit card/e-wallets, PayPal, Razorpay, Adyen and payment plans. A type existing
392
+ in the SDK does **not** mean it is enabled for your event, browser, country,
393
+ currency, merchant account or deployed backend. Offer the server-returned
394
+ methods, handle an unavailable or unsupported method, and test each enabled
395
+ rail with its provider. Wallets require their own provider and domain/device
396
+ configuration. Redirect/popup methods require allowed popups, return URLs,
397
+ content security policy and third-party access. Provider configuration and
398
+ scripts are separate from installing the npm package.
399
+
400
+ `PlaceOrderResult` contains `orderHash`, `free`, `needIdVerification`, and
401
+ optional `currency`, `paymentIntentId`, `alreadyPaid`, `pending` and
402
+ `pendingVoucherUrl`. `alreadyPaid` means the server reported a duplicate as
403
+ already paid; it does not mean this invocation charged again. OXXO vouchers and
404
+ PayPal can be pending. Show a pending state and voucher link if provided; wait
405
+ for authoritative confirmation before showing a paid receipt or fulfilling.
406
+ ID verification may still be required after payment. An ID-verification failure
407
+ is not a reason to repeat the charge.
408
+
409
+ **Zero-total orders:** this SDK ignores the free-registration response body and
410
+ reports `needIdVerification: false`. Verification or approval may still be
411
+ required, and `free: true` is not proof tickets were issued. The default inline
412
+ `EmbeddedCheckout` can nevertheless show confirmed or ticket-available copy
413
+ while approval or verification is still outstanding. Use
414
+ `confirmationMode="redirect"` with host-owned authoritative order and
415
+ verification status, or a custom composed confirmation gated on that status.
416
+ If the status is unavailable, contact support. Do not retry payment for a free
417
+ order.
418
+
419
+ `OrderConfirmation` is a reloadable presentation and does **not** check the
420
+ order's payment status before showing its default confirmed/ticket-availability
421
+ copy. Gate it on an authoritative status appropriate for that page. When
422
+ `onComplete` reports `pending`, show a pending page (and any voucher) instead;
423
+ do not route directly to `OrderConfirmation` as a paid receipt. The in-flow
424
+ `Confirmation` component reads the current result and has a pending state.
425
+
426
+ `CardInput` renders Stripe Elements and uses the checkout's default card owner;
427
+ `StripePaymentElement` is the deferred Elements option when its server config
428
+ and wallet gates are available. `XenditCardInput` renders Xendit card fields.
429
+ `Payment` wires the right input automatically. When implementing a custom pay
430
+ step, render the correct input for the selected gateway and keep it mounted and
431
+ visible for that payment. Do not collect raw card numbers in your own form or
432
+ logs. Multiple card flows must respect field ownership. With no explicit claim,
433
+ the web hook claims the default `checkout` owner's Payment Element if registered,
434
+ otherwise its `CardInput`, and refuses if neither is available. For a custom
435
+ owner, `claimCardField(owner)` claims a `CardInput` only; a
436
+ `StripePaymentElement` uses `cardClaimStep('webPaymentElement', () =>
437
+ webConfirmer.claimPaymentElement(owner))` as the `placeOrder` `claimCard` step.
438
+ For a bare card field, the `webCard` claim step should call
439
+ `webConfirmer.releasePaymentElement()` before `claimCardField(owner)`. The
440
+ standard `Payment` component handles these claims and key changes for you.
441
+
442
+ `useCheckout({ ports, options })` reads options on first render. The `ports`
443
+ option merges additional compatible web capabilities over the defaults, but its
444
+ type excludes `confirmer`; it cannot replace `webConfirmer`. `options` includes
445
+ `redirectReturnUrl` and merchant/wallet options. A custom confirmer belongs in
446
+ the lower-level `createEngine` configuration, not this web hook option.
447
+
448
+ The engine retains the order when it cannot establish whether a charge moved.
449
+ A custom confirmer should reject using `paymentProviderError(message, outcome)`;
450
+ `outcome` is `declined`, `cancelled`, `notStarted`, or `unknown`. Use the first
451
+ three only when the provider confirms nothing was charged. An unclassified
452
+ error is treated as unknown. Avoid automatic blind retries and never promise
453
+ exactly-once charging across your app, backend and provider.
454
+
455
+ ## Authentication
456
+
457
+ The embedded checkout supplies the event to its built-in `AuthModal`. A
458
+ standalone modal can do the same. The host must already provide a query client
459
+ and initialize the SDK. The checkout callback is deliberately narrow.
224
460
 
225
461
  ```tsx
226
- <Rewards orderHash={hash} snapchat />
462
+ import { useState } from 'react'
463
+ import { AuthModal } from 'tf-checkout-react'
464
+
465
+ export function SignIn() {
466
+ const [open, setOpen] = useState(false)
467
+ return (
468
+ <>
469
+ <button onClick={() => setOpen(true)}>Sign in</button>
470
+ <AuthModal
471
+ open={open}
472
+ onClose={() => setOpen(false)}
473
+ checkoutEventId="EVENT_ID"
474
+ onCheckoutAuthenticated={() => setOpen(false)}
475
+ />
476
+ </>
477
+ )
478
+ }
227
479
  ```
228
480
 
229
- **Copy** is translatable under the `rewards.*` keys through `<I18nProvider/>`,
230
- in `en-GB` and `es-MX`.
231
-
232
- **Styling.** Everything hangs off `.tf-checkout-rewards*` and uses the same
233
- `--tf-checkout-*` tokens as the rest of the checkout, inside the
234
- `.tf-checkout-confirmation-page__rewards` wrapper your theme may already target.
235
-
236
- ## Headless
237
-
238
- `useCheckout()` gives you the whole flow with no UI at all.
481
+ `AuthModal` props: `open` and `onClose` are required; optional
482
+ `checkoutEventId`, `onCheckoutAuthenticated`, `initialView`, `initialEmail`,
483
+ `message`, `allowRegistration` (default `true`), `allowPasswordReset` (default
484
+ `true`), `requireRegistrationAddress` (default `true`), `registrationFields`,
485
+ `logoUrl`, `onAuthenticated` and `className`. Public checkout auth requires its
486
+ registered client and matching deployed server contract. Browser checkout
487
+ authorization uses a popup with state and PKCE; secure browser crypto and
488
+ popups must be available. The popup may be blocked or closed, and two-factor
489
+ or email/phone proof can be required. Surface those outcomes rather than
490
+ assuming sign-in succeeded. The public checkout registration and account
491
+ completion paths force address collection even if `requireRegistrationAddress`
492
+ is `false`.
493
+
494
+ For a custom UI, `hooks.useAuthFlow(options)` owns authentication state,
495
+ validation and transitions, while `LoginForm`, `RegisterForm`,
496
+ `ForgotPasswordForm`, `ResetPasswordForm`, `TwoFactorForm`, `EmailCodeForm` and
497
+ `PhoneAuthView` provide forms/views. `LoginForm` requires `onSubmit`;
498
+ `RegisterForm` requires `onSubmit`; `ForgotPasswordForm` requires `onSubmit`
499
+ and `onBackToLogin`; `ResetPasswordForm` requires a reset `token`.
500
+ `TwoFactorForm` and `EmailCodeForm` are controlled views with required state and
501
+ callbacks; `PhoneAuthView` requires a `PhoneAuthenticationFlow`. Their exported
502
+ `*Props` types document every field. The individual controlled forms do not
503
+ turn an arbitrary callback into a complete authenticated session; use the
504
+ appropriate flow and handle all its states.
505
+
506
+ ## Component reference
507
+
508
+ Props shown without `?` are required. All named `*Props` types below are
509
+ exported unless stated otherwise; TypeScript declarations in the installed
510
+ package are the authoritative detail for callback payloads and DTOs.
511
+
512
+ ### Checkout sections
513
+
514
+ | Export | Props and purpose |
515
+ | --- | --- |
516
+ | `Tickets` | `eventId`, `title?`, `onContinue?`, `buttonLabel?`, `timeSlotDates?` (ISO dates), `step?`, `onSelectSeats?`, `initialPromoCode?`, `referrer?`, `readAttributionFromUrl?`, `className?`. `onContinue` fires after the cart is primed. |
517
+ | `SeatMap` | `eventId`, `onContinue?`, `step?`, `className?`. Callback fires after seats are added and priced. |
518
+ | `AddOns` | `eventId`, `onContinue?`, `onSkip?`, `buttonLabel?`, `eyebrow?`, `heading?`, `className?`. Skip is hidden for a required add-on. |
519
+ | `Bundles` | `eventId`, `onPurchased?(orderHash)`, `onComplete?(order)`, `confirmationMode?`, `visible?`, `heading?`, `className?`. Bundle purchase is its own flow; hide its card field using `visible` when the section is hidden. |
520
+ | `BillingInfo` | `eventId?`, `onContinue?`, `heading?`, `buttonLabel?`, `onLogin?`, `skipWhenProfileComplete?`, `requireEmailConfirmation?`, `collectTicketHolders?`, `collectHolderContact?`, `ttfOptInLabel?`, `brandOptInLabel?`, `privacyPolicy?`, `showPrivacyError?`, `step?`, `className?`. Standalone holder collection follows backend gates unless explicitly enabled. |
521
+ | `WaiverSigning` | `eventId`, `className?`. Render before payment for an event requiring a waiver. |
522
+ | `Payment` | `onComplete?`, `eventId?`, `requireEmailConfirmation?`, `collectTicketHolders?`, `requireHolderContact?`, `additionalConditions?`, `requirePrivacyPolicy?`, `onPrivacyRequired?`, `showTestCardHint?`, `className?`. Match the paired billing policies. |
523
+ | `OrderSummary` | `eventId?`, `className?`. Uses the current checkout's pricing. |
524
+ | `IDVerification` | `className?`. Uses the current checkout/order. |
525
+ | `Confirmation` | `shareUrl?`, `shareMessage?`, `viewOrderUrl?`, `children?`, `className?`. Uses the current checkout result and handles pending presentation. The default share URL and eligibility have the limitations below. |
526
+ | `OrderConfirmation` | `orderHash`, `title?`, `message?`, `helper?`, `eventTitle?`, `eventDate?`, `eventLocation?`, `viewOrderUrl?` (string or function), `showReferral?`, `showShareButtons?`, `showRewards?`, `snapchat?`, `onLoaded?`, `onError?`, `className?`. Reloads confirmation by hash; the host must gate it on appropriate payment status. The three `show*` options default to true; Snapchat defaults to false. |
527
+ | `OrderIDVerification` | `orderHash`, `onVerified?`, `className?`. For a separately routed order. |
528
+ | `Rewards` | `orderHash?`, `data?`, `snapchat?`, `className?`. Supply an order hash or an already loaded `RewardsPayload`; `data` avoids a fetch. |
529
+ | `TicketChange` | `ticketHash`, `orderHash?`, `onChanged?`, `openUrl?`, `onLogin?`, `className?`. Change/upgrade availability and any upgrade waitlist come from server options. |
530
+ | `Accommodation` | `eventId`. Renders the accommodation selection for an applicable cart; requires checkout context. |
531
+
532
+ `Confirmation` defaults `shareUrl` to `window.location.href`, displays generic
533
+ referral copy for non-pending orders, and does not check organizer referral
534
+ eligibility. The current URL may include unrelated query parameters or lack a
535
+ buyer-specific referral. `EmbeddedCheckout` does not expose a `shareUrl` prop.
536
+ For a composed `Confirmation`, pass a verified buyer-specific URL when eligible,
537
+ or `shareUrl=""` to suppress sharing. Alternatively use server-backed
538
+ `OrderConfirmation` / `Rewards` where the appropriate status and response
539
+ support the experience. Check eligibility and attribution against the deployed
540
+ backend; don't treat the generic link as evidence of a referral.
541
+
542
+ `OrderConfirmation` includes `Rewards` when its response has a rewards
543
+ payload unless `showRewards={false}`. `Rewards` can also fetch by hash without a
544
+ React Query provider. Sharing channels depend on organizer configuration and
545
+ browser capabilities. Snapchat is opt-in and loads `sdk.snapkit.com`; allow it
546
+ in the host's CSP only if used. Clipboard and share operations can be denied by
547
+ the browser or buyer. `RewardsLadder` (`rows`, `onSubmitDetails?`,
548
+ `className?`), `RewardsTree` (`tree`, `className?`), `RewardsChannels`
549
+ (`channels`, `shareLink`, `shareMessage`, `assertCurrent?`,
550
+ `shareCardImageUrl?`, `snapchat?`, `className?`), and `RewardsPackCard`
551
+ (`pack`, `onCreate`, `onJoin`, `onLeave`, `assertCurrent?`) are available for
552
+ custom reward layouts.
553
+
554
+ An upgrade waitlist is a separate server-owned ticket-change contract. Valid
555
+ change-options targets can expose Join or Leave only when the backend offers
556
+ that organizer-configured flow. A sold-out row alone does not authorize it;
557
+ malformed targets or entries are ignored. Verify the deployed contract and
558
+ confirm the server response before presenting an action as completed.
559
+
560
+ ### Account, post-purchase and discovery
561
+
562
+ These exports are available in the package, but availability and required
563
+ account authority vary by endpoint. A public checkout buyer is not automatically
564
+ a full account user. Supply a host sign-in/route callback where applicable.
565
+
566
+ | Export | Props and purpose |
567
+ | --- | --- |
568
+ | `TicketRefund` | `ticketHash`, `onSubmitted?`, `onDone?`. Presents offered refund options and submission. |
569
+ | `TicketGift` | `ticketHash`, `onTicketsChanged?`, `onDone?`, `locale?`. Gift flow. |
570
+ | `Passes` | `onTicketsChanged?`, `locale?`. Customer passes and booking actions. |
571
+ | `Trips` | `onOpen(url)`, `onPage(query)`, `data?`, `locale?`, `loading?`, `errorMessage?`, `onRetry?`. Host supplies data and navigation. |
572
+ | `AccountProfile` | `onSaved?(profile, action)`. Profile gender/image actions. |
573
+ | `AccountInformationSection` | `orderId`, `title?`, `target?`, `locale?`, `active?`, `onSaved?`. Order information/answers. |
574
+ | `AccountPaymentPlanSection` | `plan`, `orderHash`, `locale?`, `onOpen?`, `onReturn?`. Payment-plan management for an existing order. |
575
+ | `AccountRewardsSection` | `locale?`, `onBrowseEvents?`, `onViewTickets?`. Account rewards summary. |
576
+ | `AccountDeletion` | `showHeader?`, `showExplanation?`, `locale?`, `onSignIn?`, `onDone?`, `onOpenTeam?`. `AccountDeletionView` additionally requires a `deletion` flow. |
577
+ | `NotificationPreferences` | `locale?`, `onBack?`, `onSignIn?`, `onSaved?`. `NotificationPreferencesView` additionally requires a `preferences` flow. |
578
+ | `ProfileSettings` | `mode: 'links' \| 'visibility'`, `onBack?`, `onSignIn?`. `ProfileSettingsView` additionally requires a `settings` flow. |
579
+ | `AccountEmailEnrichment` | No props. `AccountEmailEnrichmentView` requires `flow`. |
580
+ | `PhoneAccountProfile` | No props. Phone account profile view. |
581
+ | `Interests` | `onSignIn?`. Interest selection. |
582
+ | `Streaming` | `onSignIn?`, `onOpenMusic?(url, provider?)`. Music connections. |
583
+ | `Friends` | `initialCode?`, `onSignIn?`. Friend flows. |
584
+ | `SavedEvents` | `onSignIn?`. Saved-event list. |
585
+ | `Recommendations` | `events`, `enabled?`. Recommendations from the supplied `AccountEvent[]`. |
586
+ | `EventComments` | `eventId`, `previewKey?`, `onSignIn?`, `className?`. Discussion with backend-supplied permissions. `EventCommentsView` accepts a supplied discussion flow. |
587
+ | `PostReaction` | `postId: string \| number`, `onSignIn?`. `PostReactionControls` accepts `flow` and `onSignIn?`. |
588
+ | `DiscoveryTravel` | `searchCities`, `searchEvents`, `storage?`, `locale?`. Host supplies search functions; see `DiscoveryTravelProps` for their signatures. |
589
+
590
+ For account orders and ticket lists use `hooks.useOrders`, `hooks.useOrder`,
591
+ `ttf.orders.list`, `ttf.orders.get` or `ttf.orders.myTickets`. In this version
592
+ `ttf.orders.get` / `useOrder` take a **numeric order ID string**, not a public
593
+ order hash; `OrderConfirmation` takes a hash. The package does not export 1.x
594
+ `MyTicketsContainer`, `OrdersContainer`, `OrderDetailsContainer` or
595
+ `TicketResaleContainer` as drop-in React replacements. Account/resale APIs and
596
+ hooks can support a host-built screen when the appropriate account session and
597
+ backend capabilities exist.
598
+
599
+ ### UI primitives
600
+
601
+ Use these within a `.tf-checkout` styling boundary such as `CheckoutProvider`;
602
+ standard HTML attributes apply where the component extends them.
603
+
604
+ | Export | Important props |
605
+ | --- | --- |
606
+ | `Card` | HTML `div` attributes and children. |
607
+ | `SectionHeader` | `title`, `step?`, `pills?`, `className?`. |
608
+ | `Pill` | `children`, `muted?`. |
609
+ | `Button` | Button attributes, `variant?: 'primary' \| 'secondary' \| 'ghost'`, `block?`, `loading?`. |
610
+ | `Field` | `children`, `label?`, `error?`, `invalid?`, `className?`; `FieldRow` takes `children`, `className?`. |
611
+ | `Input`, `Textarea`, `Select` | Corresponding HTML attributes plus `invalid?`. |
612
+ | `Checkbox` | Input attributes plus `label?`. |
613
+ | `CustomFieldInput` | `field`, `value`, `onChange`, `error?`. |
614
+ | `CustomFieldGroupFields` | `group`, `values`, `onChange(name, value)`, `ticketTypeId?`; `customFieldsComplete` is also exported. |
615
+ | `PaymentMethods` | Controlled `options`, `selected`, `onSelect`, `extraValues`, `onExtraChange`; optional `showValidation`, `children`, `inputSlotFor`, `className`. |
616
+ | `Stepper` | `value`, `onChange`, `min?`, `max?`, `step?`, `disabled?`, `className?`. |
617
+ | `QuantitySelect` | `value`, `onChange`, `min?`, `max?`, `step?`, `disabled?`, `className?`, accessible `label?`. |
618
+ | `PriceLine` | `amount`, `currency?`, `original?`, `note?`, `className?`. |
619
+ | `Timer` | `seconds`, `urgentAt?`, `label?`, `className?`. `TimerBanner` takes `seconds?`, `urgentAt?`, `message?`, `className?`. |
620
+ | `Loader` | `className?`. |
621
+ | `SignaturePad` | `label`, `clearLabel`, `onChange(pngDataUrl, inkLength)`, `disabled?`. |
622
+ | `FrozenFields` | `children`, `frozen?`; disables interaction for its nested fields. |
623
+
624
+ Additional web exports include `themeToVars`, `cx`, `I18nProvider`, `useT`,
625
+ `useInstallmentPicker`, `CardInput`, `StripePaymentElement`, `XenditCardInput`,
626
+ `claimCardField`, `webConfirmer`, `webPorts`, `webXenditTokenizer`, and the
627
+ `createWebStripeConfirmer`, `createWebPorts`, `createWebXenditTokenizer`
628
+ factories. Their installed declarations specify complete inputs and outputs.
629
+ These are lower-level integration points; preserve their payment and session
630
+ ownership rules when replacing the default flow.
631
+
632
+ ## Headless APIs
633
+
634
+ `useCheckout()` returns the snapshot both flat and as `snapshot`, plus actions.
635
+ The web version injects browser payment ports; the shared `hooks.useCheckout`
636
+ is the lower-level binding and requires an engine configuration. The following
637
+ example only reserves caller-supplied current catalog selections. It deliberately
638
+ does not place an order without collecting the event's required data.
239
639
 
240
640
  ```tsx
641
+ import { useState } from 'react'
241
642
  import { useCheckout, type TicketSelection } from 'tf-checkout-react'
242
643
 
243
- function Reserve({ eventId, selections }: { eventId: string; selections: TicketSelection[] }) {
644
+ export function ReserveButton(props: {
645
+ eventId: string
646
+ selections: TicketSelection[]
647
+ }) {
244
648
  const checkout = useCheckout()
245
- return <button onClick={() => { void checkout.setTickets(eventId, selections) }}>Reserve</button>
649
+ const [message, setMessage] = useState('')
650
+ async function reserve() {
651
+ try {
652
+ await checkout.setTickets(props.eventId, props.selections)
653
+ setMessage('Tickets reserved')
654
+ } catch (error) {
655
+ setMessage(error instanceof Error ? error.message : 'Could not reserve tickets')
656
+ }
657
+ }
658
+ return (
659
+ <div>
660
+ <button disabled={checkout.isBusy} onClick={() => void reserve()}>
661
+ Reserve
662
+ </button>
663
+ <p role="status">{message}</p>
664
+ </div>
665
+ )
246
666
  }
247
667
  ```
248
668
 
249
- Build `selections` from the current event catalog, then collect applicable
250
- billing, holder and consent data before calling `placeOrder`. The hook exposes
251
- the live snapshot (status, cart, pricing, errors), `isBusy`,
252
- `secondsLeft` for the reservation countdown, `buyerFields` (the extra fields
253
- this event gates — render them from data), `missingRequiredAddonNames`, and
254
- `isLoggedIn`.
255
-
256
- The engine coordinates concurrent placement and retains an order when a payment
257
- outcome is uncertain. Confirm the authoritative order and provider status before
258
- retrying or fulfilling; this is not a guarantee of exactly-once charging across
259
- all hosts and providers.
260
-
261
- ### Starting a new checkout
262
-
263
- `reset()` returns nothing and starts a reset. If payment is in flight, the
264
- engine waits for that operation; it retains an order when its recorded state
265
- indicates money may have moved. A closed provider page or network error is not
266
- proof that no payment occurred. Reconcile the order with the backend and payment
267
- provider before retrying or fulfilling it.
268
-
269
- To know whether reset completed, await `resetWhenSettled()` instead. It resolves
270
- `true` after reset or `false` when reset cannot proceed, including a retained
271
- order or a retired checkout. For a payment that may have moved, show the buyer
272
- `PAID_ORDER_PENDING_MESSAGE` and reconcile it. While it waits for a payment
273
- (`status === 'placing'`), tell the buyer, for example "Finishing your
274
- payment…":
669
+ A `TicketSelection` has `id` (current encrypted ticket ID), `name`,
670
+ `optionName`, `price`, `quantity` and `currency`. Construct it from the
671
+ current catalog, not a stale cached display row. Do not trust client-side prices
672
+ as the final order amount.
673
+
674
+ ### Snapshot and actions
675
+
676
+ | Field / action | Contract |
677
+ | --- | --- |
678
+ | `status`, `error`, `order`, `eventId`, `expiresAt`, `secondsLeft`, `isBusy` | Progress, latest error/result and reservation timing. `error` is a display string; async actions may also reject. |
679
+ | `selections`, `ticketTypeIds`, `addOns`, `breakdown`, `priceBreakdown` | Cart selections, decrypted ticket type IDs and server pricing. Prefer server-priced output for display. |
680
+ | `customFields`, `buyerFields`, `fieldGates`, `holderNamesRequired`, `requiredAddOns`, `missingRequiredAddonNames` | Inputs and event gates. Required add-ons sold out are exempt from the missing-add-on check. |
681
+ | `billing`, `holders`, `consent`, `conditions`, `acceptedConditions`, `existingAccountEmail` | Buyer draft and acceptance state. `checkBuyerEmail(email)` starts an existing-account check and writes the result to the snapshot. |
682
+ | `waiver`, `waiverSignatures`, `waiverSigningIndexes`, `unavailableDetails`, `orderFixed` | Waiver and order-edit state. Forms are frozen while placing or after the order is charged. |
683
+ | `gateway`, `stripe`, `paymentStripe`, `paymentPlan`, `redirectPaymentMethods`, `walletAvailability`, `stripeElementsConfig`, `currency` | Payment/configuration data after priming. Absence does not authorize a method. |
684
+ | `extraItems`, `extraDraft`, `extraQuotes`, `extraRejections`, `extraExpiresAt`, `reviewedExtraOrderTotal` | Accommodation/extras state and changed authoritative total. Show a changed total before another Pay action. |
685
+ | `setTickets(eventId, selections)`, `setSeats(eventId, lines)`, `setSeatsIo(eventId, selection)`, `resumeCart(eventId, options?)` | Build/price or resume a server cart; return promises. |
686
+ | `setAddons(addOns)`, `setExtraItems(items)` | Reprice/reserve selections asynchronously. `setExtraDraft(items \| undefined)` changes the local draft. |
687
+ | `setBilling(billing)`, `setHolders(holders, options?)`, `setConsent(consent)` | Commit buyer, holder and marketing data. `setHolders` can mark backfilled holder indexes. |
688
+ | `setAcceptedConditions(map)`, `acceptCondition(id, accepted)`, `setAddonDataCapture(map)` | Record condition acceptance and add-on answers. |
689
+ | `setWaiverSignature(index, signature, hold?)`, `holdWaiverSigning(index)` | Coordinate a signature save with the cart/holder it belongs to; release a hold when finished. Prefer `WaiverSigning` for standard UI. |
690
+ | `reloadCheckoutDetails()`, `syncCurrency(eventId, code?, options?)` | Retry unavailable details or switch/seed event currency. Refresh selection and prices after changing currency. |
691
+ | `placeOrder(method?, options?)`, `runIdVerification(orderHash, options?)` | Place/pay; optionally run verification separately. `placeOrder` accepts `autoRunIdVerification`, `idVerification` and `claimCard` options. |
692
+ | `applyPromoCode(eventId, code)`, `attributeReferrer(eventId, referrer)`, `joinWaitingList(eventId, entry)` | Helpers return promises. Refetch catalog after a code changes availability. |
693
+ | `reset()`, `resetWhenSettled()` | Restart subject to payment-settlement protection; see [lifecycle](#checkout-lifecycle). |
694
+ | `snapshot`, `store`, `isLoggedIn` | Entire state, shared store, and whether the hook sees an authenticated or checkout buyer session. The latter does not imply private-account authority. |
695
+
696
+ `checkBuyerEmail` is not itself debounced. `BillingInfo` delays calls, but a
697
+ headless host should debounce and coordinate in-flight checks. A late response
698
+ for an earlier email in the same cart can overwrite the snapshot; compare
699
+ `existingAccountEmail` with the current trimmed input before prompting and
700
+ recheck the current value if needed.
701
+
702
+ `CheckoutBilling` requires `firstName`, `lastName`, `email`; optional fields
703
+ include `confirmEmail`, `phone`, address, country/state IDs and codes, date of
704
+ birth, nationality, gender, Instagram and `dataCapture`. `CheckoutHolder`
705
+ requires `firstName`, `lastName`, `email`; optional `phone`, `ticketTypeId`,
706
+ `ticketDataCapture`. `CheckoutConsent` has boolean `ttfOptIn` and
707
+ `brandOptIn`. Event gates may require additional data even where the TypeScript
708
+ property is optional. `confirmEmail` is a UI/preflight value; the order request
709
+ uses the authoritative email.
710
+
711
+ `PaymentMethod` accepts `'card'`, `'applePay'` or `'googlePay'`.
712
+ `PaymentRequest` additionally accepts objects with `method: 'auto'`, `'card'`,
713
+ `'applePay'`, `'googlePay'`, `'redirect'` (`methodType`), `'oxxo'`,
714
+ `'xenditEWallet'` (`walletKey`, optional `extra.cashtag` / `extra.phoneNumber`),
715
+ `'paypal'`, `'razorpay'` (optional `description`), `'adyen'`, `'xenditCard'`,
716
+ or `'paymentPlan'` (optional `useSavedCard`). Availability and necessary ports
717
+ are provider-specific. `placeOrder()` defaults to card; do not use that default
718
+ for a different gateway.
719
+
720
+ ### React Query hooks and direct API
721
+
722
+ The package re-exports the shared core: `ttf` is a configured domain facade,
723
+ `api` contains raw calls, `hooks` contains React bindings, and DTOs, logic,
724
+ `createEngine` and `createCheckoutStore` are named exports. `ttf` calls return
725
+ promises; they do not render UI or replace the engine's sequencing, payment
726
+ ownership and preflight. Use them directly only when your application owns the
727
+ corresponding workflow. The installed `dist/index.d.ts` includes the complete
728
+ exported signatures. Avoid importing a private shared-core package.
275
729
 
276
730
  ```tsx
277
- if (checkout.status === 'placing') setNotice('Finishing your payment…')
278
- if (!(await checkout.resetWhenSettled())) setNotice(PAID_ORDER_PENDING_MESSAGE)
279
- ```
280
-
281
- A custom `providerPage` must resolve `{ event: 'dismiss' }` when it closes or
282
- unmounts, and other custom ports must settle too, or `resetWhenSettled()` waits.
731
+ import { hooks } from 'tf-checkout-react'
283
732
 
284
- A custom confirmer can classify a failure with
285
- `paymentProviderError(message, outcome)`, where `outcome` is `declined`,
286
- `cancelled`, `notStarted` or `unknown`. Use the first three only when the
287
- provider confirms that no charge occurred. An unknown outcome needs
288
- reconciliation, not an assumption that payment failed.
289
-
290
- For direct API access, the shared SDK exports are available from this package,
291
- including `ttf`, hooks, types and the engine. The package's TypeScript
292
- declarations describe its public exports.
293
-
294
- ## Card fields
295
-
296
- `<CardInput />` renders Stripe Elements. `<XenditCardInput />` renders the
297
- Xendit card form for gateways that use it — both are wired into `<Payment />`
298
- automatically based on the event's gateway, so you only reach for them directly
299
- when building a custom payment step.
300
-
301
- ## Development
302
-
303
- From the repo root:
304
-
305
- ```bash
306
- pnpm run start:react
733
+ export function CatalogStatus({ eventId }: { eventId: string }) {
734
+ const catalog = hooks.useTicketsWithMeta(eventId)
735
+ if (catalog.isPending) return <p>Loading tickets…</p>
736
+ if (catalog.isError) return <button onClick={() => void catalog.refetch()}>Retry</button>
737
+ return <p>{catalog.data.tickets.length} ticket types</p>
738
+ }
307
739
  ```
308
740
 
309
- `pnpm run playground:react` opens the docs-style playground instead, with a
310
- live preview and source for each panel.
311
-
312
- Use only public, non-secret configuration in `example/.env`; never put a
313
- confidential credential in browser code.
314
-
315
- ---
316
-
317
- The checkout monorepo is available to authorised repository collaborators.
318
-
319
-
320
- Public checkout sign-in uses event-scoped server-only buyer binding, without a bundled
321
- client secret. The checkout's auth modal supplies the event automatically; standalone
322
- `AuthModal` callers pass `checkoutEventId` and can receive the narrow
323
- `onCheckoutAuthenticated(CheckoutBuyerProfile)` callback. Password/signup/email/phone
324
- proofs still complete required two-factor verification. These credentials cannot read
325
- private account preferences or tickets. Existing confidential OAuth clients
326
- are trusted-server integrations; never distribute their secrets in browser/native code.
741
+ Useful hook groups, all imported through `hooks`:
742
+
743
+ - Catalog: `useEvent`, `useTickets`, `useTicketsWithMeta`,
744
+ `useTimeSlotTickets`, `useEventAddons`, `useEventBundles`, `useTicketCatalog`,
745
+ `useCountries`, `useStates`.
746
+ - Authentication/session: `useAuthFlow`, `useAuthSession`, `useLogin`,
747
+ `useRegister`, `useLogout`, `useProfile`, `usePhoneAuthentication` and
748
+ verification mutations. Choose the correct public checkout or account path.
749
+ - Checkout/selection: `useCheckout`, `useAddOnSelection`, `useSeatSelection`,
750
+ `useSeatsIoSelection`, `useWaitingListForm`, `useWaiverSigning`,
751
+ `useAccommodationSelection`, `useCheckoutPaymentMethods`, `useBundleFlow`.
752
+ - After purchase/account: `useOrders`, `useOrder`, `useOrderRewards`,
753
+ `useTicketChange`, `useTicketRefund`, `useTicketGift`, `useCustomerPasses`,
754
+ `useAccountPreferences`, `useNotificationPreferences`, `useAccountDeletion`,
755
+ `useFriends`, `useEventComments`, `useTrips`.
756
+
757
+ Catalog hooks use an event ID and many are disabled until it is present; query
758
+ results expose standard React Query loading, error, data and `refetch` states.
759
+ The account-order query uses a numeric order ID. Account access is scoped to the
760
+ buyer session; don't reuse private cached data across sign-out or buyer changes.
761
+
762
+ The `ttf` facade groups calls under `auth`, `profile`, `events`, `cart`,
763
+ `order`, `payment`, `seatmap`, `engagement`, `confirmation`, `rewards`,
764
+ `orders`, `ticketChanges`, `resale`, `refunds`, `gifts`, `passes`, `waivers`,
765
+ `extras`, `trips`, `preferences`, `friends`, `savedEvents`, `comments`, `word`,
766
+ `preRegistration` and `delegation`. For example, `ttf.events.ticketsWithMeta`
767
+ loads a catalog, `ttf.geo.countries()` loads country choices, and
768
+ `ttf.confirmation.get(orderHash)` reads a confirmation. The `geo` group is
769
+ also available. Check each method's declared input and backend authority before
770
+ calling it.
771
+
772
+ Transport errors are normalized as `TtfError`, with `message`, HTTP `status`
773
+ (`0` for transport failure), optional `code`, `data` and `retryAfter` seconds.
774
+ Engine validation and provider errors can also be ordinary errors or typed
775
+ errors such as `UNSUPPORTED_GATEWAY`; handle unknown errors as well. Avoid
776
+ logging raw error data if it may contain personal or payment information.
777
+
778
+ ### Tracking
779
+
780
+ For an integration that is authorized to send event tracking, the explicit
781
+ facade call is `ttf.events.track(eventId, { pageUrl?, page?, orderHash? })`.
782
+ The host owns when it fires, consent, deduplication and any additional analytics
783
+ pixels. No 1.x pixel setup or automatic event equivalence should be assumed.
784
+ Avoid sending unnecessary buyer data in URLs or analytics.
785
+
786
+ ## Styling and localization
787
+
788
+ Pass a `theme` to `EmbeddedCheckout`, `ResumedCheckout` or `CheckoutProvider`.
789
+ All tokens are optional:
790
+
791
+ | Group | `CheckoutTheme` keys |
792
+ | --- | --- |
793
+ | Scheme | `colorScheme: 'light' \| 'dark'`; when omitted CSS follows the browser/OS preference. |
794
+ | Colors | `accent`, `accentHover`, `accentContrast`, `surface`, `surfaceMuted`, `text`, `muted`, `border`, `error`, `success`. |
795
+ | Shape | `radiusCard`, `radiusControl`, `shadow`. |
796
+ | Type/layout | `font`, `fontSize`, `maxWidth`. |
797
+
798
+ Values are CSS strings mapped to `--tf-checkout-*` custom properties. The
799
+ provider adds `.tf-checkout` and optionally `.tf-checkout--light` or
800
+ `.tf-checkout--dark`; section classes use `tf-checkout-*`. Theme variables and
801
+ classes can be overridden by your CSS, but test embedded Stripe iframes and
802
+ third-party phone/seat-map styling separately. Stripe Elements do not inherit
803
+ page CSS directly. Preserve readable contrast, visible focus and usable error
804
+ states in custom themes.
805
+
806
+ `locale` defaults to `en-GB`; accepted `LocaleCode` values are `en-GB`,
807
+ `en-US`, `es-MX`, `fr`, `de`, `ru`, and `id`. Catalogues can be partial and
808
+ fall back to English; verify all buyer-facing copy for your event. Pass
809
+ `messages` for per-key overrides, or use `I18nProvider` / `useT` around
810
+ standalone components. Event-supplied content and provider pages may have
811
+ their own language. Test keyboard navigation, screen readers, validation focus,
812
+ small screens, reduced motion, popups, and the entire host page; an SDK control
813
+ alone cannot guarantee accessibility of the surrounding experience.
814
+
815
+ ## Migrating from 1.x
816
+
817
+ 2.x is a new shared-core integration, not a prop-compatible update. Keep the
818
+ existing 1.x integration pinned until the new flow and backend are verified.
819
+ There is no automatic translation of old URLs, component props, account
820
+ sessions or persisted 1.x state.
821
+
822
+ | 1.x integration | 2.0.0-beta.24 approach |
823
+ | --- | --- |
824
+ | `setConfigs({ BASE_URL, ... })` | `configureCheckout({ env` or `baseUrl`, `clientId`, `clientSecret: ''`, ... `})`; public client registration and session setup are required. |
825
+ | `TicketsContainer`, `SeatMapContainer`, `AddOnsContainer` | `EmbeddedCheckout` or `Tickets`, `SeatMap`, `AddOns` under `CheckoutProvider`. Adapt callbacks and navigation. |
826
+ | `BillingInfoContainer`, `PaymentContainer` | `BillingInfo` and `Payment` with matched policies and the current cart; or embedded flow. Old prop names/callbacks are not a contract. |
827
+ | `ConfirmationContainer` | `Confirmation` for the active flow or `OrderConfirmation` for a reloadable order-hash page. Handle `pending` and ID verification. |
828
+ | Legacy login/register/forgot-password modals | `AuthModal` or `hooks.useAuthFlow` and controlled forms. Distinguish event-scoped checkout identity from a full account session. |
829
+ | `AccountSettings` | No same-named drop-in replacement is established. Evaluate `AccountProfile`, `ProfileSettings`, `NotificationPreferences` and the account APIs against your required authorized account features. |
830
+ | `MyTicketsContainer`, order/detail and resale containers | No same-named drop-in component in this version. Use supported account APIs/hooks and build the host UI after verifying authorization and deployed backend support. |
831
+ | Webflow/no-code integration | No 2.x drop-in Webflow or no-code checkout integration has been established. Integrate the React checkout in a compatible host or verify a separate integration with Ticket Fairy. |
832
+ | Promo/access, waiting list, pixel and referral behavior | Re-check catalog metadata, code/attribution precedence, waiting-list contract and explicit tracking. Do not infer numeric `?r=`. |
833
+ | Legacy single-page state in localStorage | Use the 2.x checkout store/engine and server cart; do not copy sensitive state or assume 1.x persisted state can be resumed. |
834
+
835
+ Compare the exact event's ticket types, dated pricing, seats, add-ons, holder
836
+ questions, conditional fields, consent, waivers, fees, currencies, payment
837
+ methods, account and post-purchase behavior before switching. Backend rollout
838
+ and provider acceptance are independent of the npm package publication.
839
+
840
+ The distribution includes standalone widget JavaScript and CSS bundles for
841
+ other product surfaces. Their presence alone does not establish a supported
842
+ public checkout-widget API or a 2.x Webflow integration; use the documented
843
+ React exports unless a separate integration contract has been verified.
844
+
845
+ ## Testing and troubleshooting
846
+
847
+ Use a non-production event and provider test configuration. Exercise at least:
848
+
849
+ 1. A general-admission purchase, free registration, multi-quantity holders,
850
+ required and unavailable add-ons, custom questions and waiver; inspect
851
+ server-priced totals before payment.
852
+ 2. Seated/time-slot and multi-currency events if used; expiry, sold-out changes,
853
+ promo/access codes, explicit referral attribution and waiting-list states.
854
+ 3. Guest, existing-account, sign-in, two-factor and sign-out/buyer-switch flows;
855
+ verify no prior buyer's cart or private data is shown to the next buyer.
856
+ 4. Every enabled provider: success, decline, cancellation, blocked popup,
857
+ redirect return, offline/timeout and asynchronous pending settlement. Check
858
+ the authoritative order/provider before any retry or fulfilment.
859
+ 5. Refresh, back navigation, multiple clicks, a delayed response, small-screen
860
+ and keyboard/screen-reader flows. Test on the actual supported browsers,
861
+ configured origins and deployment environment.
862
+
863
+ | Symptom | Check |
864
+ | --- | --- |
865
+ | "SDK is not configured" | Run `configureCheckout` before mounting any SDK component or calling `ttf`. |
866
+ | Query context error | Wrap the relevant tree in a `QueryClientProvider` using the same React Query instance as the SDK. |
867
+ | No styles / CSS build error | Confirm the bundler processes package CSS, nested `@import`s and assets. Check the package entry and stylesheet export. |
868
+ | Login fails or popup closes | Verify the public client, event, deployed auth route, secure browser crypto, popup permission, origin/cookie/CORS settings and any required proof step. |
869
+ | Cart or pricing missing | Verify the event/environment, current ticket IDs, availability, session, network response and that reservation completed. Re-fetch after a code or currency change. |
870
+ | No seat button | The event must offer seating; a composed `Tickets` needs `onSelectSeats` connected to a `SeatMap` view. |
871
+ | Pay is disabled / validation error | Check buyer and holder rules, required add-ons/answers/conditions, privacy acknowledgement, waiver state and cart expiry. Use matching billing and payment policies. |
872
+ | Payment method unavailable | Check the event's returned method list, merchant/currency/device/provider setup, popup/CSP and backend rollout. A union member alone does not enable a rail. |
873
+ | Payment is pending or outcome uncertain | Show the pending state, retain the order and reconcile with the backend/provider. Do not blindly reset, charge again or fulfil. |
874
+ | Private account data is unavailable | A public checkout buyer session is narrower than a full account session; verify the account journey and authority. |
875
+
876
+ Only public/non-secret configuration belongs in client bundles, example `.env`
877
+ files and browser logs. Order hashes, buyer data and provider responses should
878
+ be handled as sensitive application data.