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

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 +543 -101
  2. package/docs/API.md +73 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,25 +7,40 @@ components, plus the shared API and React Query hooks. Which paths a buyer can
7
7
  actually use depend on the event, the deployed API, the buyer's session and the
8
8
  payment provider.
9
9
 
10
- This reference describes **2.0.0-beta.24**. It is a prerelease with a different
10
+ This reference describes **2.0.0-beta.26**. It is a prerelease with a different
11
11
  API from 1.x. Pin the version while evaluating it. npm's `latest` and `beta`
12
12
  tags can move independently and an unqualified install may select 1.x:
13
13
 
14
14
  ```bash
15
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
16
+ npm install tf-checkout-react@2.0.0-beta.26 @tanstack/react-query react react-dom
17
17
  ```
18
18
 
19
- The [API reference](https://unpkg.com/tf-checkout-react@2.0.0-beta.24/docs/API.md)
19
+ The [API reference](https://unpkg.com/tf-checkout-react@2.0.0-beta.26/docs/API.md)
20
20
  is included as `docs/API.md` in this package.
21
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) ·
22
+ ## Choose a checkout layout
23
+
24
+ | Your goal | Start here | What the SDK renders |
25
+ | --- | --- | --- |
26
+ | Add a complete checkout to an event page | [`EmbeddedCheckout`](#embeddedcheckout) | A ready-made ticket or seat selection view followed by add-ons, buyer details, waiver and payment when applicable. |
27
+ | Continue a cart created by an earlier flow | [`ResumedCheckout`](#resume-a-server-cart) | Ready-made buyer details, waiver and payment for the current session's existing cart. |
28
+ | Keep the checkout on one page and arrange the sections yourself | [Single-page composition](#single-page-checkout) | Ticket selection, seats, add-ons, billing and payment sections in the host's layout. |
29
+ | Show progressive steps or connect routes | [Step-by-step composition](#step-by-step-checkout) | The same sections; your application controls what is visible and how the buyer moves between steps. |
30
+ | Organize the form as an accordion | [Accordion composition](#accordion-checkout) | Native HTML disclosure sections around the SDK's checkout components. |
31
+ | Build your own checkout UI | [Headless APIs](#headless-apis) | The shared engine, hooks and API; your application owns the UI and navigation. |
32
+
33
+ `EmbeddedCheckout` and `ResumedCheckout` are the ready-made flows in this
34
+ version. The 1.x `*Container` components and named single-page/accordion
35
+ examples are not drop-in 2.x exports. The [migration guide](#migrating-from-1x)
36
+ shows how to reproduce those layouts with the current components.
37
+
38
+ [Requirements](#requirements) · [Quick start](#quick-start) ·
39
+ [Configure sessions](#configuration-and-sessions) · [Ready-made checkout](#embeddedcheckout) ·
40
+ [Custom layouts](#compose-sections) · [Payments](#payments) ·
41
+ [Authentication](#authentication) · [Component reference](#component-reference) ·
27
42
  [Styling](#styling-and-localization) · [Migration](#migrating-from-1x) ·
28
- [Testing and troubleshooting](#testing-and-troubleshooting)
43
+ [Troubleshooting](#testing-and-troubleshooting)
29
44
 
30
45
  ## Requirements
31
46
 
@@ -50,17 +65,25 @@ Its `dist/styles` files and referenced assets are part of the npm package.
50
65
 
51
66
  ## Quick start
52
67
 
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.
68
+ This example puts the ready-made checkout on one event page. You need a test
69
+ event ID and the matching **public** SDK client ID. Ask your Ticket Fairy
70
+ integration contact for these values and the appropriate environment.
71
+
72
+ 1. Install the package and its peers using the command above.
73
+ 2. Replace `EVENT_ID` and `PUBLIC_SDK_CLIENT_ID` below. Configure the SDK once
74
+ in your browser entry, before rendering the checkout.
75
+ 3. Mount `App` in your React application. The package entry imports its CSS;
76
+ the bundler must process CSS imports and their assets.
56
77
 
57
78
  ```tsx
58
79
  import { useState } from 'react'
59
80
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
60
- import { configureCheckout, EmbeddedCheckout } from 'tf-checkout-react'
81
+ import {
82
+ configureCheckout, EmbeddedCheckout, type PlaceOrderResult,
83
+ } from 'tf-checkout-react'
61
84
 
62
85
  configureCheckout({
63
- env: 'PROD',
86
+ env: 'STAG',
64
87
  clientId: 'PUBLIC_SDK_CLIENT_ID',
65
88
  clientSecret: '',
66
89
  cookieAuth: true,
@@ -69,30 +92,41 @@ configureCheckout({
69
92
  const queryClient = new QueryClient()
70
93
 
71
94
  export function App() {
72
- const [notice, setNotice] = useState('')
95
+ const [order, setOrder] = useState<PlaceOrderResult | null>(null)
96
+
73
97
  return (
74
98
  <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>
99
+ {order ? (
100
+ <section role="status">
101
+ <h1>Checking your order</h1>
102
+ <p>Check your order status before making another purchase.</p>
103
+ {order.pending && <p>Your payment is pending.</p>}
104
+ {order.needIdVerification && <p>Identity verification may be required.</p>}
105
+ </section>
106
+ ) : (
107
+ <EmbeddedCheckout
108
+ eventId="EVENT_ID"
109
+ confirmationMode="redirect"
110
+ onComplete={setOrder}
111
+ />
112
+ )}
82
113
  </QueryClientProvider>
83
114
  )
84
115
  }
85
116
  ```
86
117
 
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.
118
+ The buyer first chooses tickets or seats. After reservation, the checkout shows
119
+ applicable add-ons, buyer details, waiver and payment. `onComplete` hands the
120
+ result to the host; the example displays a neutral status instead of claiming
121
+ that tickets are ready. Connect that screen to **your backend's authoritative
122
+ order status** using `order.orderHash`, handle any required ID verification and
123
+ pending voucher, and provide support if the status cannot be established. The
124
+ callback alone is not proof of settlement or ticket issuance.
91
125
 
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.
126
+ Use `env: 'PROD'` with the matching production public client ID when your
127
+ integration is ready. This beta's stock payment UI can ask a buyer to retry an
128
+ unconfirmed payment; that wording does not establish that retrying is safe. See
129
+ [Payments](#payments) before using the stock flow in production.
96
130
 
97
131
  ## Configuration and sessions
98
132
 
@@ -147,7 +181,31 @@ third-party dependencies should be tested in that framework.
147
181
  seat selection, add-ons, billing, waivers, payment and inline completion as the
148
182
  flow requires. It must be under `QueryClientProvider`.
149
183
 
150
- Only `eventId` is required. Callbacks and options are optional.
184
+ Only `eventId` is required. Callbacks and options are optional. The component
185
+ owns its checkout provider; do not wrap it in another `CheckoutProvider` to
186
+ control its internal steps. The host still supplies `QueryClientProvider`.
187
+
188
+ ### What the buyer sees
189
+
190
+ 1. **Tickets:** the event's catalog, quantities, applicable codes and any
191
+ organizer-configured waiting list. Seated events can open a seat map.
192
+ 2. **Checkout:** a selection summary and the applicable add-ons, buyer/holder
193
+ details, waiver and payment. The reservation timer is displayed when available.
194
+ 3. **Completion:** `inline` shows the SDK's confirmation view; `redirect` calls
195
+ `onComplete` and leaves the host to show the next screen. The quick start uses
196
+ `redirect` so the host can check order status before presenting a receipt.
197
+
198
+ `initialView="seats"` opens the seat-selection view first for an applicable
199
+ seated event. `onViewChange` reports `tickets`, `seats` or `checkout`; it does
200
+ not turn the component into a controlled router. For custom paths or step
201
+ arrangements, use [section composition](#compose-sections).
202
+
203
+ When choosing `confirmationMode="redirect"`, provide `onComplete` to receive
204
+ the result and navigate to your status page. Although the prop is optional in
205
+ the type, without that handoff the checkout remains mounted and no host status
206
+ page is reached.
207
+
208
+ ### Options and callbacks
151
209
 
152
210
  | Prop | Type / default | Description |
153
211
  | --- | --- | --- |
@@ -179,98 +237,408 @@ session authorities; use the one appropriate to your configured journey.
179
237
  for the public checkout registration or account-completion journey; those
180
238
  paths force it on in this version.
181
239
 
240
+ ### Example: brand the ready-made checkout
241
+
242
+ Use the same `QueryClientProvider` and one-time configuration as the quick
243
+ start. Here `onViewChange` can update surrounding page content; `onComplete`
244
+ passes the order result to the host's status screen.
245
+
246
+ ```tsx
247
+ import { useState } from 'react'
248
+ import { EmbeddedCheckout, type PlaceOrderResult } from 'tf-checkout-react'
249
+
250
+ export function BrandedCheckout() {
251
+ const [order, setOrder] = useState<PlaceOrderResult | null>(null)
252
+ const [view, setView] = useState('tickets')
253
+
254
+ if (order) return <p role="status">Checking your order status…</p>
255
+
256
+ return (
257
+ <main>
258
+ <p>Checkout step: {view}</p>
259
+ <EmbeddedCheckout
260
+ eventId="EVENT_ID"
261
+ confirmationMode="redirect"
262
+ onComplete={setOrder}
263
+ onViewChange={setView}
264
+ initialPromoCode="WELCOME"
265
+ referrer="REFERRER_ID"
266
+ readAttributionFromUrl={false}
267
+ locale="en-US"
268
+ messages={{ 'tickets.getTickets': 'Continue to checkout' }}
269
+ theme={{ colorScheme: 'light', accent: '#6b2bd9', radiusCard: '16px' }}
270
+ className="festival-checkout"
271
+ />
272
+ </main>
273
+ )
274
+ }
275
+ ```
276
+
277
+ Use a promo or referral only when it belongs to this visit. Remove those props
278
+ when they do not apply. By default the component also reads `promo` /
279
+ `promo_code` and `ttf_r` / `referrer_id` from the URL. The example disables
280
+ that behavior so the host controls attribution. `initialPromoCode` is applied
281
+ once when the catalog mounts. It is not a guarantee that a code is valid.
282
+ `theme` changes SDK design tokens, `className` lets your stylesheet target this
283
+ instance, and `messages` overrides individual SDK copy keys. `locale` and
284
+ `messages` do not automatically translate the event's own content or an
285
+ external payment-provider page. See [Styling and localization](#styling-and-localization)
286
+ for the available theme tokens and locales.
287
+
288
+ ### Example: connect host navigation and sign-in
289
+
290
+ Pass callbacks when the surrounding application owns these actions. For
291
+ example, `openSignIn` can open your sign-in UI, and `onOrder` can hand the
292
+ result to your order-status screen. `onViewChange` is a notification, not a
293
+ way to force the checkout to a particular step.
294
+
295
+ ```tsx
296
+ import { EmbeddedCheckout, type PlaceOrderResult } from 'tf-checkout-react'
297
+
298
+ type View = 'tickets' | 'seats' | 'checkout'
299
+
300
+ export function HostCheckout(props: {
301
+ eventId: string
302
+ openSignIn: (email?: string) => void
303
+ showOrderStatus: (order: PlaceOrderResult) => void
304
+ reportView: (view: View) => void
305
+ }) {
306
+ return (
307
+ <EmbeddedCheckout
308
+ eventId={props.eventId}
309
+ onLogin={props.openSignIn}
310
+ onViewChange={props.reportView}
311
+ confirmationMode="redirect"
312
+ onComplete={props.showOrderStatus}
313
+ />
314
+ )
315
+ }
316
+ ```
317
+
318
+ The email passed to `openSignIn` can be absent. Supplying `onLogin` replaces
319
+ the built-in login modal; your host must complete the appropriate sign-in
320
+ journey. An event-scoped checkout identity is not automatically a full
321
+ account session. `showOrderStatus` must check the order and provider rather
322
+ than treating the callback as a paid receipt.
323
+
182
324
  ### Resume a server cart
183
325
 
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.
326
+ `ResumedCheckout` is a ready-made layout for a cart that **already exists in
327
+ the current session**, such as one created by a preceding resale flow. It
328
+ starts with buyer details and payment; it does not show ticket selection, create
329
+ the cart, or accept a cart ID. Its `confirmationMode` defaults to `redirect`.
330
+ Provide `onComplete` for the redirect/status handoff: without it the successful
331
+ flow remains on its loading transition. Alternatively select `inline` and
332
+ account for the confirmation and verification-copy limitations below.
189
333
 
190
334
  ```tsx
191
335
  import { useState } from 'react'
192
- import { ResumedCheckout } from 'tf-checkout-react'
336
+ import { ResumedCheckout, type PlaceOrderResult } from 'tf-checkout-react'
193
337
 
338
+ // Mount only after your preceding flow created a cart in this session.
194
339
  export function ResumePage() {
195
- const [notice, setNotice] = useState('')
340
+ const [order, setOrder] = useState<PlaceOrderResult | null>(null)
341
+ if (order) return <p role="status">Checking your order status…</p>
342
+
196
343
  return (
197
- <>
198
344
  <ResumedCheckout
199
345
  eventId="EVENT_ID"
200
346
  isFromResale
201
- confirmationMode="inline"
202
- onComplete={(order) => setNotice(order.pending ? 'Payment pending' : 'Order submitted')}
347
+ confirmationMode="redirect"
348
+ onComplete={setOrder}
203
349
  />
204
- <p role="status">{notice}</p>
205
- </>
206
350
  )
207
351
  }
208
352
  ```
209
353
 
354
+ Wrap this page in the same `QueryClientProvider` as the quick start. Set
355
+ `isFromResale` only for the resale path. Check the result with your backend,
356
+ including any verification or pending-payment requirements. If the preceding
357
+ flow did not create a cart, or the session changed, return the buyer to that
358
+ flow instead of assuming a cart can be resumed.
359
+
210
360
  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.
361
+ `onComplete`, `confirmationMode`, `requireEmailConfirmation`,
362
+ `collectTicketHolders`, `collectHolderContact` (each default `true`),
363
+ `privacyPolicy`, `brandOptInLabel`, `additionalConditions`, `theme` and
364
+ `className`. Unlike `EmbeddedCheckout`, this component does not accept
365
+ `locale`, `messages`, `auth` or `onLogin` props in this version.
218
366
 
219
367
  ## Compose sections
220
368
 
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.
369
+ Use sections when the ready-made flow does not fit your page. Put all sections
370
+ for one checkout inside a single `CheckoutProvider`, under the host's
371
+ `QueryClientProvider`. The provider shares one checkout instance with its
372
+ children. The default store is app-wide: mounting two flows does not create two
373
+ independent carts. Configure the SDK once, before mounting the provider.
374
+
375
+ The examples below assume the quick start's `configureCheckout` and
376
+ `QueryClientProvider` setup. They demonstrate page layout and section wiring;
377
+ your host still needs to implement order-status reconciliation, expiry and
378
+ recovery for its event and enabled payment methods.
379
+
380
+ These are wiring recipes, not complete buyer navigation. Each includes a return
381
+ from the seat view and keeps `Tickets` mounted while switching views. The host
382
+ still needs to implement Edit and Back from checkout (including payment to
383
+ billing), preserve entered details, handle seat holds and cart expiry, and
384
+ guard event/session changes and in-flight or retained payments. Use
385
+ `EmbeddedCheckout` for its built-in Back and guarded Edit behavior.
386
+ The seat-view Back button only changes views: it does not itself release reserved
387
+ seats or cancel an already-started reservation or cart priming. Coordinate any
388
+ in-flight work and the hold's expiry or release under your deployed backend
389
+ contract before permitting a conflicting selection.
390
+
391
+ ### Single-page checkout
392
+
393
+ This layout keeps the buyer on one browser page. Selecting tickets or seats
394
+ reveals a single combined form with add-ons, buyer details, waiver and payment.
395
+ The host supplies the status view after `onComplete`.
227
396
 
228
397
  ```tsx
229
398
  import { useState } from 'react'
230
399
  import {
231
- AddOns, BillingInfo, CheckoutProvider, OrderSummary, Payment,
232
- Tickets, WaiverSigning,
400
+ AddOns, BillingInfo, CheckoutProvider, Payment, SeatMap,
401
+ Tickets, TimerBanner, WaiverSigning, useCheckoutContext, type PlaceOrderResult,
233
402
  } from 'tf-checkout-react'
234
403
 
235
- export function ComposedCheckout() {
236
- const eventId = 'EVENT_ID'
237
- const [notice, setNotice] = useState('')
404
+ function SinglePageContents({ eventId }: { eventId: string }) {
405
+ const checkout = useCheckoutContext()
406
+ const [view, setView] = useState<'tickets' | 'seats' | 'checkout'>('tickets')
407
+ const [order, setOrder] = useState<PlaceOrderResult | null>(null)
408
+
409
+ if (order) return <p role="status">Checking your order status…</p>
410
+
411
+ return (
412
+ <main>
413
+ <div hidden={view !== 'tickets'}>
414
+ <Tickets
415
+ eventId={eventId}
416
+ onContinue={() => setView('checkout')}
417
+ onSelectSeats={() => setView('seats')}
418
+ />
419
+ </div>
420
+ {view === 'seats' && (
421
+ <>
422
+ <button type="button" onClick={() => setView('tickets')}>Back to tickets</button>
423
+ <SeatMap eventId={eventId} onContinue={() => setView('checkout')} />
424
+ </>
425
+ )}
426
+ {view === 'checkout' && (
427
+ <>
428
+ <TimerBanner seconds={checkout.secondsLeft} />
429
+ <AddOns eventId={eventId} />
430
+ <BillingInfo
431
+ eventId={eventId}
432
+ requireEmailConfirmation
433
+ collectTicketHolders
434
+ collectHolderContact
435
+ />
436
+ <WaiverSigning eventId={eventId} />
437
+ <Payment
438
+ eventId={eventId}
439
+ requireEmailConfirmation
440
+ collectTicketHolders
441
+ requireHolderContact
442
+ onComplete={setOrder}
443
+ />
444
+ </>
445
+ )}
446
+ </main>
447
+ )
448
+ }
449
+
450
+ export function SinglePageCheckout({ eventId }: { eventId: string }) {
238
451
  return (
239
452
  <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>
453
+ <SinglePageContents eventId={eventId} />
251
454
  </CheckoutProvider>
252
455
  )
253
456
  }
254
457
  ```
255
458
 
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`,
459
+ `Tickets.onContinue` fires after the cart is primed. `onSelectSeats` opens the
460
+ seat view; without it the seat-selection button is hidden in a composed
461
+ `Tickets` section. `SeatMap.onContinue` runs after seats are added and priced.
462
+ Without an `onContinue` prop, `BillingInfo` and `AddOns` use their combined-form
463
+ behavior instead of showing a separate Continue button. Match the billing and
464
+ payment email/holder settings, as shown. `AddOns` renders nothing when the
465
+ event has no applicable add-ons; do not skip a required one.
466
+
467
+ ### Step-by-step checkout
468
+
469
+ To make billing and payment separate steps, give `BillingInfo` an `onContinue`
470
+ callback. It then validates and saves the buyer details before moving on. This
471
+ example keeps the checkout provider mounted for the whole flow; it does not
472
+ change URLs or assume a browser refresh can resume the in-memory layout.
473
+
474
+ ```tsx
475
+ import { useState } from 'react'
476
+ import {
477
+ AddOns, BillingInfo, CheckoutProvider, Payment, SeatMap,
478
+ Tickets, TimerBanner, WaiverSigning, useCheckoutContext, type PlaceOrderResult,
479
+ } from 'tf-checkout-react'
480
+
481
+ type Step = 'tickets' | 'seats' | 'details' | 'payment'
482
+
483
+ function StepContents({ eventId }: { eventId: string }) {
484
+ const checkout = useCheckoutContext()
485
+ const [step, setStep] = useState<Step>('tickets')
486
+ const [order, setOrder] = useState<PlaceOrderResult | null>(null)
487
+
488
+ if (order) return <p role="status">Checking your order status…</p>
489
+
490
+ return (
491
+ <main>
492
+ {(step === 'details' || step === 'payment') && (
493
+ <TimerBanner seconds={checkout.secondsLeft} />
494
+ )}
495
+ <div hidden={step !== 'tickets'}>
496
+ <Tickets
497
+ eventId={eventId}
498
+ onContinue={() => setStep('details')}
499
+ onSelectSeats={() => setStep('seats')}
500
+ />
501
+ </div>
502
+ {step === 'seats' && (
503
+ <>
504
+ <button type="button" onClick={() => setStep('tickets')}>Back to tickets</button>
505
+ <SeatMap eventId={eventId} onContinue={() => setStep('details')} />
506
+ </>
507
+ )}
508
+ {step === 'details' && (
509
+ <BillingInfo
510
+ eventId={eventId}
511
+ requireEmailConfirmation
512
+ collectTicketHolders
513
+ collectHolderContact
514
+ onContinue={() => setStep('payment')}
515
+ />
516
+ )}
517
+ {step === 'payment' && (
518
+ <>
519
+ <AddOns eventId={eventId} />
520
+ <WaiverSigning eventId={eventId} />
521
+ <Payment
522
+ eventId={eventId}
523
+ requireEmailConfirmation
524
+ collectTicketHolders
525
+ requireHolderContact
526
+ onComplete={setOrder}
527
+ />
528
+ </>
529
+ )}
530
+ </main>
531
+ )
532
+ }
533
+
534
+ export function StepCheckout({ eventId }: { eventId: string }) {
535
+ return <CheckoutProvider><StepContents eventId={eventId} /></CheckoutProvider>
536
+ }
537
+ ```
538
+
539
+ For separate routes, keep the provider above the routed pages and connect the
540
+ callbacks to your router. A full page reload is a different session/navigation
541
+ problem: `ResumedCheckout` can attach only to an existing cart in the current
542
+ session, and the old 1.x localStorage state is not a migration format. Handle
543
+ expired reservations, browser Back, buyer changes and unconfirmed payments
544
+ before enabling navigation or a new purchase. Do not unmount or reset an
545
+ in-flight payment merely to move between steps.
546
+
547
+ The step example is a progressive layout: the next step appears after a
548
+ successful callback. A router can replace `setStep`, but the SDK does not
549
+ supply a route-based checkout or implement your browser Back handling. Do not
550
+ assume that a successful TypeScript build verifies route, refresh or payment
551
+ behavior; test those flows with your own backend and providers.
552
+
553
+ ### Accordion checkout
554
+
555
+ The SDK does not export a named accordion component. This host-built layout
556
+ uses native HTML disclosures to group the same SDK sections. The payment field
557
+ stays outside the disclosures so it remains visible while the buyer pays.
558
+ It uses the configuration and `QueryClientProvider` from the quick start.
559
+
560
+ ```tsx
561
+ import { useState } from 'react'
562
+ import {
563
+ AddOns, BillingInfo, CheckoutProvider, Payment, SeatMap,
564
+ Tickets, TimerBanner, WaiverSigning, useCheckoutContext, type PlaceOrderResult,
565
+ } from 'tf-checkout-react'
566
+
567
+ function AccordionContents({ eventId }: { eventId: string }) {
568
+ const checkout = useCheckoutContext()
569
+ const [view, setView] = useState<'tickets' | 'seats' | 'checkout'>('tickets')
570
+ const [order, setOrder] = useState<PlaceOrderResult | null>(null)
571
+
572
+ if (order) return <p role="status">Checking your order status…</p>
573
+
574
+ return (
575
+ <main>
576
+ <div hidden={view !== 'tickets'}>
577
+ <Tickets
578
+ eventId={eventId}
579
+ onContinue={() => setView('checkout')}
580
+ onSelectSeats={() => setView('seats')}
581
+ />
582
+ </div>
583
+ {view === 'seats' && (
584
+ <>
585
+ <button type="button" onClick={() => setView('tickets')}>Back to tickets</button>
586
+ <SeatMap eventId={eventId} onContinue={() => setView('checkout')} />
587
+ </>
588
+ )}
589
+ {view === 'checkout' && (
590
+ <>
591
+ <TimerBanner seconds={checkout.secondsLeft} />
592
+ <details open>
593
+ <summary>Add-ons</summary>
594
+ <AddOns eventId={eventId} />
595
+ </details>
596
+ <details open>
597
+ <summary>Buyer and ticket holders</summary>
598
+ <BillingInfo
599
+ eventId={eventId}
600
+ requireEmailConfirmation
601
+ collectTicketHolders
602
+ collectHolderContact
603
+ />
604
+ </details>
605
+ <details open>
606
+ <summary>Waiver</summary>
607
+ <WaiverSigning eventId={eventId} />
608
+ </details>
609
+ <Payment
610
+ eventId={eventId}
611
+ requireEmailConfirmation
612
+ collectTicketHolders
613
+ requireHolderContact
614
+ onComplete={setOrder}
615
+ />
616
+ </>
617
+ )}
618
+ </main>
619
+ )
620
+ }
621
+
622
+ export function AccordionCheckout({ eventId }: { eventId: string }) {
623
+ return <CheckoutProvider><AccordionContents eventId={eventId} /></CheckoutProvider>
624
+ }
625
+ ```
626
+
627
+ The buyer reserves tickets or seats, then can open or close the form groups.
628
+ Closing a `<details>` element hides its contents without unmounting them. In a
629
+ production accordion, open the relevant group when validation needs the buyer's
630
+ attention, and make sure required add-ons, fields and waivers cannot be missed.
631
+ Do not hide or replace the active payment field during a payment attempt.
632
+ As with the other examples, replace the status placeholder with your own
633
+ verified order-status and support flow.
634
+
635
+ `CheckoutProvider` props are `children` (required), `value?: UseCheckout`,
267
636
  `theme?: CheckoutTheme`, `locale?: LocaleCode`,
268
637
  `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.
638
+ `useCheckoutContext()` inside the provider to read the current checkout and
639
+ `useCheckoutValidation()` to coordinate validation between sections. The
640
+ provider's default checkout includes the installment picker. If supplying your
641
+ own `value`, wire any custom payment ports you need when creating that checkout.
274
642
 
275
643
  ## Checkout lifecycle
276
644
 
@@ -309,6 +677,18 @@ error or closed provider page means the charge failed. A custom provider page mu
309
677
  settle with `{ event: 'dismiss' }` on close/unmount, and other custom payment
310
678
  ports must settle, or a waiting reset cannot finish.
311
679
 
680
+ The beta.26 declaration comments for `reset` and `PAID_ORDER_PENDING_MESSAGE`
681
+ say pressing Pay again finishes an unconfirmed order; the `resetWhenSettled`
682
+ comment also omits retired checkouts as a reason for `false`. Those comments do
683
+ not establish that a retry is safe. Use the session and reconciliation guidance
684
+ above.
685
+
686
+ Stock `Payment` can display an engine message telling the buyer to press Pay
687
+ again when payment is unconfirmed. That wording does not establish that a retry
688
+ is safe. If neutral buyer guidance is required for uncertain outcomes, use a
689
+ custom payment flow with host-controlled error UI, retain the order and reconcile
690
+ the current session and authoritative order/provider status before recovery.
691
+
312
692
  ### Tickets, codes, seats and waiting lists
313
693
 
314
694
  `Tickets` reads the catalog, presents the event's promo/access-code controls and
@@ -354,7 +734,7 @@ holder's answers in `ticketDataCapture`, and add-on answers via
354
734
  the opaque catalog ID. Use backend country/state IDs for `countryId` and
355
735
  `stateId`; `countryCode` / `stateCode` are payment billing codes.
356
736
 
357
- In beta.24 conditional custom-field requiredness is left to the backend and
737
+ In beta.26 conditional custom-field requiredness is left to the backend and
358
738
  not all hosted-checkout conditional visibility is reproduced. Verify the
359
739
  actual event against the deployed checkout, including blanks, images, holder
360
740
  scoping and validation errors; do not claim parity from the presence of a
@@ -362,11 +742,20 @@ field alone. Terms and `additionalConditions` are client-side acceptance gates;
362
742
  respect the server's own requirements. `Payment` renders condition `content`,
363
743
  or `name` as a fallback, as HTML. Only supply trusted or appropriately sanitized
364
744
  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.
745
+
746
+ During cart priming, failed custom-field, checkout-config, add-on or event
747
+ condition reads fall back to empty/default data. The stock UI cannot show or
748
+ gate requirements it did not load. Do not treat an empty list as proof that no
749
+ requirement exists; verify deployment/failure behavior and add tested host
750
+ safeguards for mandatory fields and acknowledgments. Server validation of
751
+ required answers is not proof that a condition was acknowledged. Only failed
752
+ waiver reads other than a 404 are recorded in `unavailableDetails`. Before money
753
+ may have moved, placement retries an unavailable waiver and blocks if it remains
754
+ unknown; `reloadCheckoutDetails()` can also retry the waiver read. A retained
755
+ order with a possible payment follows separate recovery and needs reconciliation.
367
756
  `WaiverSigning` handles the SDK's signing flow.
368
757
 
369
- **Consent limitation in beta.24:** when `privacyPolicy` is supplied, the
758
+ **Consent limitation in beta.26:** when `privacyPolicy` is supplied, the
370
759
  stock `BillingInfo` replaces the Ticket Fairy marketing checkbox with the
371
760
  required privacy checkbox, but both use `consent.ttfOptIn`; the order sends
372
761
  that value as `ttf_opt_in`. The privacy acknowledgement and Ticket Fairy
@@ -406,6 +795,19 @@ for authoritative confirmation before showing a paid receipt or fulfilling.
406
795
  ID verification may still be required after payment. An ID-verification failure
407
796
  is not a reason to repeat the charge.
408
797
 
798
+ The stock in-flow `Confirmation` displays confirmed/ticket-available copy when
799
+ `pending` is absent or false; it does not independently verify final settlement.
800
+ For both paid and free orders, use `confirmationMode="redirect"` with a
801
+ host-owned status page, or a custom composed confirmation gated on authoritative
802
+ order and provider status. Arrange ID verification on the host route when
803
+ `needIdVerification` is true; redirect does not render the inline verification.
804
+ The stock `IDVerification` and routed `OrderIDVerification` verified messages
805
+ say tickets are on their way; they do not check final settlement or issuance.
806
+ Use host-owned neutral verification and status UI when that copy is premature.
807
+ If `onComplete` navigates away or unmounts the checkout, its inline verification
808
+ cannot appear: keep the provider mounted or handle verification at the destination.
809
+ For a custom flow, see [host-owned ID verification](#host-owned-id-verification).
810
+
409
811
  **Zero-total orders:** this SDK ignores the free-registration response body and
410
812
  reports `needIdVerification: false`. Verification or approval may still be
411
813
  required, and `free: true` is not proof tickets were issued. The default inline
@@ -421,7 +823,8 @@ order's payment status before showing its default confirmed/ticket-availability
421
823
  copy. Gate it on an authoritative status appropriate for that page. When
422
824
  `onComplete` reports `pending`, show a pending page (and any voucher) instead;
423
825
  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.
826
+ `Confirmation` component reads the current result and has a pending state, but
827
+ the absence of `pending` is not proof of settlement.
425
828
 
426
829
  `CardInput` renders Stripe Elements and uses the checkout's default card owner;
427
830
  `StripePaymentElement` is the deferred Elements option when its server config
@@ -452,6 +855,24 @@ three only when the provider confirms nothing was charged. An unclassified
452
855
  error is treated as unknown. Avoid automatic blind retries and never promise
453
856
  exactly-once charging across your app, backend and provider.
454
857
 
858
+ ### Host-owned ID verification
859
+
860
+ `runIdVerification(orderHash, options)` does not open the provider on its own.
861
+ Supply `openUrl` to validate the URL against your provider and scheme policy
862
+ and open it in the browser; handle a missing URL or blocked/failed opening in
863
+ your UI. The engine swallows asynchronous `openUrl` rejections, so catch and
864
+ record opening failures inside the callback: polling can otherwise continue
865
+ without a handoff. Use `onPhase` for polling state and handle browser returns;
866
+ `registerForegroundListener` can trigger a recheck on return and must return an
867
+ unsubscribe function.
868
+
869
+ Alternatively, `onRequired` receives the order hash and possibly null URL; if
870
+ it returns `true`, the engine resolves `undefined` without opening or polling,
871
+ and your host owns both steps. A timeout or poll error rejects with a message
872
+ promising that payment is safe and tickets will be emailed. Replace that text
873
+ with neutral status and reconcile authoritative payment, verification and
874
+ issuance. Verification alone does not prove settlement or issuance.
875
+
455
876
  ## Authentication
456
877
 
457
878
  The embedded checkout supplies the event to its built-in `AuthModal`. A
@@ -520,7 +941,7 @@ package are the authoritative detail for callback payloads and DTOs.
520
941
  | `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
942
  | `WaiverSigning` | `eventId`, `className?`. Render before payment for an event requiring a waiver. |
522
943
  | `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. |
944
+ | `OrderSummary` | `eventId?`, `className?`. Uses the current checkout's pricing. `Payment` already renders it; use it separately when you need a summary elsewhere. |
524
945
  | `IDVerification` | `className?`. Uses the current checkout/order. |
525
946
  | `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
947
  | `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. |
@@ -688,7 +1109,7 @@ as the final order amount.
688
1109
  | `setAcceptedConditions(map)`, `acceptCondition(id, accepted)`, `setAddonDataCapture(map)` | Record condition acceptance and add-on answers. |
689
1110
  | `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
1111
  | `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. |
1112
+ | `placeOrder(method?, options?)`, `runIdVerification(orderHash, options?)` | Place/pay; optionally run verification separately with a host URL opener or takeover. See [host-owned ID verification](#host-owned-id-verification). `placeOrder` accepts `autoRunIdVerification`, `idVerification` and `claimCard` options. |
692
1113
  | `applyPromoCode(eventId, code)`, `attributeReferrer(eventId, referrer)`, `joinWaitingList(eventId, entry)` | Helpers return promises. Refetch catalog after a code changes availability. |
693
1114
  | `reset()`, `resetWhenSettled()` | Restart subject to payment-settlement protection; see [lifecycle](#checkout-lifecycle). |
694
1115
  | `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. |
@@ -721,11 +1142,13 @@ for a different gateway.
721
1142
 
722
1143
  The package re-exports the shared core: `ttf` is a configured domain facade,
723
1144
  `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.
1145
+ `createEngine` and `createCheckoutStore` are named exports. Network calls usually
1146
+ return promises; `ttf.events.filterAddonsForTickets` and
1147
+ `ttf.order.mapProfileToBilling` are synchronous helpers. They do not render UI
1148
+ or replace the engine's sequencing, payment ownership and preflight. Use them
1149
+ directly only when your application owns the corresponding workflow. The
1150
+ installed `dist/index.d.ts` includes the complete exported signatures. Avoid
1151
+ importing a private shared-core package.
729
1152
 
730
1153
  ```tsx
731
1154
  import { hooks } from 'tf-checkout-react'
@@ -819,7 +1242,7 @@ existing 1.x integration pinned until the new flow and backend are verified.
819
1242
  There is no automatic translation of old URLs, component props, account
820
1243
  sessions or persisted 1.x state.
821
1244
 
822
- | 1.x integration | 2.0.0-beta.24 approach |
1245
+ | 1.x integration | 2.0.0-beta.26 approach |
823
1246
  | --- | --- |
824
1247
  | `setConfigs({ BASE_URL, ... })` | `configureCheckout({ env` or `baseUrl`, `clientId`, `clientSecret: ''`, ... `})`; public client registration and session setup are required. |
825
1248
  | `TicketsContainer`, `SeatMapContainer`, `AddOnsContainer` | `EmbeddedCheckout` or `Tickets`, `SeatMap`, `AddOns` under `CheckoutProvider`. Adapt callbacks and navigation. |
@@ -832,6 +1255,25 @@ sessions or persisted 1.x state.
832
1255
  | 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
1256
  | 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
1257
 
1258
+ ### Migrating layout callbacks
1259
+
1260
+ A callback name change can also change what the host receives. In particular,
1261
+ `onContinue` does not pass the old callback's response payload:
1262
+
1263
+ | 1.x usage | 2.x replacement |
1264
+ | --- | --- |
1265
+ | `TicketsContainer`'s `getTicketsLabel` | `Tickets`'s `buttonLabel`. |
1266
+ | `TicketsContainer`'s `onAddToCartSuccess(data)` | `Tickets.onContinue()` after reservation; read the current cart through `useCheckoutContext()` under the provider. |
1267
+ | `BillingInfoContainer`'s `handleSubmit(values, helpers, eventId, response)` | `BillingInfo.onContinue()` after validation and save; read the current billing and holders from the checkout context. Omit the callback for combined-form behavior. |
1268
+ | `AddOnsContainer` timer | Render `TimerBanner` with `useCheckoutContext().secondsLeft` in the host layout. |
1269
+ | Separate 1.x page URLs and localStorage state | Keep the 2.x provider mounted across host steps; implement route/refresh handling and verify any existing server cart in the current session. |
1270
+
1271
+ These are migration starting points, not prop-compatible aliases. In 2.x
1272
+ `eventId` is a string; convert a numeric ID before passing it. Old Material UI
1273
+ theme options, callback payloads and optional page behavior need explicit host
1274
+ work. For the two complete flows see [Choose a checkout layout](#choose-a-checkout-layout);
1275
+ for single-page and stepped alternatives see [Compose sections](#compose-sections).
1276
+
835
1277
  Compare the exact event's ticket types, dated pricing, seats, add-ons, holder
836
1278
  questions, conditional fields, consent, waivers, fees, currencies, payment
837
1279
  methods, account and post-purchase behavior before switching. Backend rollout
package/docs/API.md CHANGED
@@ -1,12 +1,16 @@
1
1
  # React checkout API reference
2
2
 
3
3
  This reference describes the public surface of `tf-checkout-react` version
4
- `2.0.0-beta.24`. It supplements the package's npm README with examples and an
4
+ `2.0.0-beta.26`. It supplements the package's npm README with examples and an
5
5
  inventory of the shared facade and hooks. The package's installed
6
6
  `dist/index.d.ts` is the exact versioned source for complete signatures and
7
7
  DTO fields. A named export or a typed payment method does not establish that
8
8
  the matching backend feature, account permission or payment provider is enabled.
9
9
 
10
+ For installation, a complete quick start, and examples of the ready-made,
11
+ single-page and step-by-step layouts, start with the [checkout guide](https://unpkg.com/tf-checkout-react@2.0.0-beta.26/README.md#choose-a-checkout-layout).
12
+ This file is the detailed component, engine and API reference.
13
+
10
14
  All examples assume `configureCheckout` has run, and React Query examples are
11
15
  under a host `QueryClientProvider`. Use a registered public SDK client ID and
12
16
  an empty `clientSecret` in a browser. Never ship a confidential client secret.
@@ -61,8 +65,11 @@ appropriate authorized logout flow where a backend session must also end.
61
65
 
62
66
  `Tickets` and `SeatMap` report when a cart is ready; the host owns navigation
63
67
  when composing individual sections. This skeleton connects seated selection to
64
- its view. It is not a standalone app: configure and mount it under
65
- `QueryClientProvider`.
68
+ its view. This wiring recipe is not a standalone app: configure and mount it
69
+ under `QueryClientProvider`. Its seat Back changes views only; it does not itself
70
+ release held seats or cancel an in-flight reservation or cart priming. Before
71
+ allowing a conflicting selection, coordinate that work and hold expiry or
72
+ release under your deployed backend contract.
66
73
 
67
74
  ```tsx
68
75
  import { useState } from 'react'
@@ -75,15 +82,18 @@ export function CheckoutLayout({ eventId }: { eventId: string }) {
75
82
  const [notice, setNotice] = useState('')
76
83
  return (
77
84
  <CheckoutProvider>
78
- {view === 'tickets' && (
85
+ <div hidden={view !== 'tickets'}>
79
86
  <Tickets
80
87
  eventId={eventId}
81
88
  onSelectSeats={() => setView('seats')}
82
89
  onContinue={() => setView('checkout')}
83
90
  />
84
- )}
91
+ </div>
85
92
  {view === 'seats' && (
86
- <SeatMap eventId={eventId} onContinue={() => setView('checkout')} />
93
+ <>
94
+ <button type="button" onClick={() => setView('tickets')}>Back to tickets</button>
95
+ <SeatMap eventId={eventId} onContinue={() => setView('checkout')} />
96
+ </>
87
97
  )}
88
98
  {view === 'checkout' && (
89
99
  <>
@@ -101,10 +111,11 @@ export function CheckoutLayout({ eventId }: { eventId: string }) {
101
111
  }
102
112
  ```
103
113
 
104
- Real navigation must also handle expiry, payment in flight, retained orders and
105
- buyer/session changes. Do not unconditionally discard or replace a cart on
106
- Back during a payment. Use the embedded flow when its navigation fits; when
107
- building your own, preserve validation and server reservation behavior. The
114
+ Real navigation must also implement checkout Edit/Back, preserve entered
115
+ details, and handle expiry, payment in flight, retained orders and buyer/session
116
+ changes. Do not unconditionally discard or replace a cart on Back during a
117
+ payment. Use `EmbeddedCheckout` for its built-in Back and guarded Edit behavior;
118
+ when building your own, preserve validation and server reservation behavior. The
108
119
  host can use `useCheckoutContext()` inside the provider to read the shared
109
120
  checkout and `useCheckoutValidation()` to coordinate section validation.
110
121
 
@@ -231,8 +242,18 @@ validate on the deployed server too. In this beta the backend owns conditional
231
242
  requiredness and hosted-checkout conditional visibility is not fully reproduced.
232
243
  Test the actual event's branches before release.
233
244
 
245
+ During priming, failed custom-field, checkout-config, add-on or event-condition
246
+ reads fall back to empty/default data and are not recorded in `unavailableDetails`.
247
+ The engine and stock UI cannot gate requirements they did not load. Do not treat
248
+ an empty list as proof that no requirement exists; verify failure behavior and
249
+ implement tested host safeguards for mandatory fields and acknowledgments. Server
250
+ answer validation is not proof that a condition was acknowledged.
251
+
234
252
  If `unavailableDetails` includes `'waiver'`, the read failed without establishing
235
- whether a waiver exists. `reloadCheckoutDetails()` retries it. For a custom
253
+ whether a waiver exists. Before money may have moved, placement retries that
254
+ read and blocks if it remains unknown; `reloadCheckoutDetails()` also retries it.
255
+ A retained order with a possible payment follows separate recovery and needs
256
+ reconciliation. For a custom
236
257
  waiver UI, `holdWaiverSigning(index)` returns a hold or `null`; complete or
237
258
  cancel the upload, attach via `setWaiverSignature(index, signature, hold)` only
238
259
  while the binding remains current, and release the hold. The standard
@@ -258,7 +279,34 @@ If status is unavailable, contact support. Do not retry payment for a free order
258
279
  `OrderConfirmation` does not check payment status before rendering its default
259
280
  confirmed/ticket-availability copy. Gate it on authoritative appropriate status;
260
281
  when the result is pending, show pending UI and any voucher instead. The
261
- in-flow `Confirmation` component reads the current result's pending state.
282
+ in-flow `Confirmation` component reads the current result's pending state but
283
+ shows confirmed copy when `pending` is absent or false, even for paid orders
284
+ whose final settlement has not been independently verified. Use a host-owned
285
+ status route with `confirmationMode="redirect"` or a custom composed
286
+ confirmation gated on authoritative order and provider status. Redirect omits
287
+ inline ID verification; arrange it when `needIdVerification` is true.
288
+ For `EmbeddedCheckout` in redirect mode, provide `onComplete` to route the
289
+ result. `ResumedCheckout` defaults to redirect and likewise needs that callback;
290
+ otherwise its successful flow remains in the loading transition. Inline mode
291
+ has the confirmation and verification-copy limitations described here.
292
+ The stock `IDVerification` and routed `OrderIDVerification` verified messages
293
+ say tickets are on their way but do not verify settlement or issuance. Use a
294
+ host-owned neutral verification and status UI when appropriate. If `onComplete`
295
+ navigates away or unmounts the checkout, inline verification will not appear;
296
+ keep the provider mounted or handle verification at the destination.
297
+
298
+ For custom verification, `runIdVerification(orderHash, options)` does not
299
+ open a URL by itself. Supply `openUrl`, validate the provider URL and open it
300
+ in the browser; handle a missing URL or blocked/failed opening. The engine
301
+ swallows asynchronous `openUrl` rejections, so catch and record failures in
302
+ that callback while showing neutral status. Handle `onPhase` and browser
303
+ returns; `registerForegroundListener` can recheck on return and returns an
304
+ unsubscribe function. Alternatively, return `true` from `onRequired` to take
305
+ over: the engine resolves `undefined` and neither opens nor polls. A timeout or
306
+ poll error rejects with copy promising payment is safe and tickets will be
307
+ emailed; replace it with neutral status and reconcile authoritative payment,
308
+ verification and issuance. Verification alone proves neither settlement nor
309
+ issuance.
262
310
 
263
311
  For a custom Stripe card step, the following demonstrates the boundary between
264
312
  host-collected data and SDK placement. Render it only after reserving a cart and
@@ -350,6 +398,15 @@ no-charge outcomes as `declined`, `cancelled` or `notStarted`, with `unknown`
350
398
  for uncertainty. `UNSUPPORTED_GATEWAY` indicates the necessary platform
351
399
  capability is not available, not that every provider failure is safe to retry.
352
400
 
401
+ The beta.26 declaration comments for `reset` and `PAID_ORDER_PENDING_MESSAGE`
402
+ say pressing Pay again finishes an unconfirmed order; the `resetWhenSettled`
403
+ comment omits retired checkouts as a reason for `false`. Those comments do not
404
+ establish that a retry is safe; check the current session and reconcile first.
405
+ Stock `Payment` can also display an engine message telling the buyer to press
406
+ Pay again when payment is unconfirmed. That wording does not prove a retry is
407
+ safe. If neutral guidance is required, use a custom payment flow with
408
+ host-controlled error UI, retain the order and reconcile before recovery.
409
+
353
410
  ## Auth component contracts
354
411
 
355
412
  `AuthModal` owns a complete modal flow. Supply `open`, `onClose` and, for
@@ -464,6 +521,10 @@ preflight, state and payment coordination. Raw functions are also exported in
464
521
  the `api` namespace. Calling a low-level endpoint directly does not grant a
465
522
  buyer permission or guarantee a safe payment retry.
466
523
 
524
+ `ttf.events.requiredCustomFieldLabels(eventId)` returns `[]` if its lookup
525
+ fails as well as when no matching required order/ticket labels exist. An empty
526
+ result is not proof that the event has no required custom fields.
527
+
467
528
  | Namespace | Methods |
468
529
  | --- | --- |
469
530
  | `ttf.events` | `featured`, `details`, `tickets`, `ticketsWithMeta`, `bundles`, `startBundlePurchase`, `info`, `conditions`, `timeSlotTickets`, `customFields`, `requiredCustomFieldLabels`, `addons`, `filterAddonsForTickets`, `submitPromoCode`, `setCurrency`, `authenticate`, `track`, `updateOrderDataCapture`, `updateTicketDataCapture`. |
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.0.0-beta.24",
2
+ "version": "2.0.0-beta.26",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",