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

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.
package/README.md CHANGED
@@ -1,39 +1,40 @@
1
1
  # tf-checkout-react-native
2
2
 
3
- The TicketFairy checkout for React Native. Drop in one component and you have
4
- ticket selection, add-ons, buyer details, payment, ID verification, and the
5
- receipt — or compose the sections yourself.
6
-
7
- Same API as [`tf-checkout-react`](https://github.com/theticketfairy/tf-shared-libraries/blob/main/packages/tf-checkout-react/README.md); the flow,
8
- validation, and gating rules are literally the same code.
9
-
10
- The 2.x source is maintained in [`tf-shared-libraries/packages/tf-checkout-react-native`](https://github.com/theticketfairy/tf-shared-libraries/tree/main/packages/tf-checkout-react-native).
11
- The core is private and bundled into both platform packages. Use React 18+ and
12
- React Query 5 on web; the native requirements below reflect its supported build.
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.
13
15
  Never put a confidential OAuth client secret in browser or mobile code. Public
14
16
  checkout integrations use registered SDK client IDs and owned buyer sessions.
15
17
 
16
18
  ## Install
17
19
 
18
- ### 2.0.0-beta.21 prerelease candidate
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.
19
26
 
20
- This package targets the matching web/native `2.0.0-beta.21` candidate. Confirm
21
- exact-version availability and integrity against the [canonical release receipts](https://github.com/theticketfairy/tf-shared-libraries/tree/main/docs/releases)
22
- before pinning it in an app. Candidate source and local packs alone do not prove
23
- publication. The unqualified npm package continues to resolve to stable 1.x;
24
- `@beta` follows the most recently accepted prerelease. Use the exact version
25
- below when integrating this contract.
27
+ ```bash
28
+ npm view tf-checkout-react-native dist-tags
29
+ ```
26
30
 
27
31
  This version preserves quantities across rotating encrypted ticket IDs using
28
32
  the API's optional canonical `selectionKey`, while quantity/change APIs and cart
29
33
  requests retain the latest encrypted IDs. Deploy and verify the canonical
30
34
  base/dated key and dated numeric context before relying on that behavior:
31
35
  `display_price`, `price_with_fees`, `fee_included`, `price_currency` and
32
- `price_symbol`. No localized price parsing or base-price/cap substitution is
33
- used. Missing optional keys retain opaque-ID compatibility, without a
34
- rotation-retention claim. The [beta.20 notes](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/releases/2.0.0-beta.21.md)
35
- and [app integration contract](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/integration/app-sdk.md)
36
- record API rollout requirements and separate deployment/provider/device gates.
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.
37
38
 
38
39
  Post-purchase upgrade waiting is a separate server-owned contract. Only valid
39
40
  change-options targets can expose Join or Leave; malformed target IDs, labels
@@ -41,11 +42,12 @@ or existing entries are ignored. A sold-out row does not enable it. The backend
41
42
  must explicitly offer that organizer-configured flow; local receiver validation
42
43
  does not prove its deployed availability or a completed join.
43
44
 
45
+ For an Expo host with compatible React and React Native versions:
46
+
44
47
  ```bash
45
- npx expo install tf-checkout-react-native@2.0.0-beta.21 \
48
+ npx expo install tf-checkout-react-native@2.0.0-beta.23 \
46
49
  @tanstack/react-query \
47
50
  @stripe/stripe-react-native \
48
- react-native-mmkv@3 \
49
51
  @react-native-clipboard/clipboard \
50
52
  react-native-webview \
51
53
  react-native-render-html@6.3.4
@@ -54,31 +56,31 @@ npx expo install tf-checkout-react-native@2.0.0-beta.21 \
54
56
  Bare React Native:
55
57
 
56
58
  ```bash
57
- npm install tf-checkout-react-native@2.0.0-beta.21 @tanstack/react-query @stripe/stripe-react-native react-native-mmkv@3 @react-native-clipboard/clipboard react-native-webview react-native-render-html@6.3.4
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
58
60
  ```
59
61
 
60
- These are **peer dependencies** on purpose. React contexts and native
61
- modules must exist exactly once in an app — a second copy of react-query means
62
- your `<QueryClientProvider>` is invisible to our hooks, and a second Stripe
63
- means a duplicate pod. As peers there is one copy and no Metro aliasing is ever
64
- needed.
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.
65
66
 
66
- Everything else — the SDK core, the flow, the UI — ships inside the package.
67
+ The private shared core and native UI are bundled; npm installs the package's
68
+ declared runtime dependencies separately.
67
69
 
68
- This beta.21 candidate retains the `react-native-render-html` peer `>=6.3.4 <7`
70
+ This version requires the `react-native-render-html` peer `>=6.3.4 <7`
69
71
  for formatted ticket, add-on, bundle and accommodation descriptions and its
70
72
  public `RichText` renderer. Development and component tests use `6.3.4`; the
71
- install commands above pin that compatible version. See [native rich content](../../docs/integration/native-rich-content.md)
72
- for the rendering contract and pending acceptance coverage.
73
+ install commands above pin that compatible version. Verify the rendering and
74
+ link behavior needed by your host on its supported devices.
73
75
 
74
76
  ## Requirements
75
77
 
76
- - React Native `0.81+` with the New Architecture enabled (required by
77
- `react-native-mmkv` 3.x)
78
+ - React Native `0.81+` with the New Architecture enabled (the supported build cohort)
78
79
  - React `19.1+`, Node `20.19.4+`
79
- - iOS 15.1+ · Android API 24+ (Stripe SDK minimums)
80
- - A development build. **Expo Go will not work** — this package uses native
81
- modules.
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.
82
84
 
83
85
  Add the Stripe plugin to `app.json`:
84
86
 
@@ -98,26 +100,104 @@ npx expo run:ios
98
100
 
99
101
  ## Use it
100
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`:
123
+
124
+ ```ts
125
+ // credentialStorage.ts
126
+ import * as SecureStore from 'expo-secure-store'
127
+ import type { StorageAdapter } from 'tf-checkout-react-native'
128
+
129
+ export async function restoreCredentialStorage(namespace: string): Promise<StorageAdapter> {
130
+ const raw = await SecureStore.getItemAsync(namespace)
131
+ const parsed: unknown = raw === null ? {} : JSON.parse(raw)
132
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) ||
133
+ Object.values(parsed).some(value => typeof value !== 'string')) {
134
+ throw new Error('Could not restore checkout credentials')
135
+ }
136
+ let values = new Map(Object.entries(parsed as Record<string, string>))
137
+ const commit = (next: Map<string, string>) => {
138
+ SecureStore.setItem(namespace, JSON.stringify(Object.fromEntries(next)), {
139
+ keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
140
+ requireAuthentication: false,
141
+ })
142
+ values = next
143
+ }
144
+ 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) },
148
+ }
149
+ }
150
+ ```
151
+
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/).
156
+
101
157
  ```tsx
158
+ import { useEffect, useState } from 'react'
159
+ import { Button, Text, View } from 'react-native'
102
160
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
103
161
  import { configureCheckout, EmbeddedCheckout } from 'tf-checkout-react-native'
104
-
105
- configureCheckout({ env: 'PROD', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret: '', cookieAuth: true })
162
+ import { restoreCredentialStorage } from './credentialStorage'
163
+
164
+ // Share initialization across repeated effects and permit retry after failure.
165
+ let initialization: Promise<void> | undefined
166
+ function prepareCheckout(): Promise<void> {
167
+ if (!initialization) {
168
+ initialization = (async () => {
169
+ const storage = await restoreCredentialStorage('my_app_checkout_prod')
170
+ configureCheckout({ env: 'PROD', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret: '', storage })
171
+ })().catch(error => { initialization = undefined; throw error })
172
+ }
173
+ return initialization
174
+ }
106
175
 
107
176
  const queryClient = new QueryClient()
108
177
 
109
178
  export default function App() {
179
+ const [ready, setReady] = useState(false)
180
+ const [failed, setFailed] = useState(false)
181
+ const [attempt, setAttempt] = useState(0)
182
+ useEffect(() => {
183
+ let active = true
184
+ prepareCheckout().then(() => { if (active) setReady(true) })
185
+ .catch(() => { if (active) setFailed(true) })
186
+ return () => { active = false }
187
+ }, [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>
110
190
  return (
111
191
  <QueryClientProvider client={queryClient}>
112
- <EmbeddedCheckout eventId="14304" onComplete={(order) => console.log(order)} />
192
+ <EmbeddedCheckout eventId="14304" />
113
193
  </QueryClientProvider>
114
194
  )
115
195
  }
116
196
  ```
117
197
 
118
- `configureCheckout` runs once at app start and defaults SDK-owned storage to
119
- MMKV. Storage survives a relaunch; the backend still validates the restored
120
- session.
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.
121
201
 
122
202
  ### Checkout sessions and account scope
123
203
 
@@ -126,12 +206,20 @@ cookie handling. Owned credentials use the SDK's header-backed session storage.
126
206
  Public checkout sign-in preserves the event-bound guest carrier and keeps its
127
207
  buyer credential server-side; it does not return a full-account ticket or grant
128
208
  private account access. First-party full-account flows explicitly use
129
- `accountAuth: true`; see the shared identity contract below.
209
+ `accountAuth: true` when the host is authorized for that scope.
130
210
 
131
211
  ### Environments
132
212
 
133
213
  ```ts
134
- configureCheckout({ env: 'STAG', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret: '', cookieAuth: true })
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
+ })
222
+ }
135
223
  ```
136
224
 
137
225
  `PROD` → `www.ticketfairy.com`, `STAG` → `test.ticketfairy.com`, `DEV` →
@@ -142,7 +230,7 @@ configureCheckout({ env: 'STAG', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret:
142
230
  | Prop | Type | Default | Does |
143
231
  | ----------------------------------- | ----------------------------------- | ----------- | --------------------------------------------- |
144
232
  | `eventId` | `string` | — | The event to sell. Required. |
145
- | `onComplete` | `(order: PlaceOrderResult) => void` | — | Fires once payment settles. |
233
+ | `onComplete` | `(order: PlaceOrderResult) => void` | — | Receives an order result, possibly pending. |
146
234
  | `confirmationMode` | `'inline' \| 'redirect'` | `'inline'` | `redirect` leaves routing to `onComplete`. |
147
235
  | `initialView` | `'tickets' \| 'seats'` | `'tickets'` | Opening screen. |
148
236
  | `onViewChange` | `(view) => void` | — | Reports the visible view. |
@@ -150,7 +238,7 @@ configureCheckout({ env: 'STAG', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret:
150
238
  | `collectTicketHolders` | `boolean` | `true` | One named holder per ticket. |
151
239
  | `collectHolderContact` | `boolean` | `true` | Also collect holder email/phone. |
152
240
  | `skipBillingWhenProfileComplete` | `boolean` | `false` | Skip billing for complete signed-in profiles. |
153
- | `auth` | `false \| AuthOptions` | built-in | Configure or remove the login modal. |
241
+ | `auth` | `EmbeddedCheckoutProps['auth']` | built-in | Configure or remove the login modal. |
154
242
  | `onLogin` | `(email?: string) => void` | — | Open your own login UI instead. |
155
243
  | `privacyPolicy` | `CheckoutPrivacyPolicy` | — | Your privacy policy link and brand name. |
156
244
  | `additionalConditions` | `EventCondition[]` | — | Extra acknowledgements. |
@@ -163,6 +251,13 @@ configureCheckout({ env: 'STAG', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret:
163
251
  `readAttributionFromUrl` is accepted for parity with web and is a no-op —
164
252
  there is no `window.location` on a device.
165
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
+
166
261
  ## Theming
167
262
 
168
263
  ```tsx
@@ -181,6 +276,7 @@ The same token names as web: color (`accent`, `surface`, `text`, `muted`,
181
276
  Every section reads the same `useCheckout()` store from the provider.
182
277
 
183
278
  ```tsx
279
+ import { ScrollView } from 'react-native'
184
280
  import {
185
281
  CheckoutProvider,
186
282
  Tickets,
@@ -188,14 +284,13 @@ import {
188
284
  BillingInfo,
189
285
  Payment,
190
286
  OrderSummary,
191
- useCheckout,
192
287
  } from 'tf-checkout-react-native'
193
288
 
194
289
  const MyCheckout = () => (
195
290
  <CheckoutProvider theme={{ colorScheme: 'dark' }}>
196
291
  <ScrollView>
197
- <Tickets />
198
- <AddOns />
292
+ <Tickets eventId="14304" />
293
+ <AddOns eventId="14304" />
199
294
  <BillingInfo />
200
295
  <OrderSummary />
201
296
  <Payment />
@@ -213,39 +308,59 @@ const MyCheckout = () => (
213
308
  **Auth** — `AuthModal`, `LoginForm`, `RegisterForm`, `ForgotPasswordForm`,
214
309
  `ResetPasswordForm`.
215
310
 
311
+ ## Payment methods
312
+
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
+
216
331
  ## Headless
217
332
 
218
333
  `useCheckout()` gives you the whole flow with no UI.
219
334
 
220
335
  ```tsx
221
- const checkout = useCheckout({ eventId: '14304' })
336
+ import { Button } from 'react-native'
337
+ import { useCheckout, type TicketSelection } from 'tf-checkout-react-native'
222
338
 
223
- await checkout.setTickets('14304', [{ ticketTypeId: '1', quantity: 2 }])
224
- checkout.setBilling({ firstName, lastName, email, phone })
225
- checkout.setConsent({ ttfOptIn: true, brandOptIn: false })
226
- const order = await checkout.placeOrder('card')
339
+ function Reserve({ eventId, selections }: { eventId: string; selections: TicketSelection[] }) {
340
+ const checkout = useCheckout()
341
+ return <Button title="Reserve" onPress={() => { void checkout.setTickets(eventId, selections) }} />
342
+ }
227
343
  ```
228
344
 
229
- `placeOrder` is retry-safe: one order, one charge, however many times the button
230
- is pressed.
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.
231
351
 
232
352
  ### Starting a new checkout
233
353
 
234
- `reset()` discards the cart. It returns nothing, so
235
- `useEffect(() => checkout.reset(), [eventId])` works as it always has. Before
236
- any money moves it resets at once. While a payment is going through, it waits
237
- for the payment to finish, so the payment never lands in a new cart. An order
238
- that is not confirmed yet is kept when it is charged, or when a payment step
239
- failed without an answer that nothing was charged (a network error, a
240
- timeout, or a Stripe error that is not a decline or a cancel). A Razorpay or
241
- redirect payment page the buyer closes is kept only when a check of the order
242
- fails or finds a payment; otherwise it is a cancel. Pressing Pay again then
243
- finishes the order, or pays as usual when nothing was paid. A server refusal
244
- of the order, such as an expired reservation, releases it.
245
-
246
- To know which happened, await `resetWhenSettled()` instead. It resolves `true`
247
- once the checkout is reset, or `false` when it kept the order. In that case,
248
- show the buyer `PAID_ORDER_PENDING_MESSAGE`. While it waits for a payment
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
249
364
  (`status === 'placing'`), tell the buyer, for example "Finishing your
250
365
  payment…":
251
366
 
@@ -254,20 +369,25 @@ if (checkout.status === 'placing') setNotice('Finishing your payment…')
254
369
  if (!(await checkout.resetWhenSettled())) setNotice(PAID_ORDER_PENDING_MESSAGE)
255
370
  ```
256
371
 
257
- A custom `providerPage` must resolve `'dismiss'` when it closes or unmounts, and
258
- any other custom port must settle then too, or `resetWhenSettled()` waits for it.
372
+ A custom `providerPage` must resolve `{ event: 'dismiss' }` when it closes or
373
+ unmounts, and other custom ports must settle too, or `resetWhenSettled()` waits.
259
374
 
260
- A custom confirmer marks a decline or cancel by rejecting with
261
- `paymentProviderError(message, 'declined' | 'cancelled')`. Any other rejection
262
- counts as a payment that may have gone through, so the order is kept.
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.
263
380
 
264
- The full SDK is re-exported from this package — `ttf`, `api`, `hooks`, `dtos`,
265
- `logic`, and the engine. See the [SDK reference](https://github.com/theticketfairy/tf-shared-libraries/blob/main/packages/tf-checkout-shared/README.md).
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.
266
384
 
267
385
  ## Rewards
268
386
 
269
387
  The native analogue of the web Rewards section: standing, the referral tree,
270
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.
271
391
 
272
392
  ```tsx
273
393
  import { Confirmation, Rewards } from 'tf-checkout-react-native'
@@ -277,7 +397,8 @@ import { Confirmation, Rewards } from 'tf-checkout-react-native'
277
397
  ```
278
398
 
279
399
  When `<EmbeddedCheckout/>` renders the receipt itself, pass the block through
280
- `confirmationChildren`:
400
+ `confirmationChildren`. In this example the host supplies `eventId` and
401
+ `MyRewards`:
281
402
 
282
403
  ```tsx
283
404
  <EmbeddedCheckout eventId={eventId} confirmationChildren={<MyRewards />} />
@@ -289,8 +410,7 @@ Native adaptations, and why:
289
410
  web SVG uses. Adding `react-native-svg` would be a new native peer for every
290
411
  customer app; a dot-and-line picture does not need one.
291
412
  - **One share button.** Per-channel buttons collapse into `Share.share()` — the
292
- OS sheet already lists every installed app, which is what the web's share
293
- sheet channel reaches for through the browser.
413
+ OS sheet presents the eligible share targets available on that device.
294
414
  - **Copy comes in as props**, this package's idiom. `translate`, or `locale` and
295
415
  `messages` for the built-in `rewards.*` catalogue.
296
416
 
@@ -299,13 +419,14 @@ Native adaptations, and why:
299
419
  `SeatMap` uses the existing WebView peer to render internal charts or the event's
300
420
  Seats.io chart, with the shared core owning selection, holds and reservations.
301
421
  It requires the matching backend seating/session contracts. Provider-backed hold
302
- and deployed-cart acceptance remain separate checks; see the
303
- [seating contract and evidence](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/integration/app-sdk.md#seat-chart-and-ticket-refund-checkpoint--25-october-2026).
422
+ and deployed-cart acceptance remain separate checks.
304
423
 
305
- ## Known gaps vs web
424
+ ## Known gaps vs hosted checkout
306
425
 
307
- - **Smooth scroll** — no `scrollIntoView` equivalent, so `autoScroll` has no
308
- native counterpart.
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.
309
430
 
310
431
  ## Development
311
432
 
@@ -323,24 +444,22 @@ Both need a dev client. Build one with:
323
444
  cd example && pnpm run ios
324
445
  ```
325
446
 
326
- Set credentials in `example/.env` (`EXPO_PUBLIC_`-prefixed); see
327
- `example/.env.example`.
447
+ Use only public, non-secret configuration in `example/.env`
448
+ (`EXPO_PUBLIC_`-prefixed); never put a confidential credential in the app.
328
449
 
329
450
  The example's `metro.config.js` pins React, react-query, and Stripe to single
330
451
  copies. That is a **workspace-only** concern — it exists because the example
331
- consumes the library through a symlink. Consumers installing from npm need
332
- nothing of the sort.
452
+ consumes the library through a symlink. An npm host should still verify that its
453
+ dependency tree resolves the intended single instances.
333
454
 
334
455
  ---
335
456
 
336
- Part of the [TicketFairy checkout monorepo](https://github.com/theticketfairy/tf-shared-libraries/blob/main/README.md).
337
-
457
+ The checkout monorepo is available to authorised repository collaborators.
338
458
 
339
459
  Public checkout sign-in uses event-scoped server-only buyer binding, without a bundled
340
460
  client secret. The checkout's auth modal supplies the event automatically; standalone
341
461
  `AuthModal` callers pass `checkoutEventId` and can receive the narrow
342
462
  `onCheckoutAuthenticated(CheckoutBuyerProfile)` callback. Password/signup/email/phone
343
463
  proofs still complete required two-factor verification. These credentials cannot read
344
- private account preferences or tickets. See the [shared identity contract](https://github.com/theticketfairy/tf-shared-libraries/blob/main/packages/tf-checkout-shared/README.md) for
345
- scope, retry, logout and configuration ownership. Existing confidential OAuth clients
464
+ private account preferences or tickets. Existing confidential OAuth clients
346
465
  are trusted-server integrations; never distribute their secrets in browser/native code.