tf-checkout-react 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 +102 -96
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,44 +1,36 @@
1
1
  # tf-checkout-react
2
2
 
3
- The TicketFairy checkout for the web. Drop in one component and you have ticket
4
- selection, add-ons, buyer details, payment, ID verification, and the receipt —
5
- or compose the sections yourself.
6
-
7
- The 2.x source is maintained in [`tf-shared-libraries/packages/tf-checkout-react`](https://github.com/theticketfairy/tf-shared-libraries/tree/main/packages/tf-checkout-react).
8
- The core is private and bundled into both platform packages. Use React 18+ and
9
- React Query 5 on web; the native requirements below reflect its supported build.
3
+ The Ticket Fairy checkout for the web. Use the embedded flow or compose its
4
+ ticket selection, add-ons, buyer details, payment and confirmation sections.
5
+ Available features depend on the event, backend and payment provider.
6
+
7
+ The 2.x source is maintained in `tf-shared-libraries/packages/tf-checkout-react`;
8
+ the source repository requires access.
9
+ The shared core is private and bundled into both platform packages. Use React
10
+ 18+, React DOM 18+ and React Query 5 on web.
10
11
  Never put a confidential OAuth client secret in browser or mobile code. Public
11
12
  checkout integrations use registered SDK client IDs and owned buyer sessions.
12
13
 
13
- ## Prepared paired beta.22
14
-
15
- [Beta.22 source preparation](../../docs/releases/2.0.0-beta.22.md) adds optional
16
- captured push-registration context to shared sign-out. Web checkout, storage and
17
- cache defaults are unchanged. The native package requires an explicit secure
18
- storage adapter; that migration does not apply to web. Beta.22 is not published
19
- yet; the verified beta.21 installation below remains the registry checkpoint.
20
-
21
14
  ## Install
22
15
 
23
- ### Verified paired prerelease 2.0.0-beta.21
16
+ This README describes `2.0.0-beta.23`, the paired 2.x prerelease. Pin it
17
+ explicitly to use this version. `beta` and `latest` are mutable npm tags, not
18
+ guarantees about the highest version; an unqualified install can still resolve
19
+ a stable 1.x package. Check the current dist-tags when choosing a version.
20
+ Publication does not by itself establish backend rollout, device compatibility
21
+ or provider/payment acceptance.
24
22
 
25
- Both SDKs are registry-verified as `2.0.0-beta.21` from exact tagged shared source
26
- `d7de6a71058cc293e6d521d52b42d9911768d1c2`, through protected paired pipeline15.
27
- The [canonical receipt](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/releases/2.0.0-beta.21.registry.json)
28
- records exact archive/member/SHA/SRI proof. Both stable `latest` tags are unchanged.
29
- Pin the exact version below; `@beta` follows the most recent prerelease. Publication
30
- does not establish API rollout, app delivery or device/provider acceptance.
23
+ ```bash
24
+ npm view tf-checkout-react dist-tags
25
+ ```
31
26
 
32
27
  This version preserves quantities across rotating encrypted ticket IDs using
33
28
  the API's optional canonical `selectionKey`, while quantity/change APIs and cart
34
29
  requests retain the latest encrypted IDs. Deploy and verify the canonical
35
30
  base/dated key and dated numeric context before relying on that behavior:
36
31
  `display_price`, `price_with_fees`, `fee_included`, `price_currency` and
37
- `price_symbol`. No localized price parsing or base-price/cap substitution is
38
- used. Missing optional keys retain opaque-ID compatibility, without a
39
- rotation-retention claim. The [beta.21 notes](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/releases/2.0.0-beta.21.md)
40
- and [app integration contract](https://github.com/theticketfairy/tf-shared-libraries/blob/main/docs/integration/app-sdk.md)
41
- record API rollout requirements and separate deployment/provider/device gates.
32
+ `price_symbol`. Missing optional keys retain opaque-ID compatibility without a
33
+ rotation-retention claim. The deployed API must provide and validate that data.
42
34
 
43
35
  Post-purchase upgrade waiting is a separate server-owned contract. Only valid
44
36
  change-options targets can expose Join or Leave; malformed target IDs, labels
@@ -47,14 +39,18 @@ must explicitly offer that organizer-configured flow; local receiver validation
47
39
  does not prove its deployed availability or a completed join.
48
40
 
49
41
  ```bash
50
- npm install tf-checkout-react@2.0.0-beta.21 @tanstack/react-query react react-dom
42
+ npm install tf-checkout-react@2.0.0-beta.23 @tanstack/react-query react react-dom
51
43
  ```
52
44
 
53
- `@tanstack/react-query`, `react`, and `react-dom` are peer dependencies, so
54
- there is exactly one copy in your app. Everything else — the SDK core, Stripe.js,
55
- Xendit, the seat map — ships inside the package.
45
+ `@tanstack/react-query`, `react`, and `react-dom` are peer dependencies. Install
46
+ compatible versions in the host and ensure each context provider uses the same
47
+ React and React Query instances as the SDK. The private shared core and seat
48
+ renderer are bundled; npm installs the package's other declared dependencies.
49
+ Payment-provider scripts and account configuration are separate requirements.
56
50
 
57
- Styles load automatically. There is no CSS import to remember.
51
+ The entry imports its stylesheet, so the host bundler must support CSS imports.
52
+ The stylesheet is also exported as `tf-checkout-react/checkout.css` for hosts
53
+ that need to import it explicitly.
58
54
 
59
55
  ## Use it
60
56
 
@@ -73,7 +69,7 @@ export const App = () => (
73
69
  )
74
70
  ```
75
71
 
76
- That is the whole integration. `configureCheckout` runs once at app start and
72
+ `configureCheckout` runs once at app start and
77
73
  defaults token storage to `localStorage` (SSR-safe — it no-ops on the server).
78
74
 
79
75
  ### Environments
@@ -91,7 +87,7 @@ configureCheckout({ env: 'STAG', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret:
91
87
  | Prop | Type | Default | Does |
92
88
  | ----------------------------------- | ----------------------------------- | ----------- | ----------------------------------------------------------------------- |
93
89
  | `eventId` | `string` | — | The event to sell. Required. |
94
- | `onComplete` | `(order: PlaceOrderResult) => void` | — | Fires once payment settles. |
90
+ | `onComplete` | `(order: PlaceOrderResult) => void` | — | Receives the order result, including a possible pending payment. |
95
91
  | `confirmationMode` | `'inline' \| 'redirect'` | `'inline'` | `redirect` leaves routing to `onComplete`. |
96
92
  | `initialView` | `'tickets' \| 'seats'` | `'tickets'` | Opening screen — preserves an existing `/seat-map` route. |
97
93
  | `onViewChange` | `(view) => void` | — | Reports the visible view so hosts can coordinate UI. |
@@ -100,14 +96,14 @@ configureCheckout({ env: 'STAG', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret:
100
96
  | `collectTicketHolders` | `boolean` | `true` | One named holder per ticket. |
101
97
  | `collectHolderContact` | `boolean` | `true` | Also collect holder email/phone. |
102
98
  | `skipBillingWhenProfileComplete` | `boolean` | `false` | Skip billing for complete signed-in profiles. Payment is never skipped. |
103
- | `auth` | `false \| AuthOptions` | built-in | Configure or remove the login modal. |
99
+ | `auth` | `false \| EmbeddedCheckoutAuthOptions` | built-in | Configure or remove the login modal. |
104
100
  | `onLogin` | `(email?: string) => void` | — | Open your own login UI instead of the built-in modal. |
105
101
  | `privacyPolicy` | `CheckoutPrivacyPolicy` | — | Your privacy policy link and brand name. |
106
102
  | `additionalConditions` | `EventCondition[]` | — | Extra required/optional acknowledgements. |
107
103
  | `ttfOptInLabel` / `brandOptInLabel` | `string` | — | Override the marketing opt-in copy. |
108
104
  | `initialPromoCode` | `string` | — | Apply a promo code on load. |
109
105
  | `referrer` | `string \| number` | — | Attribute the sale. |
110
- | `readAttributionFromUrl` | `boolean` | — | Read promo/referrer from the query string. |
106
+ | `readAttributionFromUrl` | `boolean` | `true` | Read supported promo/referrer parameters from the query string. |
111
107
  | `showTestCardHint` | `boolean` | `false` | Show sandbox card copy. Demos only. |
112
108
  | `theme` | `CheckoutTheme` | — | Recolor via design tokens. |
113
109
  | `className` | `string` | — | Class on the root element. |
@@ -117,6 +113,19 @@ configureCheckout({ env: 'STAG', clientId: 'PUBLIC_SDK_CLIENT_ID', clientSecret:
117
113
  `<ResumedCheckout />` attaches to a cart created elsewhere — an accepted resale
118
114
  invitation, or a session restored after a redirect.
119
115
 
116
+ ### Link attribution and conditional fields
117
+
118
+ The web ticket picker reads `promo` or `promo_code`, and `ttf_r` or
119
+ `referrer_id`, unless URL attribution is disabled. Explicit props take
120
+ precedence. It does not classify an ambiguous numeric `?r=` link. A numeric
121
+ value can represent a promotion or referral; do not pass it as `referrer`
122
+ without authoritative classification. Native hosts must supply explicit
123
+ attribution themselves.
124
+
125
+ This beta leaves conditional custom-field requiredness to the backend and does
126
+ not reproduce all hosted-checkout conditional visibility. Verify the event's
127
+ field behavior against the deployed checkout before relying on parity.
128
+
120
129
  ## Theming
121
130
 
122
131
  Pass design tokens; the component maps them to CSS variables.
@@ -136,13 +145,15 @@ Tokens cover color (`accent`, `accentHover`, `accentContrast`, `surface`,
136
145
  `surfaceMuted`, `text`, `muted`, `border`, `error`, `success`), shape
137
146
  (`radiusCard`, `radiusControl`), and typography.
138
147
 
139
- Everything is scoped under `.tf-checkout` with `tf-checkout-*` BEM classes, so
140
- plain CSS overrides work too and nothing leaks into your page.
148
+ Checkout section styles use `.tf-checkout` and `tf-checkout-*` classes. Review
149
+ the bundled third-party phone and seat-map styles and standalone component
150
+ styles when integrating them into an existing page.
141
151
 
142
152
  ## Composing your own layout
143
153
 
144
154
  Every section reads the same `useCheckout()` store from the provider, so you can
145
- arrange them however you like.
155
+ arrange them however you like. Mount this under a `QueryClientProvider` as in
156
+ the quickstart.
146
157
 
147
158
  ```tsx
148
159
  import {
@@ -152,24 +163,19 @@ import {
152
163
  BillingInfo,
153
164
  Payment,
154
165
  OrderSummary,
155
- useCheckout,
156
166
  } from 'tf-checkout-react'
157
167
 
158
- const MyCheckout = () => {
159
- const checkout = useCheckout({ eventId: '14304' })
160
-
161
- return (
162
- <CheckoutProvider theme={{ colorScheme: 'dark' }}>
163
- <Tickets />
164
- <AddOns />
165
- <div className="my-two-column">
166
- <BillingInfo />
167
- <OrderSummary />
168
- </div>
169
- <Payment />
170
- </CheckoutProvider>
171
- )
172
- }
168
+ const MyCheckout = () => (
169
+ <CheckoutProvider theme={{ colorScheme: 'dark' }}>
170
+ <Tickets eventId="14304" />
171
+ <AddOns eventId="14304" />
172
+ <div className="my-two-column">
173
+ <BillingInfo />
174
+ <OrderSummary />
175
+ </div>
176
+ <Payment />
177
+ </CheckoutProvider>
178
+ )
173
179
  ```
174
180
 
175
181
  **Sections** — `Tickets`, `SeatMap`, `AddOns`, `BillingInfo`, `Payment`,
@@ -190,7 +196,7 @@ leaderboard, who their link has brought in, what they can still win, their pack,
190
196
  and the card they share.
191
197
 
192
198
  `<OrderConfirmation/>` renders it for you whenever the confirmation response
193
- carries a `rewards` object:
199
+ carries a `rewards` object. The host supplies `hash` in these examples:
194
200
 
195
201
  ```tsx
196
202
  <OrderConfirmation orderHash={hash} /> // rewards included
@@ -206,12 +212,12 @@ import { Rewards } from 'tf-checkout-react'
206
212
 
207
213
  It fetches through `ttf` rather than react-query, so it works on a confirmation
208
214
  page with no `<QueryClientProvider>`. If your app already has one, fetch with
209
- `hooks.useOrderRewards(hash)` and pass the result in as `data`.
215
+ `hooks.useOrderRewards(hash)` and pass the query's `data` payload to `Rewards`.
210
216
 
211
217
  **Sharing.** The buttons are the channels the organiser enabled, filtered to
212
- what this browser can do. On a phone the OS share sheet leads and carries the
213
- share card as a file; elsewhere the row is link intents, with copy as the last
214
- resort so a buyer is never left unable to share.
218
+ what this browser can do. On a phone the OS share sheet is attempted and may
219
+ include the share card as a file. Other options use link intents or copy; the
220
+ browser or user can deny an individual share or clipboard operation.
215
221
 
216
222
  **Snapchat is opt-in.** It loads a third-party script (`sdk.snapkit.com`) that
217
223
  your page's content policy has to allow, so it renders only when you ask:
@@ -232,40 +238,38 @@ in `en-GB` and `es-MX`.
232
238
  `useCheckout()` gives you the whole flow with no UI at all.
233
239
 
234
240
  ```tsx
235
- const checkout = useCheckout({ eventId: '14304' })
241
+ import { useCheckout, type TicketSelection } from 'tf-checkout-react'
236
242
 
237
- await checkout.setTickets('14304', [{ ticketTypeId: '1', quantity: 2 }])
238
- checkout.setBilling({ firstName, lastName, email, phone })
239
- checkout.setHolders(holders)
240
- checkout.setConsent({ ttfOptIn: true, brandOptIn: false })
241
- const order = await checkout.placeOrder('card')
243
+ function Reserve({ eventId, selections }: { eventId: string; selections: TicketSelection[] }) {
244
+ const checkout = useCheckout()
245
+ return <button onClick={() => { void checkout.setTickets(eventId, selections) }}>Reserve</button>
246
+ }
242
247
  ```
243
248
 
244
- It exposes the live snapshot (status, cart, pricing, errors), `isBusy`,
249
+ Build `selections` from the current event catalog, then collect applicable
250
+ billing, holder and consent data before calling `placeOrder`. The hook exposes
251
+ the live snapshot (status, cart, pricing, errors), `isBusy`,
245
252
  `secondsLeft` for the reservation countdown, `buyerFields` (the extra fields
246
253
  this event gates — render them from data), `missingRequiredAddonNames`, and
247
254
  `isLoggedIn`.
248
255
 
249
- `placeOrder` is retry-safe: one order, one charge, however many times the button
250
- is pressed.
256
+ The engine coordinates concurrent placement and retains an order when a payment
257
+ outcome is uncertain. Confirm the authoritative order and provider status before
258
+ retrying or fulfilling; this is not a guarantee of exactly-once charging across
259
+ all hosts and providers.
251
260
 
252
261
  ### Starting a new checkout
253
262
 
254
- `reset()` discards the cart. It returns nothing, so
255
- `useEffect(() => checkout.reset(), [eventId])` works as it always has. Before
256
- any money moves it resets at once. While a payment is going through, it waits
257
- for the payment to finish, so the payment never lands in a new cart. An order
258
- that is not confirmed yet is kept when it is charged, or when a payment step
259
- failed without an answer that nothing was charged (a network error, a
260
- timeout, or a Stripe error that is not a decline or a cancel). A Razorpay or
261
- redirect payment page the buyer closes is kept only when a check of the order
262
- fails or finds a payment; otherwise it is a cancel. Pressing Pay again then
263
- finishes the order, or pays as usual when nothing was paid. A server refusal
264
- of the order, such as an expired reservation, releases it.
265
-
266
- To know which happened, await `resetWhenSettled()` instead. It resolves `true`
267
- once the checkout is reset, or `false` when it kept the order. In that case,
268
- show the buyer `PAID_ORDER_PENDING_MESSAGE`. While it waits for a payment
263
+ `reset()` returns nothing and starts a reset. If payment is in flight, the
264
+ engine waits for that operation; it retains an order when its recorded state
265
+ indicates money may have moved. A closed provider page or network error is not
266
+ proof that no payment occurred. Reconcile the order with the backend and payment
267
+ provider before retrying or fulfilling it.
268
+
269
+ To know whether reset completed, await `resetWhenSettled()` instead. It resolves
270
+ `true` after reset or `false` when reset cannot proceed, including a retained
271
+ order or a retired checkout. For a payment that may have moved, show the buyer
272
+ `PAID_ORDER_PENDING_MESSAGE` and reconcile it. While it waits for a payment
269
273
  (`status === 'placing'`), tell the buyer, for example "Finishing your
270
274
  payment…":
271
275
 
@@ -274,16 +278,18 @@ if (checkout.status === 'placing') setNotice('Finishing your payment…')
274
278
  if (!(await checkout.resetWhenSettled())) setNotice(PAID_ORDER_PENDING_MESSAGE)
275
279
  ```
276
280
 
277
- A custom `providerPage` must resolve `'dismiss'` when it closes or unmounts, and
278
- any other custom port must settle then too, or `resetWhenSettled()` waits for it.
281
+ A custom `providerPage` must resolve `{ event: 'dismiss' }` when it closes or
282
+ unmounts, and other custom ports must settle too, or `resetWhenSettled()` waits.
279
283
 
280
- A custom confirmer marks a decline or cancel by rejecting with
281
- `paymentProviderError(message, 'declined' | 'cancelled')`. Any other rejection
282
- counts as a payment that may have gone through, so the order is kept.
284
+ A custom confirmer can classify a failure with
285
+ `paymentProviderError(message, outcome)`, where `outcome` is `declined`,
286
+ `cancelled`, `notStarted` or `unknown`. Use the first three only when the
287
+ provider confirms that no charge occurred. An unknown outcome needs
288
+ reconciliation, not an assumption that payment failed.
283
289
 
284
- For direct API access, the full SDK is re-exported from this package — `ttf`,
285
- `api`, `hooks`, `dtos`, `logic`, and the engine. See the
286
- [SDK reference](https://github.com/theticketfairy/tf-shared-libraries/blob/main/packages/tf-checkout-shared/README.md).
290
+ For direct API access, the shared SDK exports are available from this package,
291
+ including `ttf`, hooks, types and the engine. The package's TypeScript
292
+ declarations describe its public exports.
287
293
 
288
294
  ## Card fields
289
295
 
@@ -303,11 +309,12 @@ pnpm run start:react
303
309
  `pnpm run playground:react` opens the docs-style playground instead, with a
304
310
  live preview and source for each panel.
305
311
 
306
- Set credentials in `example/.env`; see `example/.env.example`.
312
+ Use only public, non-secret configuration in `example/.env`; never put a
313
+ confidential credential in browser code.
307
314
 
308
315
  ---
309
316
 
310
- Part of the [TicketFairy checkout monorepo](https://github.com/theticketfairy/tf-shared-libraries/blob/main/README.md).
317
+ The checkout monorepo is available to authorised repository collaborators.
311
318
 
312
319
 
313
320
  Public checkout sign-in uses event-scoped server-only buyer binding, without a bundled
@@ -315,6 +322,5 @@ client secret. The checkout's auth modal supplies the event automatically; stand
315
322
  `AuthModal` callers pass `checkoutEventId` and can receive the narrow
316
323
  `onCheckoutAuthenticated(CheckoutBuyerProfile)` callback. Password/signup/email/phone
317
324
  proofs still complete required two-factor verification. These credentials cannot read
318
- 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
319
- scope, retry, logout and configuration ownership. Existing confidential OAuth clients
325
+ private account preferences or tickets. Existing confidential OAuth clients
320
326
  are trusted-server integrations; never distribute their secrets in browser/native code.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.0.0-beta.22",
2
+ "version": "2.0.0-beta.23",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",