@flopay/react 0.1.0

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 ADDED
@@ -0,0 +1,378 @@
1
+ # @flopay/react
2
+
3
+ React bindings for the FloPay SDK. Provides a context provider, drop-in checkout form components, individual element components, and hooks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @flopay/react @flopay/js @flopay/shared react react-dom
9
+ ```
10
+
11
+ Peer dependencies: `react >= 18.0.0`, `react-dom >= 18.0.0`.
12
+
13
+ ## Environment Setup
14
+
15
+ Configure which billing API the SDK uses. Choose one:
16
+
17
+ ```bash
18
+ # Option A: Environment variable (recommended)
19
+ # .env.production
20
+ NEXT_PUBLIC_FLOPAY_ENV=production
21
+
22
+ # .env.development
23
+ NEXT_PUBLIC_FLOPAY_ENV=staging
24
+ ```
25
+
26
+ ```ts
27
+ // Option B: Call at app startup
28
+ import { configureFlopay } from '@flopay/shared';
29
+ configureFlopay({ environment: 'production' });
30
+ ```
31
+
32
+ If neither is set, the SDK defaults to staging.
33
+
34
+ ## Quick Start
35
+
36
+ ### Recommended: FloPayCheckout
37
+
38
+ The simplest integration — one component, one prop:
39
+
40
+ ```tsx
41
+ import { FloPayCheckout } from '@flopay/react';
42
+
43
+ function CheckoutPage() {
44
+ return (
45
+ <FloPayCheckout
46
+ sessionId="sess_abc123"
47
+ onComplete={(result) => {
48
+ window.location.href = '/success';
49
+ }}
50
+ onError={(err) => console.error(err)}
51
+ />
52
+ );
53
+ }
54
+ ```
55
+
56
+ `FloPayCheckout` automatically fetches the session, initializes the correct payment provider, and renders a `SplitCardForm` with card fields, Apple Pay, Google Pay, and PayPal. Customer data (email, userId, name) is injected from the session.
57
+
58
+ #### Checkout Modes
59
+
60
+ `FloPayCheckout` supports three checkout modes matching the billing API's `checkoutMode` field:
61
+
62
+ - **`full`** (default) — Shows the full payment form with card fields + wallet buttons.
63
+ - **`confirm`** — Hides the payment form and shows a "Confirm Purchase" button. Uses a saved payment method on the backend.
64
+ - **`auto`** — Auto-submits with a saved payment method after the session loads. Falls back to `full` mode on failure.
65
+
66
+ ```tsx
67
+ // Confirm mode — one-click purchase with saved card
68
+ <FloPayCheckout
69
+ sessionId="sess_abc123"
70
+ checkoutMode="confirm"
71
+ confirmLabel="Complete Purchase"
72
+ onComplete={(result) => router.push('/success')}
73
+ />
74
+
75
+ // Auto mode — instant checkout, falls back to full form
76
+ <FloPayCheckout
77
+ sessionId="sess_abc123"
78
+ checkoutMode="auto"
79
+ onComplete={(result) => router.push('/success')}
80
+ onSessionCompleted={(successUrl) => router.push(successUrl)}
81
+ />
82
+ ```
83
+
84
+ The mode can also be set on the session itself via the billing API's `checkoutMode` field. The `checkoutMode` prop overrides the session value.
85
+
86
+ #### Wallet Buttons
87
+
88
+ Apple Pay, Google Pay, and PayPal are enabled by default. Toggle them with props:
89
+
90
+ ```tsx
91
+ <FloPayCheckout
92
+ sessionId="sess_abc123"
93
+ showApplePay={true} // default: true
94
+ showGooglePay={true} // default: true
95
+ showPayPal={true} // default: true
96
+ onComplete={handleSuccess}
97
+ />
98
+ ```
99
+
100
+ Apple Pay and Google Pay only render on supported devices (Apple Pay on Safari/macOS/iOS, Google Pay on Chrome).
101
+
102
+ ### Advanced: Manual Provider Setup
103
+
104
+ For full control over initialization:
105
+
106
+ ```tsx
107
+ import { loadFloPay } from '@flopay/js';
108
+ import { FloPayProvider, CheckoutForm } from '@flopay/react';
109
+
110
+ const floPayPromise = loadFloPay('pk_test_...');
111
+
112
+ function CheckoutPage() {
113
+ return (
114
+ <FloPayProvider
115
+ flopay={floPayPromise}
116
+ options={{
117
+ amount: 2999,
118
+ currency: 'usd',
119
+ }}
120
+ >
121
+ <CheckoutForm
122
+ sessionId="session_uuid"
123
+ email="user@example.com"
124
+ userId="user_1"
125
+ onComplete={(result) => {
126
+ if (result.status === 'succeeded') {
127
+ window.location.href = '/success';
128
+ }
129
+ }}
130
+ onError={(err) => console.error(err)}
131
+ />
132
+ </FloPayProvider>
133
+ );
134
+ }
135
+ ```
136
+
137
+ `CheckoutForm` handles the full payment lifecycle by default:
138
+
139
+ 1. Validate elements via `submitElements()`
140
+ 2. Tokenize card via `createPaymentMethod()` -> `pm_xxx`
141
+ 3. Create PaymentIntent via billing API
142
+ 4. Confirm card payment (handles 3D Secure)
143
+ 5. Submit tokenized body to `POST /v1/checkouts/sessions/process`
144
+ 6. If backend returns `3ds_required`, re-confirm with new client secret
145
+ 7. Resume wallet payments after redirect (PayPal)
146
+
147
+ ### Override Mode (Custom Backend)
148
+
149
+ Pass `onTokenizedBody` to handle backend submission yourself:
150
+
151
+ ```tsx
152
+ <CheckoutForm
153
+ sessionId="session_uuid"
154
+ billingApiUrl="https://billing.example.com"
155
+ email="user@example.com"
156
+ onTokenizedBody={(body) => {
157
+ // body = { id: 'pm_xxx', type: 'card', threeDSecureActionResultTokenId: 'pi_xxx' }
158
+ myCustomProcessPayment(body);
159
+ }}
160
+ />
161
+ ```
162
+
163
+ ### SplitCardForm (Split Card Fields + PayPal)
164
+
165
+ `SplitCardForm` renders separate CardNumber, CardExpiry, and CardCVC fields with an integrated PayPal button. It matches the existing checkout/StripeCardForm layout.
166
+
167
+ ```tsx
168
+ import { FloPayProvider, SplitCardForm } from '@flopay/react';
169
+ import { loadFloPay } from '@flopay/js';
170
+
171
+ const floPayPromise = loadFloPay('pk_test_...');
172
+
173
+ function CheckoutPage() {
174
+ return (
175
+ <FloPayProvider
176
+ flopay={floPayPromise}
177
+ options={{
178
+ amount: 2999,
179
+ currency: 'usd',
180
+ }}
181
+ >
182
+ <SplitCardForm
183
+ sessionId="session_uuid"
184
+ billingApiUrl="https://billing.example.com"
185
+ email="user@example.com"
186
+ userId="user_1"
187
+ totalAmount={29.99} // dollars (for PayPal Elements config)
188
+ currency="usd"
189
+ showPayPal={true} // default: true
190
+ onComplete={(result) => {
191
+ if (result.status === 'succeeded') {
192
+ window.location.href = '/success';
193
+ }
194
+ }}
195
+ onError={(err) => console.error(err)}
196
+ onFirstNameChange={(name) => setFirstName(name)}
197
+ onLastNameChange={(name) => setLastName(name)}
198
+ />
199
+ </FloPayProvider>
200
+ );
201
+ }
202
+ ```
203
+
204
+ The `SplitCardForm` layout:
205
+ 1. Wallet buttons — Apple Pay + Google Pay (via ExpressCheckoutElement in the main Elements instance)
206
+ 2. PayPal button (via ExpressCheckoutElement in a separate Elements instance)
207
+ 3. "or pay with card" divider
208
+ 4. Card Number input
209
+ 5. Card Expiry + CVC side by side
210
+ 6. Full Name input
211
+ 7. Submit button
212
+
213
+ ### PayPal Handling
214
+
215
+ PayPal requires its own Stripe Elements instance because it cannot share an Elements group that uses `paymentMethodCreation: 'manual'`. The SDK handles this in two ways:
216
+
217
+ - **`SplitCardForm`**: Automatically manages two Elements instances internally. Card fields use the provider's elements with `paymentMethodCreation: 'manual'`. PayPal renders inside a separate `<Elements>` wrapper with `captureMethod: 'manual'` and no `paymentMethodCreation`.
218
+
219
+ - **`PayPalButton`** (standalone): Must be rendered inside its own `FloPayProvider`:
220
+
221
+ ```tsx
222
+ {/* Card fields provider */}
223
+ <FloPayProvider flopay={flopay} options={{ amount, currency }}>
224
+ <SplitCardForm ... />
225
+ </FloPayProvider>
226
+
227
+ {/* PayPal provider (separate instance) */}
228
+ <FloPayProvider flopay={flopay} options={{ amount, currency, paymentMethodCreation: 'auto' }}>
229
+ <PayPalButton
230
+ sessionId="session_uuid"
231
+ billingApiUrl="https://billing.example.com"
232
+ email="user@example.com"
233
+ onComplete={() => router.push('/success')}
234
+ />
235
+ </FloPayProvider>
236
+ ```
237
+
238
+ ### Using Individual Elements
239
+
240
+ ```tsx
241
+ import { FloPayProvider, PaymentElement, CardElement, useFloPay } from '@flopay/react';
242
+
243
+ function CustomForm() {
244
+ const flopay = useFloPay();
245
+
246
+ const handleSubmit = async () => {
247
+ if (!flopay) return;
248
+ const { error } = await flopay.submitElements();
249
+ if (error) return console.error(error);
250
+ const { paymentMethodId } = await flopay.createPaymentMethod();
251
+ // Use paymentMethodId...
252
+ };
253
+
254
+ return (
255
+ <div>
256
+ <PaymentElement options={{ layout: 'tabs' }} />
257
+ <button onClick={handleSubmit}>Pay</button>
258
+ </div>
259
+ );
260
+ }
261
+ ```
262
+
263
+ ### Using Hooks
264
+
265
+ ```tsx
266
+ import { useFloPay, useElements, useCheckout } from '@flopay/react';
267
+
268
+ function PaymentStatus() {
269
+ const flopay = useFloPay(); // FloPay | null
270
+ const elements = useElements(); // FloPayElements | null
271
+ const checkout = useCheckout(); // { session, loading, error }
272
+
273
+ if (!flopay) return <div>Loading SDK...</div>;
274
+ // ...
275
+ }
276
+ ```
277
+
278
+ ## API Reference
279
+
280
+ ### Components
281
+
282
+ | Component | Description |
283
+ |-----------|-------------|
284
+ | `FloPayProvider` | Context provider. Accepts `flopay` (instance or promise), `options?`, and `children`. Creates the elements group automatically. |
285
+ | `CheckoutForm` | Drop-in form with unified PaymentElement. Self-contained by default, or override with `onTokenizedBody`. Supports `ref` for imperative `handleNextAction()`. |
286
+ | `SplitCardForm` | Split card form (CardNumber + CardExpiry + CardCvc + Full Name). Integrates PayPal via separate Elements instance. Supports `ref` for imperative `handleNextAction()`. |
287
+ | `PayPalButton` | Standalone PayPal button. Requires its own `FloPayProvider` with `paymentMethodCreation` set to something other than `'manual'`. |
288
+ | `PaymentElement` | Unified payment element (cards, wallets, etc.) |
289
+ | `CardElement` | Combined card input |
290
+ | `CardNumberElement` | Card number field |
291
+ | `CardExpiryElement` | Card expiry field |
292
+ | `CardCvcElement` | Card CVC field |
293
+ | `AddressElement` | Address input element |
294
+
295
+ ### Hooks
296
+
297
+ | Hook | Returns | Description |
298
+ |------|---------|-------------|
299
+ | `useFloPay()` | `FloPay \| null` | Current FloPay instance from context. `null` while loading. |
300
+ | `useElements()` | `FloPayElements \| null` | Current elements group from context. `null` while loading. |
301
+ | `useCheckout()` | `CheckoutState` | `{ session, loading, error }` from CheckoutContext |
302
+
303
+ ### FloPayProviderProps
304
+
305
+ | Prop | Type | Description |
306
+ |------|------|-------------|
307
+ | `flopay` | `Promise<FloPay> \| FloPay` | SDK instance or promise from `loadFloPay()` |
308
+ | `options.locale` | `string?` | Locale |
309
+ | `options.appearance` | `FloPayAppearance?` | Theme appearance |
310
+ | `options.clientSecret` | `string?` | PaymentIntent client secret (if intent already exists) |
311
+ | `options.amount` | `number?` | Amount in cents (used when no `clientSecret`) |
312
+ | `options.currency` | `string?` | ISO 4217 currency code (used when no `clientSecret`) |
313
+ | `options.paymentMethodCreation` | `'manual' \| 'auto'` | How payment methods are created |
314
+
315
+ ### CheckoutFormProps
316
+
317
+ | Prop | Type | Description |
318
+ |------|------|-------------|
319
+ | `sessionId` | `string` | Checkout session UUID |
320
+ | `billingApiUrl` | `string` | Billing API base URL |
321
+ | `email` | `string?` | User email |
322
+ | `userId` | `string?` | User ID |
323
+ | `onComplete` | `(result: PaymentResult) => void` | Success callback |
324
+ | `onError` | `(error: FloPayError) => void` | Error callback |
325
+ | `onTokenizedBody` | `(body: TokenizedBody) => void` | Override: handle backend submission yourself |
326
+ | `layout` | `'tabs' \| 'accordion' \| 'auto'` | PaymentElement layout (default: `'auto'`) |
327
+ | `submitLabel` | `string` | Button text (default: `'Pay'`) |
328
+ | `showAddress` | `boolean \| 'billing' \| 'shipping'` | Show address element (default: `false`) |
329
+ | `className` | `string?` | CSS class for form wrapper |
330
+ | `children` | `ReactNode?` | Custom submit button |
331
+ | `firstName` | `string?` | Billing first name |
332
+ | `lastName` | `string?` | Billing last name |
333
+ | `chv` | `string?` | Checkout version for A/B tracking |
334
+ | `isProcessing` | `boolean?` | External processing state |
335
+ | `error` | `string?` | External error message |
336
+ | `onErrorChange` | `(error: string \| null) => void` | Error state change callback |
337
+
338
+ ### SplitCardFormProps
339
+
340
+ Shares most props with `CheckoutFormProps`, plus:
341
+
342
+ | Prop | Type | Description |
343
+ |------|------|-------------|
344
+ | `showPayPal` | `boolean` | Show PayPal button (default: `true`) |
345
+ | `showApplePay` | `boolean` | Show Apple Pay button (default: `true`) |
346
+ | `showGooglePay` | `boolean` | Show Google Pay button (default: `true`) |
347
+ | `totalAmount` | `number` | Amount in dollars for PayPal config |
348
+ | `currency` | `string` | Currency code for PayPal config (default: `'usd'`) |
349
+ | `onFirstNameChange` | `(value: string) => void` | First name change callback |
350
+ | `onLastNameChange` | `(value: string) => void` | Last name change callback |
351
+ | `submitLabel` | `string` | Button text (default: `'CONFIRM PAYMENT'`) |
352
+
353
+ ### ElementComponentProps (shared by all element components)
354
+
355
+ | Prop | Type | Description |
356
+ |------|------|-------------|
357
+ | `className` | `string?` | CSS class for wrapper div |
358
+ | `id` | `string?` | HTML id for wrapper div |
359
+ | `style` | `CSSProperties?` | Inline styles for wrapper div |
360
+ | `options` | `Partial<ElementOptions>?` | Options for the underlying element |
361
+ | `onChange` | `(event: ElementChangeEvent) => void` | Value change handler |
362
+ | `onReady` | `() => void` | Element ready handler |
363
+ | `onFocus` | `() => void` | Focus handler |
364
+ | `onBlur` | `() => void` | Blur handler |
365
+ | `onEscape` | `() => void` | Escape key handler |
366
+
367
+ ### Types
368
+
369
+ | Type | Description |
370
+ |------|-------------|
371
+ | `FloPayProviderProps` | Props for `FloPayProvider` |
372
+ | `CheckoutFormProps` | Props for `CheckoutForm` |
373
+ | `CheckoutFormRef` | Ref type: `{ handleNextAction(clientSecret) }` |
374
+ | `SplitCardFormProps` | Props for `SplitCardForm` |
375
+ | `SplitCardFormRef` | Ref type: `{ handleNextAction(clientSecret) }` |
376
+ | `PayPalButtonProps` | Props for `PayPalButton` |
377
+ | `ElementComponentProps` | Shared props for all element components |
378
+ | `CheckoutState` | `{ session, loading, error }` |