tf-checkout-react-native 2.0.0-beta.22 → 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.
Files changed (2) hide show
  1. package/README.md +201 -120
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,53 +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
- ## Prepared native storage change — beta.22
17
-
18
- The prepared paired beta.22 prerelease requires an explicit synchronous credential-storage
19
- adapter. Native `configureCheckout` and its root `configure` alias no longer create
20
- an unencrypted MMKV store. The MMKV factory/default exports and peer dependency
21
- are removed. This is a native beta breaking change; web defaults are unchanged.
22
- See [beta.22 release preparation](../../docs/releases/2.0.0-beta.22.md) and
23
- [secure native setup and migration](../../docs/integration/native-session-storage.md).
24
-
25
- The replacement release is not published yet. The beta.21 receipt below remains
26
- the last verified registry checkpoint; its published artifact still has the older
27
- MMKV dependency. The following setup examples describe current source, not an
28
- in-place change to an already published version.
29
-
30
18
  ## Install
31
19
 
32
- ### Last verified paired prerelease 2.0.0-beta.21
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.
33
26
 
34
- Both SDKs are registry-verified as `2.0.0-beta.21` from exact tagged shared source
35
- `d7de6a71058cc293e6d521d52b42d9911768d1c2`, through protected paired pipeline15.
36
- The [canonical receipt](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/releases/2.0.0-beta.21.registry.json)
37
- records exact archive/member/SHA/SRI proof. Both stable `latest` tags are unchanged.
38
- Pin the exact version below; `@beta` follows the most recent prerelease. Publication
39
- does not establish API rollout, app delivery or device/provider acceptance.
27
+ ```bash
28
+ npm view tf-checkout-react-native dist-tags
29
+ ```
40
30
 
41
31
  This version preserves quantities across rotating encrypted ticket IDs using
42
32
  the API's optional canonical `selectionKey`, while quantity/change APIs and cart
43
33
  requests retain the latest encrypted IDs. Deploy and verify the canonical
44
34
  base/dated key and dated numeric context before relying on that behavior:
45
35
  `display_price`, `price_with_fees`, `fee_included`, `price_currency` and
46
- `price_symbol`. No localized price parsing or base-price/cap substitution is
47
- used. Missing optional keys retain opaque-ID compatibility, without a
48
- rotation-retention claim. The [beta.21 notes](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/releases/2.0.0-beta.21.md)
49
- and [app integration contract](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/integration/app-sdk.md)
50
- 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.
51
38
 
52
39
  Post-purchase upgrade waiting is a separate server-owned contract. Only valid
53
40
  change-options targets can expose Join or Leave; malformed target IDs, labels
@@ -55,10 +42,10 @@ or existing entries are ignored. A sold-out row does not enable it. The backend
55
42
  must explicitly offer that organizer-configured flow; local receiver validation
56
43
  does not prove its deployed availability or a completed join.
57
44
 
58
- After the replacement paired prerelease is published:
45
+ For an Expo host with compatible React and React Native versions:
59
46
 
60
47
  ```bash
61
- npx expo install tf-checkout-react-native@2.0.0-beta.22 \
48
+ npx expo install tf-checkout-react-native@2.0.0-beta.23 \
62
49
  @tanstack/react-query \
63
50
  @stripe/stripe-react-native \
64
51
  @react-native-clipboard/clipboard \
@@ -69,30 +56,31 @@ npx expo install tf-checkout-react-native@2.0.0-beta.22 \
69
56
  Bare React Native:
70
57
 
71
58
  ```bash
72
- npm install tf-checkout-react-native@2.0.0-beta.22 @tanstack/react-query @stripe/stripe-react-native @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
73
60
  ```
74
61
 
75
- These are **peer dependencies** on purpose. React contexts and native
76
- modules must exist exactly once in an app — a second copy of react-query means
77
- your `<QueryClientProvider>` is invisible to our hooks, and a second Stripe
78
- means a duplicate pod. As peers there is one copy and no Metro aliasing is ever
79
- 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.
80
66
 
81
- 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.
82
69
 
83
- Beta.21 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`
84
71
  for formatted ticket, add-on, bundle and accommodation descriptions and its
85
72
  public `RichText` renderer. Development and component tests use `6.3.4`; the
86
- install commands above pin that compatible version. See [native rich content](../../docs/integration/native-rich-content.md)
87
- 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.
88
75
 
89
76
  ## Requirements
90
77
 
91
78
  - React Native `0.81+` with the New Architecture enabled (the supported build cohort)
92
79
  - React `19.1+`, Node `20.19.4+`
93
- - iOS 15.1+ · Android API 24+ (Stripe SDK minimums)
94
- - A development build. **Expo Go will not work** — this package uses native
95
- 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.
96
84
 
97
85
  Add the Stripe plugin to `app.json`:
98
86
 
@@ -112,25 +100,93 @@ npx expo run:ios
112
100
 
113
101
  ## Use it
114
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
+
115
157
  ```tsx
158
+ import { useEffect, useState } from 'react'
159
+ import { Button, Text, View } from 'react-native'
116
160
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
117
161
  import { configureCheckout, EmbeddedCheckout } from 'tf-checkout-react-native'
118
162
  import { restoreCredentialStorage } from './credentialStorage'
119
163
 
120
- // Await this in the host's startup gate before rendering App or any SDK hook.
121
- export async function prepareCheckout() {
122
- const storage = await restoreCredentialStorage('my_app_checkout_prod')
123
- configureCheckout({
124
- env: 'PROD',
125
- clientId: 'PUBLIC_SDK_CLIENT_ID',
126
- clientSecret: '',
127
- storage,
128
- })
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
129
174
  }
130
175
 
131
176
  const queryClient = new QueryClient()
132
177
 
133
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>
134
190
  return (
135
191
  <QueryClientProvider client={queryClient}>
136
192
  <EmbeddedCheckout eventId="14304" />
@@ -139,16 +195,9 @@ export default function App() {
139
195
  }
140
196
  ```
141
197
 
142
- Copy the [Expo example's credential-storage mirror](example/src/credentialStorage.ts)
143
- or supply your own OS-protected adapter. The [complete example startup gate](example/src/app/_layout.tsx)
144
- waits for restoration before mounting checkout and offers retry on failure.
145
- Install `expo-secure-store` only if your host uses that example; the SDK adds no
146
- Expo storage peer. Bare React Native hosts can use their own Keychain/Keystore
147
- integration through the same `StorageAdapter` contract. The backend still validates
148
- every restored session. Setup does not read/probe storage or prove its security.
149
- The runnable example uses actual synchronous OS writes after hydration. If your
150
- host chooses asynchronous writes instead, built-in auth completion does not await
151
- the final flush automatically; follow the [host completion contract](../../docs/integration/native-session-storage.md).
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.
152
201
 
153
202
  ### Checkout sessions and account scope
154
203
 
@@ -157,7 +206,7 @@ cookie handling. Owned credentials use the SDK's header-backed session storage.
157
206
  Public checkout sign-in preserves the event-bound guest carrier and keeps its
158
207
  buyer credential server-side; it does not return a full-account ticket or grant
159
208
  private account access. First-party full-account flows explicitly use
160
- `accountAuth: true`; see the shared identity contract below.
209
+ `accountAuth: true` when the host is authorized for that scope.
161
210
 
162
211
  ### Environments
163
212
 
@@ -181,7 +230,7 @@ export async function prepareStagingCheckout() {
181
230
  | Prop | Type | Default | Does |
182
231
  | ----------------------------------- | ----------------------------------- | ----------- | --------------------------------------------- |
183
232
  | `eventId` | `string` | — | The event to sell. Required. |
184
- | `onComplete` | `(order: PlaceOrderResult) => void` | — | Fires once payment settles. |
233
+ | `onComplete` | `(order: PlaceOrderResult) => void` | — | Receives an order result, possibly pending. |
185
234
  | `confirmationMode` | `'inline' \| 'redirect'` | `'inline'` | `redirect` leaves routing to `onComplete`. |
186
235
  | `initialView` | `'tickets' \| 'seats'` | `'tickets'` | Opening screen. |
187
236
  | `onViewChange` | `(view) => void` | — | Reports the visible view. |
@@ -189,7 +238,7 @@ export async function prepareStagingCheckout() {
189
238
  | `collectTicketHolders` | `boolean` | `true` | One named holder per ticket. |
190
239
  | `collectHolderContact` | `boolean` | `true` | Also collect holder email/phone. |
191
240
  | `skipBillingWhenProfileComplete` | `boolean` | `false` | Skip billing for complete signed-in profiles. |
192
- | `auth` | `false \| AuthOptions` | built-in | Configure or remove the login modal. |
241
+ | `auth` | `EmbeddedCheckoutProps['auth']` | built-in | Configure or remove the login modal. |
193
242
  | `onLogin` | `(email?: string) => void` | — | Open your own login UI instead. |
194
243
  | `privacyPolicy` | `CheckoutPrivacyPolicy` | — | Your privacy policy link and brand name. |
195
244
  | `additionalConditions` | `EventCondition[]` | — | Extra acknowledgements. |
@@ -202,6 +251,13 @@ export async function prepareStagingCheckout() {
202
251
  `readAttributionFromUrl` is accepted for parity with web and is a no-op —
203
252
  there is no `window.location` on a device.
204
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
+
205
261
  ## Theming
206
262
 
207
263
  ```tsx
@@ -220,6 +276,7 @@ The same token names as web: color (`accent`, `surface`, `text`, `muted`,
220
276
  Every section reads the same `useCheckout()` store from the provider.
221
277
 
222
278
  ```tsx
279
+ import { ScrollView } from 'react-native'
223
280
  import {
224
281
  CheckoutProvider,
225
282
  Tickets,
@@ -227,14 +284,13 @@ import {
227
284
  BillingInfo,
228
285
  Payment,
229
286
  OrderSummary,
230
- useCheckout,
231
287
  } from 'tf-checkout-react-native'
232
288
 
233
289
  const MyCheckout = () => (
234
290
  <CheckoutProvider theme={{ colorScheme: 'dark' }}>
235
291
  <ScrollView>
236
- <Tickets />
237
- <AddOns />
292
+ <Tickets eventId="14304" />
293
+ <AddOns eventId="14304" />
238
294
  <BillingInfo />
239
295
  <OrderSummary />
240
296
  <Payment />
@@ -252,39 +308,59 @@ const MyCheckout = () => (
252
308
  **Auth** — `AuthModal`, `LoginForm`, `RegisterForm`, `ForgotPasswordForm`,
253
309
  `ResetPasswordForm`.
254
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
+
255
331
  ## Headless
256
332
 
257
333
  `useCheckout()` gives you the whole flow with no UI.
258
334
 
259
335
  ```tsx
260
- const checkout = useCheckout({ eventId: '14304' })
336
+ import { Button } from 'react-native'
337
+ import { useCheckout, type TicketSelection } from 'tf-checkout-react-native'
261
338
 
262
- await checkout.setTickets('14304', [{ ticketTypeId: '1', quantity: 2 }])
263
- checkout.setBilling({ firstName, lastName, email, phone })
264
- checkout.setConsent({ ttfOptIn: true, brandOptIn: false })
265
- 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
+ }
266
343
  ```
267
344
 
268
- `placeOrder` is retry-safe: one order, one charge, however many times the button
269
- 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.
270
351
 
271
352
  ### Starting a new checkout
272
353
 
273
- `reset()` discards the cart. It returns nothing, so
274
- `useEffect(() => checkout.reset(), [eventId])` works as it always has. Before
275
- any money moves it resets at once. While a payment is going through, it waits
276
- for the payment to finish, so the payment never lands in a new cart. An order
277
- that is not confirmed yet is kept when it is charged, or when a payment step
278
- failed without an answer that nothing was charged (a network error, a
279
- timeout, or a Stripe error that is not a decline or a cancel). A Razorpay or
280
- redirect payment page the buyer closes is kept only when a check of the order
281
- fails or finds a payment; otherwise it is a cancel. Pressing Pay again then
282
- finishes the order, or pays as usual when nothing was paid. A server refusal
283
- of the order, such as an expired reservation, releases it.
284
-
285
- To know which happened, await `resetWhenSettled()` instead. It resolves `true`
286
- once the checkout is reset, or `false` when it kept the order. In that case,
287
- 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
288
364
  (`status === 'placing'`), tell the buyer, for example "Finishing your
289
365
  payment…":
290
366
 
@@ -293,20 +369,25 @@ if (checkout.status === 'placing') setNotice('Finishing your payment…')
293
369
  if (!(await checkout.resetWhenSettled())) setNotice(PAID_ORDER_PENDING_MESSAGE)
294
370
  ```
295
371
 
296
- A custom `providerPage` must resolve `'dismiss'` when it closes or unmounts, and
297
- 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.
298
374
 
299
- A custom confirmer marks a decline or cancel by rejecting with
300
- `paymentProviderError(message, 'declined' | 'cancelled')`. Any other rejection
301
- 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.
302
380
 
303
- The full SDK is re-exported from this package — `ttf`, `api`, `hooks`, `dtos`,
304
- `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.
305
384
 
306
385
  ## Rewards
307
386
 
308
387
  The native analogue of the web Rewards section: standing, the referral tree,
309
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.
310
391
 
311
392
  ```tsx
312
393
  import { Confirmation, Rewards } from 'tf-checkout-react-native'
@@ -316,7 +397,8 @@ import { Confirmation, Rewards } from 'tf-checkout-react-native'
316
397
  ```
317
398
 
318
399
  When `<EmbeddedCheckout/>` renders the receipt itself, pass the block through
319
- `confirmationChildren`:
400
+ `confirmationChildren`. In this example the host supplies `eventId` and
401
+ `MyRewards`:
320
402
 
321
403
  ```tsx
322
404
  <EmbeddedCheckout eventId={eventId} confirmationChildren={<MyRewards />} />
@@ -328,8 +410,7 @@ Native adaptations, and why:
328
410
  web SVG uses. Adding `react-native-svg` would be a new native peer for every
329
411
  customer app; a dot-and-line picture does not need one.
330
412
  - **One share button.** Per-channel buttons collapse into `Share.share()` — the
331
- OS sheet already lists every installed app, which is what the web's share
332
- sheet channel reaches for through the browser.
413
+ OS sheet presents the eligible share targets available on that device.
333
414
  - **Copy comes in as props**, this package's idiom. `translate`, or `locale` and
334
415
  `messages` for the built-in `rewards.*` catalogue.
335
416
 
@@ -338,13 +419,14 @@ Native adaptations, and why:
338
419
  `SeatMap` uses the existing WebView peer to render internal charts or the event's
339
420
  Seats.io chart, with the shared core owning selection, holds and reservations.
340
421
  It requires the matching backend seating/session contracts. Provider-backed hold
341
- and deployed-cart acceptance remain separate checks; see the
342
- [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.
343
423
 
344
- ## Known gaps vs web
424
+ ## Known gaps vs hosted checkout
345
425
 
346
- - **Smooth scroll** — no `scrollIntoView` equivalent, so `autoScroll` has no
347
- 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.
348
430
 
349
431
  ## Development
350
432
 
@@ -362,23 +444,22 @@ Both need a dev client. Build one with:
362
444
  cd example && pnpm run ios
363
445
  ```
364
446
 
365
- Set credentials in `example/.env` (`EXPO_PUBLIC_`-prefixed); see
366
- `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.
367
449
 
368
450
  The example's `metro.config.js` pins React, react-query, and Stripe to single
369
451
  copies. That is a **workspace-only** concern — it exists because the example
370
- consumes the library through a symlink. Consumers installing from npm need
371
- 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.
372
454
 
373
455
  ---
374
456
 
375
- Part of the [TicketFairy checkout monorepo](https://github.com/theticketfairy/tf-shared-libraries/blob/main/README.md).
457
+ The checkout monorepo is available to authorised repository collaborators.
376
458
 
377
459
  Public checkout sign-in uses event-scoped server-only buyer binding, without a bundled
378
460
  client secret. The checkout's auth modal supplies the event automatically; standalone
379
461
  `AuthModal` callers pass `checkoutEventId` and can receive the narrow
380
462
  `onCheckoutAuthenticated(CheckoutBuyerProfile)` callback. Password/signup/email/phone
381
463
  proofs still complete required two-factor verification. These credentials cannot read
382
- 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
383
- scope, retry, logout and configuration ownership. Existing confidential OAuth clients
464
+ private account preferences or tickets. Existing confidential OAuth clients
384
465
  are trusted-server integrations; never distribute their secrets in browser/native code.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tf-checkout-react-native",
3
- "version": "2.0.0-beta.22",
3
+ "version": "2.0.0-beta.23",
4
4
  "description": "TheTicketFairy ReactNative Checkout library",
5
5
  "homepage": "https://github.com/theticketfairy/tf-shared-libraries#readme",
6
6
  "author": "The Ticket Fairy",