tf-checkout-react-native 2.0.0-beta.23 → 2.0.0-beta.25

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 +896 -366
  2. package/docs/API.md +602 -0
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,133 +1,126 @@
1
- # tf-checkout-react-native
2
-
3
- The Ticket Fairy checkout for React Native. 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, device and payment provider.
6
-
7
- The native and [web](https://www.npmjs.com/package/tf-checkout-react)
8
- packages share a checkout core, with platform-specific UI and payment adapters.
9
- They do not yet have complete hosted-checkout parity.
10
-
11
- The 2.x source is maintained in `tf-shared-libraries/packages/tf-checkout-react-native`;
12
- the source repository requires access.
13
- The shared core is private and bundled into both platform packages. Native host
14
- requirements and peer dependencies are listed below.
15
- Never put a confidential OAuth client secret in browser or mobile code. Public
16
- checkout integrations use registered SDK client IDs and owned buyer sessions.
17
-
18
- ## Install
19
-
20
- This README describes `2.0.0-beta.23`, the paired 2.x prerelease. Pin it
21
- explicitly to use this version. `beta` and `latest` are mutable npm tags, not
22
- guarantees about the highest version; an unqualified install can still resolve
23
- a stable 1.x package. Check the current dist-tags when choosing a version.
24
- Publication does not by itself establish backend rollout, device compatibility
25
- or provider/payment acceptance.
26
-
27
- ```bash
28
- npm view tf-checkout-react-native dist-tags
1
+ # Ticket Fairy Checkout for React Native
2
+
3
+ Native React components and a headless checkout engine for selling Ticket Fairy
4
+ tickets. Start with `EmbeddedCheckout`, compose individual sections, or own the
5
+ presentation using `useCheckout` and the shared API. The package includes its
6
+ private shared core; hosts do not install `tf-checkout-shared`.
7
+
8
+ This reference is for **`tf-checkout-react-native@2.0.0-beta.25`**, a
9
+ prerelease with a different API from 1.x (including its `CheckoutV2`). Pin the
10
+ version: `latest` and `beta` tags can move; check `npm view tf-checkout-react-native dist-tags`.
11
+ The [API reference](https://unpkg.com/tf-checkout-react-native@2.0.0-beta.25/docs/API.md)
12
+ covers components and hooks; the [declarations](https://unpkg.com/tf-checkout-react-native@2.0.0-beta.25/dist/index.d.ts)
13
+ contain all types. The [web package](https://www.npmjs.com/package/tf-checkout-react/v/2.0.0-beta.25)
14
+ shares the engine but has different UI and ports. Availability depends on the
15
+ backend, event, gateway, account and device; installation does not establish
16
+ hosted-checkout parity or payment acceptance.
17
+
18
+ ## Contents
19
+
20
+ - [Requirements and installation](#requirements-and-installation)
21
+ - [Initialize secure storage and configure](#initialize-secure-storage-and-configure)
22
+ - [Embed a checkout](#embed-a-checkout)
23
+ - [Compose sections and navigation](#compose-sections-and-navigation)
24
+ - [Checkout section reference](#checkout-section-reference)
25
+ - [Tickets, seats, codes and attribution](#tickets-seats-codes-and-attribution)
26
+ - [Billing, custom fields and consent](#billing-custom-fields-and-consent)
27
+ - [Payments and safe completion](#payments-and-safe-completion)
28
+ - [Headless checkout](#headless-checkout)
29
+ - [Hooks and API](#hooks-and-api)
30
+ - [Authentication and account components](#authentication-and-account-components)
31
+ - [Theme, primitives and localization](#theme-primitives-and-localization)
32
+ - [Migration from 1.x](#migration-from-1x)
33
+ - [Testing and troubleshooting](#testing-and-troubleshooting)
34
+
35
+ ## Requirements and installation
36
+
37
+ The package declares Node `>=20.19.4`, React `>=19.1`, React Native `>=0.81`,
38
+ `@tanstack/react-query >=5`, `@stripe/stripe-react-native >=0.45.0`,
39
+ `@react-native-clipboard/clipboard >=1.14.0`, `react-native-webview >=13.15.0`,
40
+ and `react-native-render-html >=6.3.4 <7` as host requirements or peers. The
41
+ supported RN 0.81 cohort uses the New Architecture, iOS 15.1+ and Android API
42
+ 24+. Check the native dependencies' own requirements and the versions supported
43
+ by your app; a peer range is not proof that every combination has been tested.
44
+ The package itself declares `axios`, `entities` and `htmlparser2` as runtime
45
+ dependencies. Use one copy of React, React Query and each native module in the
46
+ host. `npm ls react @tanstack/react-query @stripe/stripe-react-native` helps
47
+ identify duplicates.
48
+
49
+ For an Expo project with compatible React and RN versions:
50
+
51
+ ```sh
52
+ npx expo install tf-checkout-react-native@2.0.0-beta.25 \
53
+ @tanstack/react-query @stripe/stripe-react-native \
54
+ @react-native-clipboard/clipboard react-native-webview \
55
+ react-native-render-html@6.3.4 expo-secure-store
29
56
  ```
30
57
 
31
- This version preserves quantities across rotating encrypted ticket IDs using
32
- the API's optional canonical `selectionKey`, while quantity/change APIs and cart
33
- requests retain the latest encrypted IDs. Deploy and verify the canonical
34
- base/dated key and dated numeric context before relying on that behavior:
35
- `display_price`, `price_with_fees`, `fee_included`, `price_currency` and
36
- `price_symbol`. Missing optional keys retain opaque-ID compatibility without a
37
- rotation-retention claim. The deployed API must provide and validate that data.
38
-
39
- Post-purchase upgrade waiting is a separate server-owned contract. Only valid
40
- change-options targets can expose Join or Leave; malformed target IDs, labels
41
- or existing entries are ignored. A sold-out row does not enable it. The backend
42
- must explicitly offer that organizer-configured flow; local receiver validation
43
- does not prove its deployed availability or a completed join.
44
-
45
- For an Expo host with compatible React and React Native versions:
46
-
47
- ```bash
48
- npx expo install tf-checkout-react-native@2.0.0-beta.23 \
49
- @tanstack/react-query \
50
- @stripe/stripe-react-native \
51
- @react-native-clipboard/clipboard \
52
- react-native-webview \
53
- react-native-render-html@6.3.4
54
- ```
55
-
56
- Bare React Native:
57
-
58
- ```bash
59
- npm install tf-checkout-react-native@2.0.0-beta.23 @tanstack/react-query @stripe/stripe-react-native @react-native-clipboard/clipboard react-native-webview react-native-render-html@6.3.4
60
- ```
61
-
62
- These packages, React and React Native are **peer dependencies**. Install
63
- compatible versions in the host and ensure the SDK and host resolve the same
64
- React, React Query and native-module instances. Check your dependency tree and
65
- native build; declaring peers does not itself prevent duplicates.
66
-
67
- The private shared core and native UI are bundled; npm installs the package's
68
- declared runtime dependencies separately.
69
-
70
- This version requires the `react-native-render-html` peer `>=6.3.4 <7`
71
- for formatted ticket, add-on, bundle and accommodation descriptions and its
72
- public `RichText` renderer. Development and component tests use `6.3.4`; the
73
- install commands above pin that compatible version. Verify the rendering and
74
- link behavior needed by your host on its supported devices.
75
-
76
- ## Requirements
77
-
78
- - React Native `0.81+` with the New Architecture enabled (the supported build cohort)
79
- - React `19.1+`, Node `20.19.4+`
80
- - iOS 15.1+ · Android API 24+ for the supported React Native 0.81 cohort
81
- - A development build is recommended for this SDK and real wallet
82
- configuration. Expo Go compatibility for the complete checkout is not
83
- established.
84
-
85
- Add the Stripe plugin to `app.json`:
58
+ The last package is used by the storage example below; the SDK does not install
59
+ or require Expo SecureStore. Build a development client containing the native
60
+ modules. Complete checkout compatibility with Expo Go is not established. Add
61
+ the Stripe config plugin to the app configuration, choosing wallet settings for
62
+ your own app, and rebuild after changing native configuration. For example,
63
+ this starts without Google Pay:
86
64
 
87
65
  ```json
88
66
  {
89
67
  "expo": {
68
+ "scheme": "myapp",
90
69
  "plugins": [["@stripe/stripe-react-native", { "enableGooglePay": false }]]
91
70
  }
92
71
  }
93
72
  ```
94
73
 
95
- Then build a dev client once:
74
+ Run `npx expo run:ios` or `npx expo run:android`, or make a development build
75
+ using your normal process. For a bare React Native app, install the peers in
76
+ the app (React and React Native are normally already present):
96
77
 
97
- ```bash
98
- npx expo run:ios
78
+ ```sh
79
+ npm install tf-checkout-react-native@2.0.0-beta.25 @tanstack/react-query \
80
+ @stripe/stripe-react-native @react-native-clipboard/clipboard \
81
+ react-native-webview react-native-render-html@6.3.4
99
82
  ```
100
83
 
101
- ## Use it
102
-
103
- Native `configureCheckout` and the root `configure` alias require a host-owned
104
- synchronous `StorageAdapter`. The SDK does not create a persistent MMKV store
105
- or verify that the host's adapter is encrypted. Restore an OS-protected adapter
106
- before configuring checkout; migrate old credentials securely or require a new
107
- sign-in. Use a separate namespace per environment.
108
-
109
- The adapter has synchronous `getItem(key)`, `setItem(key, value)` and
110
- `removeItem(key)` methods. `getItem` returns a string, `null` or `undefined`.
111
- An adapter that queues asynchronous persistence may also expose `flush():
112
- Promise<void>`; the host must await it after credential-changing operations
113
- before reporting durable success. The built-in auth completion does not await
114
- that final flush automatically. Never pass async functions as the synchronous
115
- methods or mount SDK hooks before restoring an asynchronous mirror.
116
-
117
- ### Expo storage example
118
-
119
- The following host-owned example restores one Keychain/Keystore-backed entry
120
- before setup, then writes the next snapshot synchronously before updating its
121
- in-memory mirror. Install it in an Expo development build with
122
- `npx expo install expo-secure-store`:
84
+ Configure autolinking and Stripe's native setup, run `pod install` in the iOS
85
+ directory, and rebuild both platforms. Provide a Keychain/Keystore-backed
86
+ storage adapter using a native library appropriate to the host. Consult the
87
+ [Stripe React Native setup](https://docs.stripe.com/payments/accept-a-payment?platform=react-native)
88
+ and [Expo SecureStore documentation](https://docs.expo.dev/versions/latest/sdk/securestore/)
89
+ for native configuration and platform behavior. Configure wallet entitlements,
90
+ merchant registration and deep links separately if those features are used.
91
+
92
+ ## Initialize secure storage and configure
93
+
94
+ `configureCheckout(config)` is synchronous and must run **after credential
95
+ storage is restored**, before any SDK API call, hook or component mounts. Its
96
+ root export alias `configure` has the same native requirements. Configuration
97
+ is app-wide. Set up one environment and buyer session at a time; avoid
98
+ reconfiguring during checkout or payment.
99
+
100
+ `storage` is required. It has synchronous `getItem(key): string | null |
101
+ undefined`, `setItem(key, value): void`, `removeItem(key): void`, and optional
102
+ `flush(): Promise<void>`. The wrapper checks method shape; it does not certify
103
+ encryption, persistence, isolation, backup or correctness. `createMemoryStorage`
104
+ is exported for tests and loses credentials at process exit; it is not a
105
+ production secure store. Use separate storage namespaces per app/environment.
106
+ Never put a confidential OAuth client secret or provider secret key in mobile
107
+ code. Obtain a public SDK client ID authorized for the integration and use an
108
+ empty `clientSecret` for public checkout; do not substitute a confidential
109
+ credential. Environment variables compiled into an app are visible to its users.
110
+
111
+ The SDK reads storage synchronously. The following Expo example asynchronously
112
+ restores one protected snapshot first, then makes each subsequent OS write
113
+ synchronously *before* publishing the new in-memory snapshot. The methods are
114
+ not `async` and a failed write throws instead of reporting success. Replace the
115
+ namespace with an app-owned, environment-specific key.
123
116
 
124
117
  ```ts
125
118
  // credentialStorage.ts
126
119
  import * as SecureStore from 'expo-secure-store'
127
120
  import type { StorageAdapter } from 'tf-checkout-react-native'
128
121
 
129
- export async function restoreCredentialStorage(namespace: string): Promise<StorageAdapter> {
130
- const raw = await SecureStore.getItemAsync(namespace)
122
+ export async function restoreCredentialStorage(key: string): Promise<StorageAdapter> {
123
+ const raw = await SecureStore.getItemAsync(key)
131
124
  const parsed: unknown = raw === null ? {} : JSON.parse(raw)
132
125
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) ||
133
126
  Object.values(parsed).some(value => typeof value !== 'string')) {
@@ -135,331 +128,868 @@ export async function restoreCredentialStorage(namespace: string): Promise<Stora
135
128
  }
136
129
  let values = new Map(Object.entries(parsed as Record<string, string>))
137
130
  const commit = (next: Map<string, string>) => {
138
- SecureStore.setItem(namespace, JSON.stringify(Object.fromEntries(next)), {
131
+ SecureStore.setItem(key, JSON.stringify(Object.fromEntries(next)), {
139
132
  keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
140
133
  requireAuthentication: false,
141
134
  })
142
135
  values = next
143
136
  }
144
137
  return {
145
- getItem: key => values.get(key) ?? null,
146
- setItem: (key, value) => { const next = new Map(values); next.set(key, value); commit(next) },
147
- removeItem: key => { const next = new Map(values); next.delete(key); commit(next) },
138
+ getItem: name => values.get(name) ?? null,
139
+ setItem: (name, value) => { const next = new Map(values); next.set(name, value); commit(next) },
140
+ removeItem: name => { const next = new Map(values); next.delete(name); commit(next) },
148
141
  }
149
142
  }
150
143
  ```
151
144
 
152
- This example does not request biometric authentication. Secure storage has
153
- platform-specific size and backup behavior; configure your host's backup policy
154
- and handle restoration/write failures. Synchronous writes can block the JS
155
- thread. See the public [Expo SecureStore documentation](https://docs.expo.dev/versions/latest/sdk/securestore/).
145
+ This illustrative adapter does not request biometric authentication. Account
146
+ for OS storage size limits, backup/restore policy, device locking and write
147
+ failures in the host; synchronous storage can block the JS thread. If your
148
+ persistence API is asynchronous, hydrate a synchronous mirror before setup,
149
+ serialize its writes, implement `flush`, and await the final flush after every
150
+ credential-changing operation before telling the user that persistence
151
+ succeeded. Built-in auth completion does not await that final flush for you.
152
+ Do not pass promises or async functions as the three synchronous methods. A
153
+ corrupt snapshot or unavailable OS store must be handled explicitly; do not
154
+ silently report durable sign-in. Migrate old credentials securely or require
155
+ sign-in again.
156
156
 
157
- ```tsx
158
- import { useEffect, useState } from 'react'
159
- import { Button, Text, View } from 'react-native'
160
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
161
- import { configureCheckout, EmbeddedCheckout } from 'tf-checkout-react-native'
157
+ ```ts
158
+ // checkoutSetup.ts
159
+ import { configureCheckout } from 'tf-checkout-react-native'
162
160
  import { restoreCredentialStorage } from './credentialStorage'
163
161
 
164
- // Share initialization across repeated effects and permit retry after failure.
165
162
  let initialization: Promise<void> | undefined
166
- function prepareCheckout(): Promise<void> {
163
+ export function prepareCheckout(): Promise<void> {
167
164
  if (!initialization) {
168
165
  initialization = (async () => {
169
166
  const storage = await restoreCredentialStorage('my_app_checkout_prod')
170
- configureCheckout({ env: 'PROD', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret: '', storage })
167
+ configureCheckout({
168
+ env: 'PROD',
169
+ clientId: 'YOUR_PUBLIC_SDK_CLIENT_ID',
170
+ clientSecret: '',
171
+ storage,
172
+ })
171
173
  })().catch(error => { initialization = undefined; throw error })
172
174
  }
173
175
  return initialization
174
176
  }
177
+ ```
175
178
 
176
- const queryClient = new QueryClient()
179
+ Configuration fields:
180
+
181
+ | Field | Meaning |
182
+ | --- | --- |
183
+ | `clientId`, `clientSecret` | Required strings. Public checkout uses a registered public SDK ID and empty secret. |
184
+ | `storage` | Required host-owned synchronous `StorageAdapter`, restored before setup. |
185
+ | `env` | `PROD` (default, `www.ticketfairy.com`), `STAG` (`test.ticketfairy.com`) or `DEV` (`sandbox.ticketfairy.com`). |
186
+ | `baseUrl` | Optional host origin overriding `env`; the SDK appends `/api`. Use only a trusted host. |
187
+ | `sourceOrigin` | Optional `X-Source-Origin` header value. |
188
+ | `auth` | Optional synchronous callback returning additional request headers. Treat credentials in headers as sensitive; the native wrapper also forces header-only session mode and no-store/no-cache. |
189
+ | `cookieAuth` | Defaults to `true` on native. The native wrapper forces `withCredentials: false`; its header-backed session is stored via the adapter, rather than relying on ambient OS cookies. |
190
+ | `accountAuth` | Opt in only for an authorized first-party full-account journey and deployed backend. Defaults off. |
191
+ | `browserAuth`, `sdkCheckoutBrowserAuth` | Optional platform ports for supported browser authentication. The host owns secure proof generation and browser/callback handling. |
192
+ | `sdkCheckoutPhoneAuth` | Opt in only when the corresponding first-party checkout phone journey is deployed. |
193
+
194
+ A restored session is still validated by the backend. Public checkout sign-in
195
+ preserves the event-bound guest cart and keeps the checkout buyer credential
196
+ server-side; it does not return a full-account ticket or grant private account
197
+ access. Do not infer full account authorization from checkout authentication.
198
+
199
+ ## Embed a checkout
200
+
201
+ Mount only after `prepareCheckout` resolves. `EmbeddedCheckout` provides its own
202
+ `CheckoutProvider` and React Query client. Any separate React Query hooks outside
203
+ that subtree need a `QueryClientProvider` of their own.
177
204
 
178
- export default function App() {
179
- const [ready, setReady] = useState(false)
180
- const [failed, setFailed] = useState(false)
205
+ ```tsx
206
+ import React, { useEffect, useState } from 'react'
207
+ import { Button, Text, View } from 'react-native'
208
+ import { EmbeddedCheckout, type PlaceOrderResult } from 'tf-checkout-react-native'
209
+ import { prepareCheckout } from './checkoutSetup'
210
+
211
+ export default function EventCheckout() {
212
+ const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading')
181
213
  const [attempt, setAttempt] = useState(0)
214
+ const [order, setOrder] = useState<PlaceOrderResult | null>(null)
182
215
  useEffect(() => {
183
216
  let active = true
184
- prepareCheckout().then(() => { if (active) setReady(true) })
185
- .catch(() => { if (active) setFailed(true) })
217
+ prepareCheckout().then(() => { if (active) setStatus('ready') })
218
+ .catch(() => { if (active) setStatus('error') })
186
219
  return () => { active = false }
187
220
  }, [attempt])
188
- if (failed) return <View><Text>Checkout setup failed.</Text><Button title="Try again" onPress={() => { setFailed(false); setAttempt(value => value + 1) }} /></View>
189
- if (!ready) return <Text>Getting checkout ready…</Text>
190
- return (
191
- <QueryClientProvider client={queryClient}>
192
- <EmbeddedCheckout eventId="14304" />
193
- </QueryClientProvider>
221
+
222
+ if (status === 'error') return (
223
+ <View><Text>Could not prepare checkout.</Text>
224
+ <Button title="Try again" onPress={() => { setStatus('loading'); setAttempt(n => n + 1) }} />
225
+ </View>
226
+ )
227
+ if (status !== 'ready') return <Text>Preparing checkout…</Text>
228
+
229
+ if (order) return (
230
+ <View>
231
+ <Text>Order submitted: {order.orderHash}. Check payment status before fulfillment.</Text>
232
+ {order.pending && <Text>Payment pending.</Text>}
233
+ {order.pendingVoucherUrl && <Text selectable>{order.pendingVoucherUrl}</Text>}
234
+ {order.needIdVerification && <Text>Identity verification is required.</Text>}
235
+ {order.free && <Text>Check if registration needs approval or verification.</Text>}
236
+ </View>
194
237
  )
238
+ return <EmbeddedCheckout eventId="YOUR_EVENT_ID" confirmationMode="redirect"
239
+ onComplete={setOrder} />
195
240
  }
196
241
  ```
197
242
 
198
- The SDK adds no Expo storage peer. Bare React Native hosts can implement the
199
- same contract with their own OS-protected storage. The backend still validates
200
- restored sessions; setup checks adapter shape but does not prove its security.
243
+ `onComplete` is an order result, not proof of settled payment. `pending: true`
244
+ requires pending instructions, but `pending !== true` also does not prove
245
+ capture or settlement. `confirmationMode="inline"` (default) shows the stock
246
+ receipt, which can say tickets are confirmed for a non-pending order before
247
+ final capture/settlement. Hosts requiring verified paid status should use
248
+ `"redirect"` or custom UI and reconcile with the authoritative backend and
249
+ provider before presenting that status or fulfilling. Redirect mode delegates
250
+ routing to `onComplete` and keeps checkout mounted during handoff; the host owns
251
+ navigation. The example shows a local fallback, not a complete status screen:
252
+ the host must implement reconciliation, retain the order reference and voucher
253
+ across its navigation/restart, and arrange verification when required. Validate
254
+ provider URLs against the host's policy before opening them.
255
+
256
+ ### `EmbeddedCheckout` props
257
+
258
+ | Prop | Default | Purpose |
259
+ | --- | --- | --- |
260
+ | `eventId: string` | required | Event to sell. |
261
+ | `onComplete?: (order: PlaceOrderResult) => void` | — | Reports ticket or bundle result; reconcile payment status, even if not marked pending. |
262
+ | `confirmationMode?: 'inline' \| 'redirect'` | `inline` | Show receipt inline or let host route. |
263
+ | `confirmationChildren?: ReactNode` | — | Content inside the inline ticket receipt, e.g. `Rewards`. |
264
+ | `initialView?: 'tickets' \| 'seats'` | `tickets` | Initial selection screen. |
265
+ | `onViewChange?: (view) => void` | — | Reports `tickets`, `seats` or `checkout`. |
266
+ | `requireEmailConfirmation?: boolean` | `true` | Require matching guest email confirmation. |
267
+ | `collectTicketHolders?: boolean` | `true` | Collect one named holder per ticket. |
268
+ | `collectHolderContact?: boolean` | `true` | Also collect holder email/phone when holder details are shown. |
269
+ | `skipBillingWhenProfileComplete?: boolean` | `false` | May hide billing for a complete profile; see the privacy-policy interaction below. |
270
+ | `onLogin?: (email?: string) => void` | — | Use host login instead of the built-in modal. |
271
+ | `auth?: false \| object` | built-in | `false` removes built-in auth; otherwise supports `allowRegistration`, `allowPasswordReset`, `requireRegistrationAddress`, `logoUrl`, `onCheckoutAuthenticated` and `onAuthenticated`. `onLogin` takes precedence. |
272
+ | `privacyPolicy?: CheckoutPrivacyPolicy` | — | Host policy: `{ url, brandName, linkLabel? }`; requires acknowledgment. |
273
+ | `additionalConditions?: EventCondition[]` | `[]` | Extra required/optional acknowledgment blocks. |
274
+ | `ttfOptInLabel`, `brandOptInLabel` | SDK copy / absent | Label overrides; `privacyPolicy` reuses the Ticket Fairy opt-in value. See consent limitations below. |
275
+ | `initialPromoCode?: string`, `referrer?: string \| number` | — | Explicit attribution supplied by the host. |
276
+ | `readAttributionFromUrl?: boolean` | accepted, no-op | Native has no browser URL to read. |
277
+ | `pickImage?: CheckoutImagePicker` | — | Host OS image picker for applicable questions. |
278
+ | `showTestCardHint?: boolean` | `false` | Sandbox demo copy only; keep off in production. |
279
+ | `theme?: CheckoutTheme` | light tokens | Partial token overrides. |
280
+
281
+ `pickImage` is `() => Promise<{ base64: string; width?: number; height?: number } | null>`.
282
+ Return raw base64, not a data URI, or `null` for cancellation. The SDK checks
283
+ base64 encoding and the image signature, the 2 MiB maximum and any supplied
284
+ dimensions (at most 4096 pixels per side); the server validates decoded bytes.
285
+ The host owns permission and picker UX. For the separate event discussion picker, see its props below.
286
+
287
+ ## Compose sections and navigation
288
+
289
+ `CheckoutProvider` owns a native `useCheckout()` instance, a query provider,
290
+ theme and the built-in provider-page and installment modals. Sections inside
291
+ it share the app checkout store. `useCheckoutContext()` reads this instance and
292
+ throws outside the provider. The following intentionally uses one combined
293
+ billing/payment form after reservation; callbacks route tickets and seats.
201
294
 
202
- ### Checkout sessions and account scope
203
-
204
- The native wrapper defaults `cookieAuth` to true and disables ambient native
205
- cookie handling. Owned credentials use the SDK's header-backed session storage.
206
- Public checkout sign-in preserves the event-bound guest carrier and keeps its
207
- buyer credential server-side; it does not return a full-account ticket or grant
208
- private account access. First-party full-account flows explicitly use
209
- `accountAuth: true` when the host is authorized for that scope.
295
+ ```tsx
296
+ import React, { useState } from 'react'
297
+ import { ScrollView, Text, View } from 'react-native'
298
+ import {
299
+ CheckoutProvider, Tickets, SeatMap, AddOns, BillingInfo, WaiverSigning,
300
+ OrderSummary, Payment, IDVerification, useCheckoutContext,
301
+ type PlaceOrderResult,
302
+ } from 'tf-checkout-react-native'
210
303
 
211
- ### Environments
304
+ function Flow({ eventId, onComplete }: {
305
+ eventId: string
306
+ onComplete: (order: PlaceOrderResult) => void
307
+ }) {
308
+ const [view, setView] = useState<'tickets' | 'seats' | 'checkout'>('tickets')
309
+ const checkout = useCheckoutContext()
310
+ if (checkout.status === 'succeeded' && checkout.order) return (
311
+ <View>
312
+ <Text>Order submitted: {checkout.order.orderHash}. Check payment status.</Text>
313
+ {checkout.order.pending && <Text>Payment pending.</Text>}
314
+ {checkout.order.pendingVoucherUrl && <Text selectable>{checkout.order.pendingVoucherUrl}</Text>}
315
+ <IDVerification />
316
+ </View>
317
+ )
318
+ return (
319
+ <ScrollView keyboardShouldPersistTaps="handled">
320
+ {view === 'tickets' && <Tickets eventId={eventId}
321
+ onSelectSeats={() => setView('seats')}
322
+ onContinue={() => setView('checkout')} />}
323
+ {view === 'seats' && <SeatMap eventId={eventId}
324
+ onContinue={() => setView('checkout')} />}
325
+ {view === 'checkout' && <>
326
+ <AddOns eventId={eventId} />
327
+ <BillingInfo eventId={eventId} requireEmailConfirmation
328
+ collectTicketHolders collectHolderContact />
329
+ <WaiverSigning eventId={eventId} />
330
+ <OrderSummary eventId={eventId} />
331
+ <Payment requireEmailConfirmation collectTicketHolders requireHolderContact
332
+ onComplete={onComplete} />
333
+ </>}
334
+ </ScrollView>
335
+ )
336
+ }
212
337
 
213
- ```ts
214
- export async function prepareStagingCheckout() {
215
- const storage = await restoreCredentialStorage('my_app_checkout_stag')
216
- configureCheckout({
217
- env: 'STAG',
218
- clientId: 'PUBLIC_SDK_CLIENT_ID',
219
- clientSecret: '',
220
- storage,
221
- })
338
+ export function ComposedCheckout({ eventId, onComplete }: {
339
+ eventId: string
340
+ onComplete: (order: PlaceOrderResult) => void
341
+ }) {
342
+ return <CheckoutProvider><Flow eventId={eventId} onComplete={onComplete} /></CheckoutProvider>
222
343
  }
223
344
  ```
224
345
 
225
- `PROD` → `www.ticketfairy.com`, `STAG` → `test.ticketfairy.com`, `DEV` →
226
- `sandbox.ticketfairy.com`. Pass `baseUrl` to point elsewhere.
227
-
228
- ## `<EmbeddedCheckout />`
229
-
230
- | Prop | Type | Default | Does |
231
- | ----------------------------------- | ----------------------------------- | ----------- | --------------------------------------------- |
232
- | `eventId` | `string` | — | The event to sell. Required. |
233
- | `onComplete` | `(order: PlaceOrderResult) => void` | — | Receives an order result, possibly pending. |
234
- | `confirmationMode` | `'inline' \| 'redirect'` | `'inline'` | `redirect` leaves routing to `onComplete`. |
235
- | `initialView` | `'tickets' \| 'seats'` | `'tickets'` | Opening screen. |
236
- | `onViewChange` | `(view) => void` | — | Reports the visible view. |
237
- | `requireEmailConfirmation` | `boolean` | `true` | Make guests repeat their email. |
238
- | `collectTicketHolders` | `boolean` | `true` | One named holder per ticket. |
239
- | `collectHolderContact` | `boolean` | `true` | Also collect holder email/phone. |
240
- | `skipBillingWhenProfileComplete` | `boolean` | `false` | Skip billing for complete signed-in profiles. |
241
- | `auth` | `EmbeddedCheckoutProps['auth']` | built-in | Configure or remove the login modal. |
242
- | `onLogin` | `(email?: string) => void` | — | Open your own login UI instead. |
243
- | `privacyPolicy` | `CheckoutPrivacyPolicy` | — | Your privacy policy link and brand name. |
244
- | `additionalConditions` | `EventCondition[]` | — | Extra acknowledgements. |
245
- | `ttfOptInLabel` / `brandOptInLabel` | `string` | — | Override the marketing opt-in copy. |
246
- | `initialPromoCode` | `string` | — | Apply a promo code on load. |
247
- | `referrer` | `string \| number` | — | Attribute the sale. |
248
- | `showTestCardHint` | `boolean` | `false` | Show sandbox card copy. Demos only. |
249
- | `theme` | `CheckoutTheme` | — | Recolor via design tokens. |
250
-
251
- `readAttributionFromUrl` is accepted for parity with web and is a no-op —
252
- there is no `window.location` on a device.
253
-
254
- Native hosts must resolve and supply any supported promo or referral explicitly.
255
- An ambiguous numeric `?r=` can represent either; do not send it as a referral
256
- without authoritative classification. This beta also leaves conditional custom
257
- field requiredness to the backend and does not reproduce every hosted-checkout
258
- conditional visibility rule. Verify the event's field behavior before relying
259
- on parity.
260
-
261
- ## Theming
346
+ `AddOns` can render nothing when there are no applicable items; do not make a
347
+ navigation transition depend solely on its `onContinue` unless your host handles
348
+ that case. With no `onContinue`, `AddOns` and `BillingInfo` use combined-form
349
+ behavior. Match `BillingInfo`'s email/holder policy to `Payment`'s
350
+ `requireEmailConfirmation`, `collectTicketHolders` and `requireHolderContact`.
351
+ The composed example renders the verification control for a required order;
352
+ the buyer must press Verify. Before changing event, leaving payment or starting
353
+ another cart, handle in-flight and retained orders as described under
354
+ [safe completion](#payments-and-safe-completion).
355
+
356
+ `CheckoutProvider` props: `children` (required), `theme`, `pickImage`,
357
+ `options` (wallet/redirect `CheckoutOptions`), `paymentSheet`, `queryClient`,
358
+ and `value`. Omit `value` to let the provider create the native hook, provider
359
+ page and installment picker. If supplying `value`, create it with the native
360
+ `useCheckout` and provide any necessary ports yourself; `options` and
361
+ `paymentSheet` on a controlled provider do not configure that value. Keep the
362
+ choice of controlled versus self-provided stable for the mount. A custom query
363
+ client can be supplied with `queryClient`. `CheckoutThemeProvider` supplies only
364
+ theme, not checkout state or a query client.
365
+
366
+ ## Checkout section reference
367
+
368
+ Unless noted, these sections read checkout state from `CheckoutProvider` and
369
+ are imported from `tf-checkout-react-native`. All props are optional except
370
+ those marked **required**. Prop interfaces are represented on the component in
371
+ the published declarations; not every `*Props` name is itself a top-level type
372
+ export. Derive one if needed with `React.ComponentProps<typeof Tickets>`.
373
+
374
+ ### `Tickets`
375
+
376
+ Loads the catalog, quantities, promo/access-code and waiting-list controls,
377
+ currency and time slots when offered. Reserves/prices the cart before calling
378
+ `onContinue`.
379
+
380
+ | Prop | Description |
381
+ | --- | --- |
382
+ | `eventId: string` | **Required** event ID. |
383
+ | `onContinue?: () => void` | Cart was primed; reveal the next screen. |
384
+ | `onSelectSeats?: () => void` | Show a seat map for seated events. Without it the seat CTA is hidden. |
385
+ | `title?: string \| false` | Catalog heading; default `false`. |
386
+ | `buttonLabel?: string` | Reserve CTA; default `Get tickets`. |
387
+ | `timeSlotDates?: string[]` | Force time-slot selection with ISO `yyyy-mm-dd` dates; omit to use event data. |
388
+ | `initialPromoCode?: string`, `referrer?: string \| number` | Apply/attribute once on mount. |
389
+ | `readAttributionFromUrl?: boolean` | Accepted for web parity; does nothing on native. |
390
+ | `unstyled?: boolean` | Place catalog in the host's own container. |
391
+ | `renderTicket?: (row) => ReactNode` | Replace row visuals. `row` has `ticket`, `quantity`, `range: {min,max,step}`, `unavailable`, `seated`, `onQuantityChange`. |
392
+ | `renderContinue?: (cta) => ReactNode` | Replace CTA; `cta` has `onPress`, `disabled`, `loading`, `title`. |
393
+
394
+ The render callbacks retain SDK selection/reservation logic. Preserve the
395
+ provided disabled/loading state and accessible labels in custom UI.
396
+
397
+ ### `SeatMap`, `AddOns`, `Bundles`, `Accommodation`
398
+
399
+ | Component | Props and behavior |
400
+ | --- | --- |
401
+ | `SeatMap` | **`eventId`**; `onContinue?` after a seat cart is primed. Internal or Seats.io maps render through WebView. The event and backend must support the chart, hold and cart contracts. |
402
+ | `AddOns` | **`eventId`**; `onContinue?`, `onSkip?`, `buttonLabel?`, `eyebrow?`, `heading?`. Saves quantities and add-on answers; skip is hidden when a required add-on exists. It may render nothing. |
403
+ | `Bundles` | **`eventId`**; `onPurchased?(orderHash)`, `onComplete?(order)`, `confirmationMode?: 'inline' \| 'redirect'`, `visible?`, `heading?`, `locale?`, `messages?`, `translate?`. Bundles use their own purchase/order flow, not the ticket cart. Set `visible={false}` while hidden so its card field is not mounted. |
404
+ | `Accommodation` | **`eventId`**. Composes stay selection, quote and reservation state for supported events. Availability and authoritative prices come from the server. |
405
+
406
+ ### `BillingInfo`
407
+
408
+ Collects buyer, order-level and ticket-holder answers, then saves them into the
409
+ shared checkout. With `onContinue`, it has a continue button; without it, edits
410
+ are committed in the combined form. It uses event gates and may show additional
411
+ required fields. All props are optional:
412
+
413
+ | Prop | Description |
414
+ | --- | --- |
415
+ | `eventId?: string` | Event context for country defaults. |
416
+ | `onContinue?: () => void` | Called after valid billing is saved. |
417
+ | `heading?`, `buttonLabel?` | Copy overrides. |
418
+ | `onLogin?: (email?: string) => void` | Offer sign-in, including an email already associated with an account. |
419
+ | `skipWhenProfileComplete?: boolean` | May hide the card for a complete signed-in profile; default `false`. Do not combine with `privacyPolicy` without a verified workaround. |
420
+ | `requireEmailConfirmation?: boolean` | Require guest email confirmation; standalone default `false`. |
421
+ | `collectTicketHolders?: boolean` | Require named holders beyond event gates; standalone default `false`. |
422
+ | `collectHolderContact?: boolean` | Collect holder email and phone when holders are shown; standalone default `false`. |
423
+ | `ttfOptInLabel?`, `brandOptInLabel?` | Label overrides; privacy and Ticket Fairy opt-in share a value when `privacyPolicy` is supplied. |
424
+ | `privacyPolicy?: { url: string; brandName: string; linkLabel?: string }` | Host policy and required acknowledgment. |
425
+ | `showPrivacyError?: boolean` | Show inline privacy error after a failed attempt. |
426
+ | `step?: ReactNode` | Accepted for API parity but not rendered by native `SectionHeader`. |
427
+
428
+ ### `OrderSummary`, `WaiverSigning`, `Payment`, `IDVerification`, `Confirmation`
429
+
430
+ | Component | Props and behavior |
431
+ | --- | --- |
432
+ | `OrderSummary` | `eventId?`, `locale?`, `messages?`, `translate?`. Displays server-priced cart totals and optional event name. |
433
+ | `WaiverSigning` | **`eventId`**; `locale?`, `messages?`, `translate?`. Renders only when the event has a waiver; supports the server-offered signing methods. |
434
+ | `Payment` | `onComplete?(order)`, `eventId?` (currently unused by native payment), `requireEmailConfirmation?`, `collectTicketHolders?`, `requireHolderContact?`, `additionalConditions?`, `requirePrivacyPolicy?`, `onPrivacyRequired?`, `showTestCardHint?`, `locale?`, `messages?`, `translate?`. Standalone boolean defaults are false. It validates, renders eligible methods and submits. |
435
+ | `IDVerification` | No props. When required, it shows a Verify button; the buyer must press it to open and poll the provider. A stall proves neither failure nor settlement. |
436
+ | `Confirmation` | `shareUrl?`, `shareMessage?`, `viewOrderUrl?`, `message?`, `children?`. Reads the current order. Its stock title and share copy do not independently verify payment settlement or referral eligibility; see below. |
437
+
438
+ `Payment` gates its rendered event conditions, host `additionalConditions` and
439
+ requested privacy acknowledgment in its UI. Each `EventCondition` is
440
+ `{ id: string, name: string, content: string, isRequired: boolean }`. These
441
+ acknowledgments are client-side and are not sent as separate server fields.
442
+ Conditions use native `RichText`, which filters tags/styles and opens only
443
+ HTTP(S) links; its rendering is not browser HTML parity. A headless
444
+ `placeOrder` checks loaded event conditions in the engine, but does not receive
445
+ host `additionalConditions` or enforce the UI privacy gate. Headless hosts must
446
+ implement and test their own required host acknowledgments before placement.
447
+
448
+ `Rewards` renders order-scoped rewards, a referral tree, ladder, packs and a
449
+ native share sheet. Pass `orderHash` or an already loaded `data` payload; it also
450
+ accepts `locale`, `messages`, `translate`. Embed it as a `Confirmation` child,
451
+ or use `confirmationChildren` on `EmbeddedCheckout`. `RewardsTree` accepts
452
+ **`tree`** and optional `size` (default 260); `RewardsLadder` accepts **`rows`**,
453
+ **`translate`** and optional async `onSubmitDetails(awardId, details)`.
454
+ Account-level `AccountRewardsSection` has separate read-only account semantics.
455
+ `Rewards` uses the server's `share_link` and hides itself when
456
+ `referrals_enabled` is explicitly false. Verify the returned eligibility and
457
+ buyer-specific link against your deployed contract. Standalone `Confirmation`
458
+ displays reward-promising share copy for any non-pending order when `shareUrl`
459
+ is supplied, without checking eligibility or generating a buyer-specific link.
460
+ Omit it unless your host has verified both; `EmbeddedCheckout` does not expose a
461
+ `shareUrl` prop. The example below omits that block and assumes the host has
462
+ already verified the status required to show confirmed receipt copy, and the
463
+ original result was not pending. `Confirmation` reads the original
464
+ `checkout.order.pending`; it does not refresh that flag after later settlement.
465
+ Use a host status UI based on a fresh authoritative read for an asynchronously
466
+ settled order.
262
467
 
263
468
  ```tsx
264
- <EmbeddedCheckout
265
- eventId="14304"
266
- theme={{ colorScheme: 'dark', accent: '#ff2d55', radiusCard: 16 }}
267
- />
268
- ```
269
-
270
- The same token names as web: color (`accent`, `surface`, `text`, `muted`,
271
- `border`, `error`, `success`), shape, and typography. Read them anywhere with
272
- `useTheme()`.
469
+ import { Confirmation, Rewards, useCheckoutContext } from 'tf-checkout-react-native'
273
470
 
274
- ## Composing your own layout
471
+ function Receipt({ verified }: { verified: boolean }) {
472
+ const checkout = useCheckoutContext()
473
+ if (!verified || checkout.order?.pending) return null
474
+ return (
475
+ <Confirmation>
476
+ {checkout.order?.orderHash ? <Rewards orderHash={checkout.order.orderHash} /> : null}
477
+ </Confirmation>
478
+ )
479
+ }
480
+ ```
275
481
 
276
- Every section reads the same `useCheckout()` store from the provider.
482
+ ## Tickets, seats, codes and attribution
483
+
484
+ Use the server's current event catalog and displayed prices. Time-slot ticket
485
+ IDs encode their selected session. `Tickets` supports ticket quantities,
486
+ groups, promo/access codes, waiting-list forms and seat-map routing when the
487
+ backend offers them. Waiting-list availability is a separate, authoritative
488
+ catalog contract; a sold-out row alone does not imply that a list is open.
489
+
490
+ Unlike 1.x, native `Tickets` has no password prompt or password prop. For a
491
+ password-protected event, a host can collect the password and call
492
+ `ttf.events.authenticate(eventId, password)`, handle wrong-password and session
493
+ errors, then refetch the event/catalog for the same buyer and event context.
494
+ Keep the password out of logs and verify the deployed password/session contract
495
+ and the refreshed result; the API call alone is not proof of a working native
496
+ unlock flow.
497
+
498
+ For a headless flow, `TicketSelection` has `id`, `name`, `optionName`, `price`,
499
+ `quantity` and `currency`. Build it from current catalog data and call
500
+ `setTickets(eventId, selections)`. `setSeats` takes server-reserved `SeatLine`s;
501
+ `setSeatsIo` takes a `SeatsIoSelection`. Do not fabricate seat holds. The
502
+ native `SeatMap` drives the chart and shared selection controller; provider
503
+ holds, server cart acceptance and actual device behavior need separate testing.
504
+
505
+ Quantity retention across rotating encrypted IDs requires deployed canonical
506
+ `selectionKey`s and dated numeric context (`display_price`, `price_with_fees`,
507
+ `fee_included`, `price_currency`, `price_symbol`). Without those optional keys,
508
+ opaque-ID compatibility remains; rotation retention is not guaranteed.
509
+
510
+ Supply `initialPromoCode` and `referrer` explicitly to `EmbeddedCheckout` or
511
+ `Tickets`, or use `applyPromoCode` / `attributeReferrer` from the hook. Native
512
+ `readAttributionFromUrl` is a no-op; the host must parse and classify incoming
513
+ links. An ambiguous numeric `?r=` can be a promo or referral. Do not send it as
514
+ a referral without authoritative classification. This beta does not reproduce
515
+ every hosted conditional-field visibility rule; conditional requiredness is
516
+ left to the backend. Test your event's fields and referral/promo links against
517
+ the deployed service before relying on parity.
518
+
519
+ Post-purchase upgrade waiting is server-owned and separate from the event
520
+ waiting list. `TicketChange` exposes it only for valid targets actually offered
521
+ by the server; a sold-out ticket is not sufficient. Validate the organizer's
522
+ configuration and deployed join/leave behavior.
523
+
524
+ ## Billing, custom fields and consent
525
+
526
+ `CheckoutBilling` requires `firstName`, `lastName` and `email`. Other fields
527
+ include `confirmEmail`, phone, address, `countryId`/`stateId` (backend numeric
528
+ IDs encoded as strings), `countryCode`/`stateCode` (payment billing codes), DOB,
529
+ `nationality`, `gender`, `instagramHandle` and order `dataCapture`. A
530
+ `CheckoutHolder` requires `firstName`, `lastName`, `email` and may include
531
+ `phone`, numeric `ticketTypeId` as a string and ticket `ticketDataCapture`.
532
+ `CheckoutConsent` is `{ ttfOptIn: boolean, brandOptIn: boolean }`.
533
+
534
+ The event supplies `buyerFields`, `fieldGates`, `customFields`, `conditions`,
535
+ required add-ons and waiver requirements. Use the server's ticket breakdown to
536
+ associate holder answers with ticket types. Add-on capture is separately keyed by
537
+ add-on ID. Do not hard-code a minimal buyer form as valid for every event. The
538
+ standalone `BillingInfo` default is less strict than `EmbeddedCheckout`'s holder
539
+ and email-confirmation defaults; match it with the paired `Payment` props. The
540
+ SDK's confirmation email value is a UI check; the order API uses the primary
541
+ email. Recheck terms, privacy and marketing consent with your own policy.
542
+
543
+ **Consent limitation:** when `privacyPolicy` is supplied, the stock
544
+ privacy checkbox reads and writes `consent.ttfOptIn`, which the order sends as
545
+ `ttf_opt_in`. It is not independent of the Ticket Fairy marketing flag. The
546
+ brand opt-in uses a separate value. If both opt-in labels are explicitly empty,
547
+ `BillingInfo` can skip committing consent even while it shows the privacy
548
+ checkbox, leaving the payment gate blocked. Also, enabling
549
+ `skipBillingWhenProfileComplete` (or standalone `skipWhenProfileComplete`) can
550
+ hide that checkbox while `Payment` still requires it. Avoid those combinations
551
+ without a verified host workaround; where separate privacy and marketing consent
552
+ is required, use a custom flow and a tested backend consent contract.
553
+
554
+ During cart priming, failed custom-field, checkout-config, add-on or event
555
+ condition reads fall back to empty/default data. The stock UI cannot show or
556
+ gate requirements it did not load. Only a failed waiver read (except a 404) is
557
+ recorded in `unavailableDetails` and retried/gated. Do not treat an empty list as
558
+ proof that no requirement exists; verify deployment/failure behavior and add
559
+ tested host safeguards for mandatory fields and acknowledgments. Server
560
+ validation of required answers is not proof that a condition was acknowledged.
561
+
562
+ Waiver signatures are separate from holder fields. The waiver component reads
563
+ the event's permitted methods and stores the uploaded signature reference. If
564
+ waiver details could not be loaded, placement waits/retries that read rather
565
+ than assuming no waiver. Image questions require a host `pickImage` adapter.
566
+
567
+ ## Payments and safe completion
568
+
569
+ `Payment` uses server event/method configuration and device support. Free
570
+ orders, card, native wallets, payment plans, OXXO, provider pages and other
571
+ methods are different paths; an SDK export does not enable a method on your
572
+ account. The server's review and breakdown are authoritative for amount,
573
+ currency and receiving account. Never collect PAN or CVC in your JS form or
574
+ logs. Use the provider-owned card field or eligible PaymentSheet.
575
+
576
+ ### Result, retry and reset
577
+
578
+ `PlaceOrderResult` includes `orderHash`, `free`, `needIdVerification`, optional
579
+ `currency`, `paymentIntentId`, `alreadyPaid`, `pending` and
580
+ `pendingVoucherUrl`. `pending: true` represents an asynchronous result such as
581
+ an OXXO voucher or PayPal settlement. Show pending instructions and verify
582
+ status with the backend/provider; do not label it paid or fulfill it as paid.
583
+ Conversely, `pending !== true` and `status === 'succeeded'` are not settlement
584
+ proof: Stripe `Processing` and `RequiresCapture` can produce a result without
585
+ `pending`. The stock non-pending receipt can show confirmed copy before final
586
+ capture. Reconcile the authoritative order/provider status and use custom or
587
+ redirect routing when your fulfillment policy requires verified payment.
588
+ When `needIdVerification` is true, the host must arrange a separate verification
589
+ journey. Inline `IDVerification` requires the buyer to press Verify; redirect
590
+ mode does not render it. A composed host can render `IDVerification` under the
591
+ active `CheckoutProvider`, or call `runIdVerification(orderHash)` from a custom
592
+ flow. Handle navigation/returns; a failed or delayed verification proves neither
593
+ payment failure nor settlement.
594
+
595
+ **Zero-total orders:** this SDK ignores the free-registration response body and
596
+ reports `needIdVerification: false`. Verification or approval may still be
597
+ required, and `free: true` is not proof tickets were issued. The default inline
598
+ `EmbeddedCheckout` can nevertheless say registration is complete and the buyer
599
+ is registered while approval or verification is still outstanding. Use
600
+ `confirmationMode="redirect"` with host-owned authoritative order and
601
+ verification status, or a custom composed confirmation gated on that status.
602
+ If the status is unavailable, contact support. Do not retry payment for a free
603
+ order.
604
+
605
+ `status` is `idle`, `priming`, `ready`, `repricing`, `placing`, `succeeded`,
606
+ `expired` or `error`. Disable repeated submissions while `isBusy` or `placing`,
607
+ show the reservation countdown and handle expiry. The engine coordinates
608
+ concurrent placement and can retain the original order when payment may have
609
+ moved. A timeout, dismissed provider page, navigation or app interruption is
610
+ not proof that no payment occurred. Reconcile the original order with your
611
+ backend and payment provider before retrying a purchase or fulfilling it. This
612
+ is not an exactly-once charging guarantee across every host/provider.
613
+
614
+ `reset()` starts a reset and returns no completion signal. Prefer
615
+ `await resetWhenSettled()` when navigating to a new checkout: it waits for an
616
+ in-flight payment and returns `true` only if reset completed. `false` can mean
617
+ the original order is retained or the checkout was retired. Show
618
+ `PAID_ORDER_PENDING_MESSAGE` where appropriate and reconcile; do not clear the
619
+ store, start a new order or promise a refund merely to bypass this guard. A
620
+ custom `providerPage` must settle on close/unmount with `{ event: 'dismiss' }`;
621
+ other custom async ports must settle too, or reset can wait indefinitely.
277
622
 
278
623
  ```tsx
279
- import { ScrollView } from 'react-native'
280
- import {
281
- CheckoutProvider,
282
- Tickets,
283
- AddOns,
284
- BillingInfo,
285
- Payment,
286
- OrderSummary,
287
- } from 'tf-checkout-react-native'
624
+ import { PAID_ORDER_PENDING_MESSAGE, useCheckoutContext } from 'tf-checkout-react-native'
288
625
 
289
- const MyCheckout = () => (
290
- <CheckoutProvider theme={{ colorScheme: 'dark' }}>
291
- <ScrollView>
292
- <Tickets eventId="14304" />
293
- <AddOns eventId="14304" />
294
- <BillingInfo />
295
- <OrderSummary />
296
- <Payment />
297
- </ScrollView>
298
- </CheckoutProvider>
299
- )
626
+ function useStartAgain(setNotice: (message: string) => void) {
627
+ const checkout = useCheckoutContext()
628
+ return async () => {
629
+ if (checkout.status === 'placing') setNotice('Finishing your payment…')
630
+ if (!(await checkout.resetWhenSettled())) setNotice(PAID_ORDER_PENDING_MESSAGE)
631
+ }
632
+ }
300
633
  ```
301
634
 
302
- **Sections** — `Tickets`, `AddOns`, `BillingInfo`, `OrderSummary`, `Payment`,
303
- `IDVerification`, `Confirmation`.
304
-
305
- **UI primitives** — `Card`, `Button`, `BodyText`, `MutedText`, `Field`,
306
- `Select`, `QuantitySelect`, `PaymentMethods`, `Timer`, `TimerBanner`.
635
+ `TtfError` exposes `message`, HTTP `status` (`0` for transport failure),
636
+ optional backend `code`, `data` and `retryAfter` seconds. `errorMessage(error)`
637
+ and `isCartExpiredError(error)` help with display and expiry handling. Do not
638
+ log tokens, raw payment credentials or sensitive buyer data. An error after
639
+ payment initiation can be ambiguous even when its UI says to retry.
307
640
 
308
- **Auth** — `AuthModal`, `LoginForm`, `RegisterForm`, `ForgotPasswordForm`,
309
- `ResetPasswordForm`.
641
+ A custom confirmer can reject with `paymentProviderError(message, outcome)`:
642
+ `declined`, `cancelled`, `notStarted` or `unknown`. Only classify the first
643
+ three when the provider establishes that no charge occurred. Other rejections
644
+ are treated as unknown. Preserve the original order and reconcile it.
310
645
 
311
- ## Payment methods
646
+ ### Stripe card and PaymentSheet
312
647
 
313
- The payment section uses event and server eligibility plus device support to
314
- select available methods. Stripe card entry uses the native card field by
315
- default. A composed host can opt into Stripe PaymentSheet by supplying
316
- `paymentSheet={{ merchantDisplayName, returnURL }}` and
317
- `options={{ urlScheme }}` to `CheckoutProvider`; the registered return URL must
318
- be `${urlScheme}://stripe-redirect` and the host must route Stripe callbacks.
319
- The embedded component does not expose this opt-in.
320
-
321
- This beta admits the sheet for eligible non-MXN Stripe PaymentIntents containing
322
- card alone, or card plus Cash App Pay in USD; delayed methods are not admitted
323
- in that sheet. MXN installments and new-card payment plans use the card field.
324
- Stripe may offer Link when the account and intent qualify. Link is separate from
325
- saved-card AutoFill, which is not guaranteed by the SDK or PaymentSheet.
326
- Apple Pay and Google Pay require backend, device and merchant configuration;
327
- the event's payment account and the host's wallet setup must be compatible.
328
- Other gateway or redirect flows can require a provider page. Test each enabled
329
- method on the intended account and device before relying on it.
330
-
331
- ## Headless
332
-
333
- `useCheckout()` gives you the whole flow with no UI.
648
+ By default, native `Payment` uses Stripe's native `CardInput` for an eligible
649
+ card path. A host composing its own UI can opt into PaymentSheet on the
650
+ provider. Use a static merchant name and scheme for the life of the mounted
651
+ checkout. Configure the app to receive the same deep link and pass cold- and
652
+ warm-start Stripe callback URLs to Stripe according to its React Native setup.
653
+ The embedded component does not expose the PaymentSheet opt-in.
334
654
 
335
655
  ```tsx
336
- import { Button } from 'react-native'
337
- import { useCheckout, type TicketSelection } from 'tf-checkout-react-native'
656
+ import React from 'react'
657
+ import { CheckoutProvider, Payment } from 'tf-checkout-react-native'
338
658
 
339
- function Reserve({ eventId, selections }: { eventId: string; selections: TicketSelection[] }) {
340
- const checkout = useCheckout()
341
- return <Button title="Reserve" onPress={() => { void checkout.setTickets(eventId, selections) }} />
659
+ export function PaymentStep() {
660
+ return (
661
+ <CheckoutProvider
662
+ options={{ urlScheme: 'myapp' }}
663
+ paymentSheet={{
664
+ merchantDisplayName: 'My Events',
665
+ returnURL: 'myapp://stripe-redirect',
666
+ }}
667
+ >
668
+ {/* Render the ticket, billing and consent sections in this provider too. */}
669
+ <Payment />
670
+ </CheckoutProvider>
671
+ )
342
672
  }
343
673
  ```
344
674
 
345
- Build `selections` from the current event catalog, then collect the applicable
346
- billing, holder and consent data before calling `placeOrder`. The engine
347
- coordinates concurrent placement and retains an order when a payment outcome
348
- is uncertain. Confirm the authoritative order and provider status before
349
- retrying or fulfilling; this is not a guarantee of exactly-once charging across
350
- all hosts and providers.
351
-
352
- ### Starting a new checkout
353
-
354
- `reset()` returns nothing and starts a reset. If payment is in flight, the
355
- engine waits for that operation; it retains an order when its recorded state
356
- indicates money may have moved. A closed provider page or network error is not
357
- proof that no payment occurred. Reconcile the order with the backend and payment
358
- provider before retrying or fulfilling it.
359
-
360
- To know whether reset completed, await `resetWhenSettled()` instead. It resolves
361
- `true` after reset or `false` when reset cannot proceed, including a retained
362
- order or a retired checkout. For a payment that may have moved, show the buyer
363
- `PAID_ORDER_PENDING_MESSAGE` and reconcile it. While it waits for a payment
364
- (`status === 'placing'`), tell the buyer, for example "Finishing your
365
- payment…":
675
+ The return URL must equal `${urlScheme}://stripe-redirect`; register the actual
676
+ scheme in iOS and Android and route callbacks before normal navigation. A
677
+ controlled provider must create its `value` with the same `paymentSheet` and
678
+ `options` on `useCheckout` itself. Changing the config during a mount is not
679
+ supported. In headless UI, use `checkout.paymentSheet?.placeOrder()` when
680
+ available; the normal `Payment` section chooses it for the eligible card row.
681
+
682
+ In beta.25 the sheet is limited to eligible non-MXN Stripe PaymentIntents whose
683
+ original methods are card alone, or card plus Cash App Pay in USD. It does not
684
+ admit delayed methods, create a new intent or add a Stripe Customer/saved-card
685
+ contract. The SDK checks the original intent's identity, amount and currency
686
+ against the order review. Where the method list or sheet setup cannot be
687
+ admitted, the flow may ask for the native card form; a financial identity
688
+ mismatch instead requires order review. MXN installment card payments and new
689
+ card payment plans retain CardField. Account, event, device and provider
690
+ eligibility still apply.
691
+
692
+ Stripe may show card-mode Link when its account and session qualify; the SDK
693
+ requests automatic Link display but cannot promise its availability. Link is
694
+ not the same thing as OS/password-manager card autofill, which is also
695
+ provider/device dependent. Test Link and callbacks on real devices and the
696
+ actual receiving Stripe account.
697
+
698
+ ### Apple Pay, Google Pay and other providers
699
+
700
+ Native wallet rows are separate from PaymentSheet. They appear only when the
701
+ server enables the wallet, the corresponding OS/device supports it and Stripe's
702
+ platform-pay check succeeds. Configure merchant entitlements and identifiers,
703
+ the Stripe account, app scheme and native build for your app. The order review
704
+ must supply valid wallet amount, currency and merchant country.
705
+ A composed provider can pass:
366
706
 
367
707
  ```tsx
368
- if (checkout.status === 'placing') setNotice('Finishing your payment…')
369
- if (!(await checkout.resetWhenSettled())) setNotice(PAID_ORDER_PENDING_MESSAGE)
708
+ <CheckoutProvider options={{
709
+ merchantIdentifier: 'merchant.com.example.app',
710
+ merchantName: 'My Events',
711
+ googlePayTestEnv: false,
712
+ urlScheme: 'myapp',
713
+ }}>
714
+ {/* Render the ticket and billing sections in this provider too. */}
715
+ <Payment />
716
+ </CheckoutProvider>
370
717
  ```
371
718
 
372
- A custom `providerPage` must resolve `{ event: 'dismiss' }` when it closes or
373
- unmounts, and other custom ports must settle too, or `resetWhenSettled()` waits.
374
-
375
- A custom confirmer can classify a failure with
376
- `paymentProviderError(message, outcome)`, where `outcome` is `declined`,
377
- `cancelled`, `notStarted` or `unknown`. Use the first three only when the
378
- provider confirms that no charge occurred. An unknown outcome needs
379
- reconciliation, not an assumption that payment failed.
380
-
381
- The shared SDK exports are available from this package, including `ttf`, hooks,
382
- types and the engine. The package's TypeScript declarations describe its public
383
- exports.
384
-
385
- ## Rewards
386
-
387
- The native analogue of the web Rewards section: standing, the referral tree,
388
- the prize ladder, packs, the share card, and one share button.
389
- Render standalone `Confirmation` within an active `CheckoutProvider`; the host
390
- provides the `orderHash` used in this example.
719
+ `CheckoutOptions` also accepts `stripePublishableKey` as an override and
720
+ `redirectReturnUrl` and `merchantCountryCode`. Normally use the keys from the
721
+ order review; do not hard code a connected account or infer it from the event.
722
+ In this beta, native wallet confirmation takes merchant country from the
723
+ authoritative order review rather than the `merchantCountryCode` option. The
724
+ native confirmer uses `Order` as its merchant-name fallback; set your merchant
725
+ name explicitly. The example matches the earlier `PROD` setup. For Google Pay,
726
+ the `Payment` support probe uses test mode when `env` is `STAG` or `DEV`, while
727
+ confirmation uses `options.googlePayTestEnv`. Set both consistently for the
728
+ intended environment; a `baseUrl` override does not set `env`. Configure the
729
+ native/plugin wallet setting and verify both the support probe and confirmation
730
+ on device, especially with custom hosts. Expo settings, Apple entitlement,
731
+ deep links and provider account approval are host responsibilities. Test
732
+ eligibility, cancellation, return and settlement on each platform.
733
+
734
+ `CheckoutProvider` without a controlled value supplies a WebView provider-page
735
+ port and an installment picker. Provider pages may be used for PayPal,
736
+ Razorpay, Xendit, Adyen or redirect methods when the backend enables them.
737
+ Host routing and provider behavior still determine whether a particular flow
738
+ works; check network, app-link, browser and return behavior on device. A
739
+ headless `useCheckout` host must supply `ports.providerPage` and any other
740
+ needed ports; `rnPorts` alone does not provide the provider-page UI. Its
741
+ `providerPage` receives `{ uri?, html?, baseUrl?, returnUrlPrefix }` and resolves
742
+ `{ event: 'success' | 'dismiss' | 'failed', returnUrl?, data?, description?,
743
+ notOpened?, paymentOutcome? }`. Report `notOpened` or `paymentOutcome:
744
+ 'declined'` only when actually established. The engine still reconciles the
745
+ provider outcome.
746
+
747
+ ### `CardInput` for custom payment UI
748
+
749
+ `CardInput` props are `owner?: string` (default `checkout`),
750
+ `visible?: boolean`, and `onComplete?: (complete: boolean) => void`. The
751
+ native hook's default card claim uses the default owner. If multiple flows can
752
+ mount card fields, give each its own owner and use the matching
753
+ `claimCardField(owner)` and `cardClaimStep` as `placeOrder`'s `claimCard` option;
754
+ otherwise one flow must not use another's field. Keep the field mounted and
755
+ visible through its confirmation. `Payment` handles its own field ownership.
756
+ Do not treat `onComplete` as confirmation or payment success.
757
+
758
+ ## Headless checkout
759
+
760
+ `useCheckout()` is the native-wired React binding, not the old 1.x React Query
761
+ mutation. It returns a flat `CheckoutSnapshot`, `snapshot`, `store`, derived
762
+ state and actions. It injects the native Stripe confirmer and ports. Use it
763
+ inside a React component after configuration, and supply a React Query provider
764
+ where the host also uses query hooks. The app-wide checkout store is shared
765
+ across native hook calls and provider sections: they are not independent carts.
391
766
 
392
767
  ```tsx
393
- import { Confirmation, Rewards } from 'tf-checkout-react-native'
394
- ;<Confirmation>
395
- <Rewards orderHash={orderHash} />
396
- </Confirmation>
397
- ```
398
-
399
- When `<EmbeddedCheckout/>` renders the receipt itself, pass the block through
400
- `confirmationChildren`. In this example the host supplies `eventId` and
401
- `MyRewards`:
768
+ import React, { useState } from 'react'
769
+ import { Button, Text, View } from 'react-native'
770
+ import { useCheckout, type TicketSelection } from 'tf-checkout-react-native'
402
771
 
403
- ```tsx
404
- <EmbeddedCheckout eventId={eventId} confirmationChildren={<MyRewards />} />
772
+ // selections must come from this event's current server catalog.
773
+ export function Reserve({ eventId, selections }: {
774
+ eventId: string
775
+ selections: TicketSelection[]
776
+ }) {
777
+ const checkout = useCheckout()
778
+ const [message, setMessage] = useState('')
779
+ const reserve = async () => {
780
+ try {
781
+ await checkout.setTickets(eventId, selections)
782
+ setMessage('Tickets reserved. Collect the required details next.')
783
+ } catch {
784
+ setMessage('Could not reserve tickets. Check availability and try again.')
785
+ }
786
+ }
787
+ return <View><Button title="Reserve" disabled={checkout.isBusy} onPress={() => { void reserve() }} />
788
+ <Text>{message}</Text></View>
789
+ }
405
790
  ```
406
791
 
407
- Native adaptations, and why:
408
-
409
- - **No SVG.** The tree is positioned `View`s, from the same shared layout the
410
- web SVG uses. Adding `react-native-svg` would be a new native peer for every
411
- customer app; a dot-and-line picture does not need one.
412
- - **One share button.** Per-channel buttons collapse into `Share.share()` — the
413
- OS sheet presents the eligible share targets available on that device.
414
- - **Copy comes in as props**, this package's idiom. `translate`, or `locale` and
415
- `messages` for the built-in `rewards.*` catalogue.
416
-
417
- ## Seat maps
418
-
419
- `SeatMap` uses the existing WebView peer to render internal charts or the event's
420
- Seats.io chart, with the shared core owning selection, holds and reservations.
421
- It requires the matching backend seating/session contracts. Provider-backed hold
422
- and deployed-cart acceptance remain separate checks.
423
-
424
- ## Known gaps vs hosted checkout
425
-
426
- Native URL attribution requires host-supplied values, conditional custom-field
427
- visibility is not fully mirrored, and native smooth scroll is not equivalent to
428
- web's `autoScroll`. Provider, wallet and device behavior also needs separate
429
- qualification. The list is not a claim of full hosted or web SDK parity.
430
-
431
- ## Development
792
+ A real headless host must collect the event's gated buyer/holder data, custom
793
+ answers, conditions, add-ons and waivers, render a provider-owned payment input
794
+ or wallet/provider UI, validate against the event, and handle every result and
795
+ error before placing an order. Enforce host privacy and `additionalConditions`
796
+ yourself; the headless engine does not receive these `Payment` props. The short
797
+ example only primes the cart.
798
+
799
+ ### Snapshot and actions
800
+
801
+ | Member | Purpose |
802
+ | --- | --- |
803
+ | `status`, `error`, `order`, `orderFixed` | Lifecycle, error text, result and whether order inputs are frozen. |
804
+ | `eventId`, `selections`, `ticketTypeIds`, `breakdown`, `priceBreakdown` | Current cart and server-priced display data. |
805
+ | `secondsLeft`, `expiresAt`, `isBusy` | Countdown in whole seconds, absolute expiry in epoch ms and operation state. `secondsLeft` can be undefined. |
806
+ | `customFields`, `buyerFields`, `fieldGates`, `holderNamesRequired` | Event-specific capture requirements. |
807
+ | `requiredAddOns`, `missingRequiredAddonNames`, `addOns`, `addOnDataCapture` | Add-on requirements and selections. |
808
+ | `billing`, `holders`, `consent`, `conditions`, `acceptedConditions` | Buyer data and acknowledgments. |
809
+ | `waiver`, `waiverSignatures`, `waiverSigningIndexes`, `unavailableDetails` | Waiver state; `unavailableDetails` tracks waiver failures, not other failed detail reads. |
810
+ | `gateway`, `stripe`, `paymentStripe`, `paymentPlan`, `redirectPaymentMethods`, `walletAvailability`, `currency` | Server/engine payment and currency context. Their presence is not a guarantee of provider eligibility. |
811
+ | `extraItems`, `extraDraft`, `extraQuotes`, `extraRejections`, `extraExpiresAt`, `reviewedExtraOrderTotal` | Accommodation/extra reservation and price state. |
812
+ | `setTickets(eventId, selections)` | Prime ticket cart and price it. |
813
+ | `setSeats(eventId, lines)`, `setSeatsIo(eventId, selection)`, `resumeCart(eventId, options?)` | Prime a seat cart or attach to a supported existing cart. |
814
+ | `setAddons(selection)` | Set add-on quantities and reprice; await it. |
815
+ | `setExtraItems(items)`, `setExtraDraft(items \| undefined)` | Reserve or edit accommodation/extras. |
816
+ | `setBilling(billing)`, `setHolders(holders, options?)`, `setConsent(consent)` | Commit buyer data; optional holder `backfilled` describes copies of buyer details. |
817
+ | `setAcceptedConditions(map)`, `acceptCondition(id, accepted)` | Commit required/optional acknowledgment state. |
818
+ | `setAddonDataCapture(capture)` | Set answers keyed by add-on ID. |
819
+ | `holdWaiverSigning(index)`, `setWaiverSignature(index, signature, hold?)` | Bind a signature to its original cart/holder; release the returned hold after saving. |
820
+ | `checkBuyerEmail(email)`, `reloadCheckoutDetails()` | Check for an existing account; retry an unavailable waiver read. |
821
+ | `placeOrder(method?, options?)` | Place/pay via an eligible method; returns `Promise<PlaceOrderResult>`. Options include `autoRunIdVerification`, `idVerification`, `claimCard`. |
822
+ | `runIdVerification(orderHash, options?)` | Open/poll verification as a separate post-order step. |
823
+ | `syncCurrency(eventId, code?, options?)` | Seed/change currency; refetch catalog and re-prime as needed. |
824
+ | `applyPromoCode(eventId, promo)`, `attributeReferrer(eventId, referrer)`, `joinWaitingList(eventId, entry)` | Event engagement helpers. |
825
+ | `reset()`, `resetWhenSettled()` | Reset subject to payment safety; use the awaited form to know its outcome. |
826
+ | `paymentSheet?.placeOrder()` | Present the explicitly configured eligible native sheet. May be absent. |
827
+
828
+ `placeOrder` supports simple `'card'`, `'applePay'`, `'googlePay'` or typed
829
+ `PaymentRequest` objects: `auto`, `card`, `applePay`, `googlePay`, `redirect`
830
+ (with `methodType`), `oxxo`, `xenditEWallet` (with `walletKey` and optional extra),
831
+ `paypal`, `razorpay`, `adyen`, `xenditCard`, or `paymentPlan`. A union member
832
+ is not an availability promise. Render choices from server/provider eligibility
833
+ and supply the required port. `UseCheckoutOptions` accepts `{ ports?, options?,
834
+ paymentSheet? }`; ports merge over `rnPorts`. `createRnStripeConfirmer`,
835
+ `createRnPorts`, `rnConfirmer` and `rnPorts` are exported for advanced hosts.
836
+ For non-React integration, `createEngine({ ports, options?, store? })` provides
837
+ `prime`, `placeOrder`, `getSnapshot` and `subscribe`; its required `confirmer`
838
+ and optional ports are described by the published `CheckoutPorts` type.
839
+
840
+ ## Hooks and API
841
+
842
+ Import the shared React Query hooks through the **`hooks` namespace**, not as
843
+ individual top-level imports. `CheckoutProvider` includes a query provider for
844
+ its descendants; outside it, wrap your components with your own
845
+ `QueryClientProvider`. Query hooks expose React Query states such as `data`,
846
+ `isLoading`, `error` and `refetch`; mutation hooks expose `mutate` /
847
+ `mutateAsync`, pending state and error. Query and mutation options follow the
848
+ published `QueryOpts` and `MutationOpts` types. Event/order hooks can be gated
849
+ until their ID is available.
432
850
 
433
- From the repo root:
851
+ ```tsx
852
+ import React from 'react'
853
+ import { Text } from 'react-native'
854
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
855
+ import { hooks } from 'tf-checkout-react-native'
434
856
 
435
- ```bash
436
- pnpm run start:react-native
857
+ const queryClient = new QueryClient()
858
+ function CatalogName({ eventId }: { eventId: string }) {
859
+ const tickets = hooks.useTickets(eventId)
860
+ if (tickets.isLoading) return <Text>Loading tickets…</Text>
861
+ if (tickets.error) return <Text>Could not load tickets.</Text>
862
+ return <Text>{tickets.data?.length ?? 0} ticket types</Text>
863
+ }
864
+ export function Catalog({ eventId }: { eventId: string }) {
865
+ return <QueryClientProvider client={queryClient}><CatalogName eventId={eventId} /></QueryClientProvider>
866
+ }
437
867
  ```
438
868
 
439
- `pnpm run playground:react-native` opens the playground mode instead.
440
-
441
- Both need a dev client. Build one with:
869
+ The API reference lists the available hooks by domain and their parameters.
870
+
871
+ The top-level native `useCheckout()` adds native ports. `hooks.useCheckout` is
872
+ the shared lower-level binding and expects an `EngineConfig` with ports; do not
873
+ substitute it into the native quick start. The root also exports `ttf` (a
874
+ configured domain-grouped API facade), `api` (raw API namespace), types,
875
+ `createEngine`, store/logic utilities and translation functions. For example,
876
+ `ttf.events.tickets(eventId)`, `ttf.geo.countries()`, `ttf.orders.get(orderId)`
877
+ and `ttf.order.review(orderHash)` are promise-returning calls. Private account
878
+ reads require an appropriately authorized account session; the checkout buyer
879
+ scope does not grant it. Low-level APIs do not replace checkout preflight or
880
+ payment reconciliation.
881
+
882
+ The [API reference](https://unpkg.com/tf-checkout-react-native@2.0.0-beta.25/docs/API.md)
883
+ includes the full component and hook catalog.
884
+
885
+ ## Authentication and account components
886
+
887
+ `AuthModal` accepts required `open` and `onClose`, plus `checkoutEventId`,
888
+ `onCheckoutAuthenticated`, `onAuthenticated`, `allowRegistration`,
889
+ `allowPasswordReset`, `requireRegistrationAddress`, `logoUrl`, `initialEmail`
890
+ and `message`. Supply `checkoutEventId` for checkout-scoped auth. The
891
+ `LoginForm`, `RegisterForm`, `ForgotPasswordForm`, `TwoFactorForm` and
892
+ `EmailCodeForm` present fields and delegate their callbacks/state to the host
893
+ or shared controller. `ResetPasswordForm` takes a required reset `token` and
894
+ optional `onSuccess` / `onBackToLogin`. `PasswordInput` wraps `Input` with a
895
+ visibility toggle. The expanded reference lists required form callbacks.
896
+
897
+ Additional exports include `TicketChange`, `TicketRefund`, `TicketGift`,
898
+ `Passes`, `Trips`, `AccountRewardsSection`, `AccountInformationSection`,
899
+ `AccountPaymentPlanSection`, account deletion, profile and notification views,
900
+ `Streaming`, `PhoneAuthentication`, `AccountEmailEnrichment`,
901
+ `EventDiscussionView` and post reactions. Their availability depends on the
902
+ deployed backend, authorized account scope and host-owned navigation. See the
903
+ [API reference](https://unpkg.com/tf-checkout-react-native@2.0.0-beta.25/docs/API.md)
904
+ for component variants and required props.
905
+
906
+ Standalone account components that use query hooks need a `QueryClientProvider`;
907
+ `CheckoutThemeProvider` can theme them without creating a checkout. The `View`
908
+ variants let a host create and retain a shared flow itself. The host must handle
909
+ account sign-in, deep links, browser handoff and navigation; these components do
910
+ not grant account scope by being rendered.
911
+
912
+ ## Theme, primitives and localization
913
+
914
+ Pass a partial `CheckoutTheme` to `EmbeddedCheckout`, `CheckoutProvider` or
915
+ `CheckoutThemeProvider`. `colorScheme` is `'light'` (default) or `'dark'`;
916
+ other tokens are `accent`, `accentContrast`, `surface`, `surfaceMuted`,
917
+ `background`, `text`, `muted`, `border`, `error`, `success`, `radiusCard`,
918
+ `radiusControl`, `fontSize`, `fontFamily`. Colors/fonts are strings; radii and
919
+ font size are numbers. `resolveTheme(theme)` returns all resolved tokens and
920
+ `useTheme()` reads the current theme. Check contrast and native accessibility.
442
921
 
443
- ```bash
444
- cd example && pnpm run ios
922
+ ```tsx
923
+ <EmbeddedCheckout eventId="YOUR_EVENT_ID" theme={{
924
+ colorScheme: 'dark', accent: '#7c3aed', accentContrast: '#ffffff', radiusCard: 16,
925
+ }} />
445
926
  ```
446
927
 
447
- Use only public, non-secret configuration in `example/.env`
448
- (`EXPO_PUBLIC_`-prefixed); never put a confidential credential in the app.
449
-
450
- The example's `metro.config.js` pins React, react-query, and Stripe to single
451
- copies. That is a **workspace-only** concern — it exists because the example
452
- consumes the library through a symlink. An npm host should still verify that its
453
- dependency tree resolves the intended single instances.
454
-
455
- ---
456
-
457
- The checkout monorepo is available to authorised repository collaborators.
458
-
459
- Public checkout sign-in uses event-scoped server-only buyer binding, without a bundled
460
- client secret. The checkout's auth modal supplies the event automatically; standalone
461
- `AuthModal` callers pass `checkoutEventId` and can receive the narrow
462
- `onCheckoutAuthenticated(CheckoutBuyerProfile)` callback. Password/signup/email/phone
463
- proofs still complete required two-factor verification. These credentials cannot read
464
- private account preferences or tickets. Existing confidential OAuth clients
465
- are trusted-server integrations; never distribute their secrets in browser/native code.
928
+ The exported theme-aware primitives are `Loader`, `Card`, `SectionHeader`,
929
+ `Divider`, `BodyText`, `MutedText`, `ErrorText`, `Button`, `FrozenFields`,
930
+ `Field`, `Input`, `Checkbox`, `Select`, `QuantitySelect`, `Timer`, `TimerBanner`,
931
+ `PaymentMethods` and `RichText`. `useStyles` and `useFrozen` are also exported.
932
+ `PaymentMethods` is a controlled presentation component, not a Pay action.
933
+ `RichText` accepts `html`, `textStyle`, `tagsStyles`, `onLinkPress` and
934
+ `selectable`; it uses the render-html peer. Test external links with your host's
935
+ navigation policy. `FrozenFields` disables its supported SDK controls when the
936
+ order is fixed; custom controls need the same guard. The expanded API reference
937
+ has the individual control props.
938
+
939
+ Selected sections accept `locale`, `messages` and `translate`; these are per
940
+ component, not a promise that every `EmbeddedCheckout` string can be globally
941
+ replaced. Shared localization exports include `createTranslator`,
942
+ `resolveLocale`, `registerCatalogue` and `DEFAULT_LOCALE`. Organizer-authored
943
+ content is data, and should not be assumed to have a translation.
944
+
945
+ ## Migration from 1.x
946
+
947
+ This is a major integration change. Pin 1.x while planning migration and test
948
+ against the exact new version, backend and devices. The historical
949
+ [1.0.47 README](https://unpkg.com/tf-checkout-react-native@1.0.47/README.md)
950
+ and [1.0.47 declarations](https://unpkg.com/tf-checkout-react-native@1.0.47/lib/typescript/src/index.d.ts)
951
+ describe the old package; examples there are not 2.x instructions.
952
+
953
+ | 1.x integration | 2.x beta.25 approach |
954
+ | --- | --- |
955
+ | `setConfig({ EVENT_ID, ENV, ... })` and event-bound global UI | Restore a secure adapter and call `configureCheckout({ env, clientId, clientSecret: '', storage })`; pass `eventId` to the checkout or section. |
956
+ | `Tickets` callbacks such as `onAddToCartSuccess` / `onPressMyOrders` | `Tickets` uses `onContinue` / `onSelectSeats`; the host owns orders navigation. Rework the host flow, not just prop names. |
957
+ | 1.x `Checkout`, `CheckoutV2`, `BillingInfo` and older core components | Use `EmbeddedCheckout` or compose the 2.x sections and `CheckoutProvider`. The 1.x `CheckoutV2` name does not mean this 2.x API. |
958
+ | 1.x `useCheckout()` mutation | Native `useCheckout()` is a stateful engine binding with `setTickets`, setters and `placeOrder`; query hooks are under `hooks`. |
959
+ | Implicit old native storage and session behavior | Supply and restore an OS-protected synchronous adapter. Migrate credentials securely or ask the buyer to sign in again. Checkout-only and account scopes differ. |
960
+ | Per-component style/text props from the old UI | Use theme tokens, available render callbacks and section props; rebuild custom presentation where required. |
961
+ | Existing Stripe setup | Verify current peer/native builds, receiving account, provider URLs, return scheme, wallets and PaymentSheet separately. |
962
+
963
+ Rebuild the native binary after replacing native dependencies and pods. The 1.x
964
+ My Orders/order-detail screens, PDF handling and resale screen are not drop-in
965
+ 2.x equivalents: build the needed host UI, navigation and PDF handling around
966
+ the available order/resale hooks and APIs. Revalidate refund, resale, account
967
+ and password-protected event flows against the deployed service.
968
+
969
+ ## Testing and troubleshooting
970
+
971
+ 1. Pin the SDK and peer versions in the lockfile; verify one React, React Query
972
+ and native-module instance. Make a clean development build and install pods.
973
+ 2. Use an authorized test event and receiving account. Check free and paid
974
+ orders, stock and pricing, required fields/waivers, add-ons, holders, promo
975
+ and referral classification, time slots and seat holds. Verify actual orders
976
+ against the backend and provider, including fee/currency/account context.
977
+ 3. Test supported iOS/Android devices: backgrounding, cold/warm returns,
978
+ interruption, network loss, duplicate taps, expiry, cancellation, guest
979
+ sign-in and account changes. Test each enabled method, wallet and account.
980
+ 4. Configure production merchant credentials, entitlements, public client ID,
981
+ HTTPS endpoint and callback scheme. Remove test-card hints; verify storage,
982
+ privacy and backend contracts. Never log real card details.
983
+
984
+ | Symptom | Check |
985
+ | --- | --- |
986
+ | `TS2304: Cannot find name 'Crypto'` in SDK declarations | A host without DOM and without a compatible ambient `Crypto` type may report this. Prefer compatible host type definitions. `skipLibCheck` skips declaration-file checks, and unresolved `Crypto` can degrade affected types to `any`, allowing invalid `newBundleIdempotencyKey` arguments. Neither option installs runtime crypto. |
987
+ | Configuration/storage error | Hydrate first, then pass an object with synchronous `getItem`, `setItem`, `removeItem`; check native storage failures. |
988
+ | `No QueryClient set` or provider context error | Wrap standalone query hooks in `QueryClientProvider`, and checkout sections in `CheckoutProvider`; check duplicate React Query copies. |
989
+ | Blank chart or missing seat CTA | Confirm event seating support, pass `onSelectSeats`, rebuild WebView, and verify provider/chart/hold contracts. |
990
+ | Missing Apple Pay / Google Pay / Link | Check backend eligibility, real receiving account, device support and host wallet configuration; a simulator or local configuration alone may not qualify. |
991
+ | PaymentSheet unavailable or card field requested | Check static scheme/return URL, original intent and supported method/currency combination. The embedded component does not expose sheet configuration. |
992
+ | Redirect does not return | Register and route the exact app scheme, including cold starts; verify the provider account and allowed return URL. |
993
+ | Email/holder/privacy validation does not match UI | Align `BillingInfo` and `Payment` policies; load current event gates and custom fields. |
994
+ | Outcome unclear or reset returns false | Keep the original order and reconcile backend/provider status. A page close or timeout is not proof of no charge. |
995
+ | Code/referral or conditional question differs from hosted checkout | Classify incoming links server-side or from an authoritative source; native URL reading is a no-op and conditional visibility is incomplete in this beta. |