flintn-checkout 0.0.10 → 0.0.12

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
@@ -18,7 +18,7 @@ Full checkout UI rendered inside a single iframe. Includes card form, express pa
18
18
  import { useFlintNPayment } from 'flintn-checkout/react';
19
19
 
20
20
  function Checkout() {
21
- const { containerRef, isReady, paymentResult, error } = useFlintNPayment({
21
+ const { containerRef, isReady, paymentResult, attempts, error } = useFlintNPayment({
22
22
  config: {
23
23
  clientSessionId: 'your_client_session_id',
24
24
  },
@@ -29,6 +29,11 @@ function Checkout() {
29
29
  console.log('Payment failed:', result.error);
30
30
  }
31
31
  },
32
+ onEvent: (event) => {
33
+ // Per-attempt analytics — fires for every payment attempt outcome
34
+ // (initiated, soft/hard decline, 3DS, cancel, network error, success).
35
+ console.log('Attempt:', event.result, event.method, event.code);
36
+ },
32
37
  });
33
38
 
34
39
  return (
@@ -55,6 +60,9 @@ const payment = createFlintNPayment({
55
60
  console.log('Payment failed:', result.error);
56
61
  }
57
62
  },
63
+ onEvent: (event) => {
64
+ console.log('Attempt:', event.result, event.method);
65
+ },
58
66
  onReady: () => {
59
67
  console.log('Widget ready');
60
68
  },
@@ -88,6 +96,9 @@ payment.mount('#payment-container');
88
96
  onPayment: (result) => {
89
97
  console.log('Payment result:', result);
90
98
  },
99
+ onEvent: (event) => {
100
+ console.log('Attempt:', event);
101
+ },
91
102
  });
92
103
 
93
104
  payment.mount('#payment-container');
@@ -96,6 +107,92 @@ payment.mount('#payment-container');
96
107
  </html>
97
108
  ```
98
109
 
110
+ ## Events — terminal vs per-attempt
111
+
112
+ The SDK fires two streams of events. Use the one that fits your use case:
113
+
114
+ ### `onPayment` — terminal (session-final)
115
+
116
+ Fires **once** when the checkout session reaches a final state. Use this for redirecting, marking the order done, sending receipts, and conversion-failure analytics.
117
+
118
+ ```ts
119
+ onPayment(result: PaymentResult)
120
+ ```
121
+
122
+ Fires when:
123
+ - Payment succeeds → `status: 'PAYMENT_SUCCESS'`
124
+ - Session cannot continue (non-retryable decline, network error, SDK error, missing payment id, 3DS failure) → `status: 'PAYMENT_ERROR'`
125
+
126
+ **Does not fire** for soft declines, hard declines with a retryable session, or buyer cancellations — the buyer can still try another card or method.
127
+
128
+ ### `onEvent` — per-attempt analytics
129
+
130
+ Fires for **every payment attempt outcome**, including ones where the session continues. Use for funnel analytics, retry tracking, A/B test instrumentation, fraud signals.
131
+
132
+ ```ts
133
+ onEvent(event: PaymentAttempt)
134
+ ```
135
+
136
+ The `result` discriminator describes what happened at this step:
137
+
138
+ | `result` | When | Terminal `onPayment` follows? |
139
+ |---|---|---|
140
+ | `'INITIATED'` | Buyer clicked Pay / Apple Pay / PayPal and validation passed | — |
141
+ | `'THREE_DS_INITIATED'` | Authorize returned `THREE_DS_INITIATED`; 3DS challenge is opening | — |
142
+ | `'AUTHORIZED'` | Authorize succeeded | ✅ `PAYMENT_SUCCESS` |
143
+ | `'SOFT_DECLINE'` | Decline with `decline_type: SOFT_DECLINE` — buyer can retry | only if session is non-retryable |
144
+ | `'HARD_DECLINE'` | Decline with `decline_type: HARD_DECLINE` — card permanently bad | only if session is non-retryable |
145
+ | `'CANCELLED'` | Buyer dismissed Apple Pay sheet / closed PayPal popup / closed 3DS dialog | — |
146
+ | `'NETWORK_ERROR'` | Authorize HTTP failure, SDK crashed, missing token, unknown status | ✅ `PAYMENT_ERROR` |
147
+
148
+ A terminal `onPayment` event always fires *after* the corresponding `onEvent` for `AUTHORIZED` / `NETWORK_ERROR` / non-retryable declines / 3DS failures.
149
+
150
+ **Example funnel — soft decline → retry success:**
151
+
152
+ ```
153
+ onEvent → { result: 'INITIATED', method: 'PAYMENT_CARD' }
154
+ onEvent → { result: 'SOFT_DECLINE', method: 'PAYMENT_CARD', code: 'do_not_honor', message: 'Payment declined.' }
155
+ // buyer retries with another card
156
+ onEvent → { result: 'INITIATED', method: 'PAYMENT_CARD' }
157
+ onEvent → { result: 'AUTHORIZED', method: 'PAYMENT_CARD', paymentId: 'pay_...' }
158
+ onPayment → { status: 'PAYMENT_SUCCESS', data: 'pay_...' }
159
+ ```
160
+
161
+ **Example — Apple Pay buyer cancel (DEFAULT variant):**
162
+
163
+ ```
164
+ onEvent → { result: 'INITIATED', method: 'APPLE_PAY' }
165
+ onEvent → { result: 'CANCELLED', method: 'APPLE_PAY', message: 'Buyer dismissed Apple Pay' }
166
+ // no onPayment — session stays alive, buyer can pick another method
167
+ ```
168
+
169
+ **Example — PayPal popup → 3DS → success:**
170
+
171
+ ```
172
+ onEvent → { result: 'INITIATED', method: 'PAYPAL' }
173
+ onEvent → { result: 'THREE_DS_INITIATED', method: 'PAYPAL', paymentId: 'pay_...' }
174
+ onEvent → { result: 'AUTHORIZED', method: 'PAYPAL', paymentId: 'pay_...' }
175
+ onPayment → { status: 'PAYMENT_SUCCESS', data: 'pay_...' }
176
+ ```
177
+
178
+ ### `attempts` (React) — accumulated log
179
+
180
+ The `useFlintNPayment` hook also exposes `attempts: PaymentAttempt[]` — every event since mount, useful for rendering a per-attempt UI without wiring `onEvent` manually:
181
+
182
+ ```tsx
183
+ const { attempts } = useFlintNPayment({ ... });
184
+
185
+ return (
186
+ <ul>
187
+ {attempts.map((a, i) => (
188
+ <li key={i}>[{a.method}] {a.result} {a.code && `— ${a.code}`}</li>
189
+ ))}
190
+ </ul>
191
+ );
192
+ ```
193
+
194
+ The list resets when the checkout remounts (e.g. new `clientSessionId`).
195
+
99
196
  ## Container Sizing
100
197
 
101
198
  The widget auto-sizes to fit its content — you only need to set a width on the container. Height is managed by the SDK and updates automatically as the form changes state (loading, express view, card form, success).
@@ -111,7 +208,8 @@ Do **not** set a fixed `height` on the container — it will leave empty space b
111
208
  | Option | Type | Required | Default | Description |
112
209
  |--------|------|----------|---------|-------------|
113
210
  | `config` | `FlintNConfig` | Yes | — | Checkout configuration |
114
- | `onPayment` | `(result: PaymentResult) => void` | No | — | Card payment result callback |
211
+ | `onPayment` | `(result: PaymentResult) => void` | No | — | Terminal session result (success / error) |
212
+ | `onEvent` | `(event: PaymentAttempt) => void` | No | — | Per-attempt analytics signal |
115
213
  | `onReady` | `() => void` | No | — | Widget loaded and ready |
116
214
  | `onError` | `(error: PaymentError) => void` | No | — | SDK initialization error |
117
215
  | `debug` | `boolean` | No | `false` | Enable console debug logs |
@@ -131,7 +229,8 @@ Do **not** set a fixed `height` on the container — it will leave empty space b
131
229
  |-------|------|-------------|
132
230
  | `containerRef` | `RefObject<HTMLDivElement>` | Ref to attach to container element |
133
231
  | `isReady` | `boolean` | Widget is loaded and ready |
134
- | `paymentResult` | `PaymentResult \| null` | Result after payment attempt |
232
+ | `paymentResult` | `PaymentResult \| null` | Result after terminal `onPayment` event |
233
+ | `attempts` | `PaymentAttempt[]` | Per-attempt event log (accumulated since mount) |
135
234
  | `error` | `PaymentError \| null` | SDK error if any |
136
235
 
137
236
  ---
@@ -142,6 +241,8 @@ Individual PCI-compliant input fields rendered as separate iframes. You control
142
241
 
143
242
  Each field (card number, expiry, CVV) is a separate iframe. Raw card data never touches your page.
144
243
 
244
+ > **Note:** Hosted Fields emits only the terminal `onPayment` event — there is no `onEvent` per-attempt stream. Use Iframe Checkout if you need granular attempt analytics.
245
+
145
246
  **React**
146
247
 
147
248
  ```tsx
@@ -378,6 +479,43 @@ interface PaymentError {
378
479
  }
379
480
  ```
380
481
 
482
+ ### PaymentAttempt
483
+
484
+ ```typescript
485
+ interface PaymentAttempt {
486
+ result:
487
+ | 'INITIATED'
488
+ | 'THREE_DS_INITIATED'
489
+ | 'AUTHORIZED'
490
+ | 'SOFT_DECLINE'
491
+ | 'HARD_DECLINE'
492
+ | 'CANCELLED'
493
+ | 'NETWORK_ERROR';
494
+ method:
495
+ | 'PAYMENT_CARD'
496
+ | 'GOOGLE_PAY'
497
+ | 'APPLE_PAY'
498
+ | 'PAYPAL'
499
+ | 'BANK_ACCOUNT'
500
+ | 'OTHER';
501
+ paymentId?: string; // Set when the backend has minted a payment id (auth response, 3DS, approve)
502
+ code?: string; // Processor decline code or internal error code (FNT-EC-*, FORBIDDEN, PAYPAL_ERROR, THREE_DS_FAILED, …)
503
+ message?: string; // Translated merchant-facing message for the code
504
+ }
505
+ ```
506
+
507
+ Const-object accessors are also exported if you prefer typed comparisons over string literals:
508
+
509
+ ```ts
510
+ import { AttemptResult, PaymentMethod } from 'flintn-checkout';
511
+
512
+ onEvent: (e) => {
513
+ if (e.result === AttemptResult.SOFT_DECLINE && e.method === PaymentMethod.PAYMENT_CARD) {
514
+ analytics.track('soft_decline_card', { code: e.code });
515
+ }
516
+ }
517
+ ```
518
+
381
519
  ### FieldState
382
520
  ```typescript
383
521
  interface FieldState {
@@ -472,6 +610,20 @@ The iframe checkout supports three layout variants via `formFields.formVariant`:
472
610
 
473
611
  LIST and RADIO automatically fall back to the DEFAULT layout when no express payment methods are available, since there's nothing to toggle between. The `cardButtonColor`, `cardButtonHoverColor`, and `cardButtonText` style overrides apply only to the LIST variant's "Credit or Debit Card" button.
474
612
 
613
+ ### Cancel-message behavior across variants
614
+
615
+ Express buttons (Apple Pay, PayPal) and the 3DS dialog can all be cancelled by the buyer. When that happens, an inline cancel message is shown in the card form **only when the card form is currently visible**:
616
+
617
+ | Variant | Apple Pay / PayPal cancel | 3DS dialog close |
618
+ |---|---|---|
619
+ | `DEFAULT` | Inline message shown | Inline message shown |
620
+ | `LIST` (EXPRESS view) | No inline message (form hidden) | No inline message |
621
+ | `LIST` (CARD view, after toggle) | Not reachable — cancel originates from EXPRESS view | Inline message shown |
622
+ | `RADIO` (EXPRESS active) | No inline message | No inline message |
623
+ | `RADIO` (CARD active) | Not reachable — cancel originates from EXPRESS view | Inline message shown |
624
+
625
+ Every cancellation still emits a `CANCELLED` `onEvent` regardless of variant, so analytics is unaffected.
626
+
475
627
  ## Supported Payment Methods
476
628
 
477
629
  ### Iframe Checkout
@@ -500,15 +652,6 @@ useFlintNFields({
500
652
  });
501
653
  ```
502
654
 
503
- ## Test Cards
504
-
505
- | Card Number | Result |
506
- |-------------|--------|
507
- | `4111 1111 1111 1111` | Success |
508
- | `4000 0000 0000 0002` | Declined |
509
-
510
- Use any future expiry date and any 3-digit CVV.
511
-
512
655
  ## Browser Support
513
656
 
514
657
  - Chrome (latest)
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  declare const EventType: {
2
2
  readonly WIDGET_READY: "WIDGET_READY";
3
3
  readonly WIDGET_CONFIG: "WIDGET_CONFIG";
4
+ readonly PAYMENT_ATTEMPT: "PAYMENT_ATTEMPT";
4
5
  readonly PAYMENT: "PAYMENT";
5
- readonly EXPRESS_PAYMENT: "EXPRESS_PAYMENT";
6
6
  readonly PAYMENT_SUCCESS: "PAYMENT_SUCCESS";
7
7
  readonly PAYMENT_ERROR: "PAYMENT_ERROR";
8
8
  readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
@@ -46,6 +46,32 @@ interface FlintNConfig {
46
46
  styles?: FormStyles;
47
47
  successRedirectUrl?: string;
48
48
  }
49
+ declare const PaymentMethod: {
50
+ readonly PAYMENT_CARD: "PAYMENT_CARD";
51
+ readonly GOOGLE_PAY: "GOOGLE_PAY";
52
+ readonly APPLE_PAY: "APPLE_PAY";
53
+ readonly PAYPAL: "PAYPAL";
54
+ readonly BANK_ACCOUNT: "BANK_ACCOUNT";
55
+ readonly OTHER: "OTHER";
56
+ };
57
+ type TPaymentMethod = (typeof PaymentMethod)[keyof typeof PaymentMethod];
58
+ declare const AttemptResult: {
59
+ readonly INITIATED: "INITIATED";
60
+ readonly THREE_DS_INITIATED: "THREE_DS_INITIATED";
61
+ readonly AUTHORIZED: "AUTHORIZED";
62
+ readonly SOFT_DECLINE: "SOFT_DECLINE";
63
+ readonly HARD_DECLINE: "HARD_DECLINE";
64
+ readonly CANCELLED: "CANCELLED";
65
+ readonly NETWORK_ERROR: "NETWORK_ERROR";
66
+ };
67
+ type TAttemptResult = (typeof AttemptResult)[keyof typeof AttemptResult];
68
+ interface PaymentAttempt {
69
+ result: TAttemptResult;
70
+ method: TPaymentMethod;
71
+ paymentId?: string;
72
+ code?: string;
73
+ message?: string;
74
+ }
49
75
  interface PaymentResult {
50
76
  status: 'PAYMENT_SUCCESS' | 'PAYMENT_ERROR' | 'PAYMENT_CANCELLED';
51
77
  data?: string;
@@ -62,7 +88,7 @@ interface FlintNPaymentOptions {
62
88
  origin?: string;
63
89
  config: FlintNConfig;
64
90
  onPayment?: (result: PaymentResult) => void;
65
- onExpressPayment?: (result: PaymentResult) => void;
91
+ onEvent?: (event: PaymentAttempt) => void;
66
92
  onReady?: () => void;
67
93
  onError?: (error: PaymentError) => void;
68
94
  debug?: boolean;
@@ -147,6 +173,7 @@ interface FlintNFieldInternalCallbacks {
147
173
  onFocus: (fieldType: TFieldType) => void;
148
174
  onBlur: (fieldType: TFieldType, state: FieldState) => void;
149
175
  onHeight: (fieldType: TFieldType, height: number) => void;
176
+ onError?: (err: Error) => void;
150
177
  }
151
178
  interface FlintNField {
152
179
  mount(selector: string | HTMLElement): void;
@@ -179,4 +206,4 @@ declare const validateConfig: (config: {
179
206
  }) => void;
180
207
  declare const sanitizeToken: (token: string) => string;
181
208
 
182
- export { CheckoutFormVariant, EventType, type FieldChangeEvent, FieldEventType, type FieldOptions, type FieldState, FieldType, type FieldValidationResult, type FieldsValidationResult, type FlintNConfig, type FlintNField, type FlintNFieldInternalCallbacks, type FlintNFields, type FlintNFieldsConfig, type FlintNFieldsOptions, type FlintNPayment, type FlintNPaymentOptions, type FormFields, type FormStyles, type PaymentError, type PaymentResult, type SubmitOptions, type TCheckoutFormVariant, type TEventType, type TFieldEventType, type TFieldType, buildIframeSrc, createFlintNField, createFlintNFields, createFlintNPayment, createIframeElement, parseOrigin, sanitizeToken, validateConfig };
209
+ export { AttemptResult, CheckoutFormVariant, EventType, type FieldChangeEvent, FieldEventType, type FieldOptions, type FieldState, FieldType, type FieldValidationResult, type FieldsValidationResult, type FlintNConfig, type FlintNField, type FlintNFieldInternalCallbacks, type FlintNFields, type FlintNFieldsConfig, type FlintNFieldsOptions, type FlintNPayment, type FlintNPaymentOptions, type FormFields, type FormStyles, type PaymentAttempt, type PaymentError, PaymentMethod, type PaymentResult, type SubmitOptions, type TAttemptResult, type TCheckoutFormVariant, type TEventType, type TFieldEventType, type TFieldType, type TPaymentMethod, buildIframeSrc, createFlintNField, createFlintNFields, createFlintNPayment, createIframeElement, parseOrigin, sanitizeToken, validateConfig };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  declare const EventType: {
2
2
  readonly WIDGET_READY: "WIDGET_READY";
3
3
  readonly WIDGET_CONFIG: "WIDGET_CONFIG";
4
+ readonly PAYMENT_ATTEMPT: "PAYMENT_ATTEMPT";
4
5
  readonly PAYMENT: "PAYMENT";
5
- readonly EXPRESS_PAYMENT: "EXPRESS_PAYMENT";
6
6
  readonly PAYMENT_SUCCESS: "PAYMENT_SUCCESS";
7
7
  readonly PAYMENT_ERROR: "PAYMENT_ERROR";
8
8
  readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
@@ -46,6 +46,32 @@ interface FlintNConfig {
46
46
  styles?: FormStyles;
47
47
  successRedirectUrl?: string;
48
48
  }
49
+ declare const PaymentMethod: {
50
+ readonly PAYMENT_CARD: "PAYMENT_CARD";
51
+ readonly GOOGLE_PAY: "GOOGLE_PAY";
52
+ readonly APPLE_PAY: "APPLE_PAY";
53
+ readonly PAYPAL: "PAYPAL";
54
+ readonly BANK_ACCOUNT: "BANK_ACCOUNT";
55
+ readonly OTHER: "OTHER";
56
+ };
57
+ type TPaymentMethod = (typeof PaymentMethod)[keyof typeof PaymentMethod];
58
+ declare const AttemptResult: {
59
+ readonly INITIATED: "INITIATED";
60
+ readonly THREE_DS_INITIATED: "THREE_DS_INITIATED";
61
+ readonly AUTHORIZED: "AUTHORIZED";
62
+ readonly SOFT_DECLINE: "SOFT_DECLINE";
63
+ readonly HARD_DECLINE: "HARD_DECLINE";
64
+ readonly CANCELLED: "CANCELLED";
65
+ readonly NETWORK_ERROR: "NETWORK_ERROR";
66
+ };
67
+ type TAttemptResult = (typeof AttemptResult)[keyof typeof AttemptResult];
68
+ interface PaymentAttempt {
69
+ result: TAttemptResult;
70
+ method: TPaymentMethod;
71
+ paymentId?: string;
72
+ code?: string;
73
+ message?: string;
74
+ }
49
75
  interface PaymentResult {
50
76
  status: 'PAYMENT_SUCCESS' | 'PAYMENT_ERROR' | 'PAYMENT_CANCELLED';
51
77
  data?: string;
@@ -62,7 +88,7 @@ interface FlintNPaymentOptions {
62
88
  origin?: string;
63
89
  config: FlintNConfig;
64
90
  onPayment?: (result: PaymentResult) => void;
65
- onExpressPayment?: (result: PaymentResult) => void;
91
+ onEvent?: (event: PaymentAttempt) => void;
66
92
  onReady?: () => void;
67
93
  onError?: (error: PaymentError) => void;
68
94
  debug?: boolean;
@@ -147,6 +173,7 @@ interface FlintNFieldInternalCallbacks {
147
173
  onFocus: (fieldType: TFieldType) => void;
148
174
  onBlur: (fieldType: TFieldType, state: FieldState) => void;
149
175
  onHeight: (fieldType: TFieldType, height: number) => void;
176
+ onError?: (err: Error) => void;
150
177
  }
151
178
  interface FlintNField {
152
179
  mount(selector: string | HTMLElement): void;
@@ -179,4 +206,4 @@ declare const validateConfig: (config: {
179
206
  }) => void;
180
207
  declare const sanitizeToken: (token: string) => string;
181
208
 
182
- export { CheckoutFormVariant, EventType, type FieldChangeEvent, FieldEventType, type FieldOptions, type FieldState, FieldType, type FieldValidationResult, type FieldsValidationResult, type FlintNConfig, type FlintNField, type FlintNFieldInternalCallbacks, type FlintNFields, type FlintNFieldsConfig, type FlintNFieldsOptions, type FlintNPayment, type FlintNPaymentOptions, type FormFields, type FormStyles, type PaymentError, type PaymentResult, type SubmitOptions, type TCheckoutFormVariant, type TEventType, type TFieldEventType, type TFieldType, buildIframeSrc, createFlintNField, createFlintNFields, createFlintNPayment, createIframeElement, parseOrigin, sanitizeToken, validateConfig };
209
+ export { AttemptResult, CheckoutFormVariant, EventType, type FieldChangeEvent, FieldEventType, type FieldOptions, type FieldState, FieldType, type FieldValidationResult, type FieldsValidationResult, type FlintNConfig, type FlintNField, type FlintNFieldInternalCallbacks, type FlintNFields, type FlintNFieldsConfig, type FlintNFieldsOptions, type FlintNPayment, type FlintNPaymentOptions, type FormFields, type FormStyles, type PaymentAttempt, type PaymentError, PaymentMethod, type PaymentResult, type SubmitOptions, type TAttemptResult, type TCheckoutFormVariant, type TEventType, type TFieldEventType, type TFieldType, type TPaymentMethod, buildIframeSrc, createFlintNField, createFlintNFields, createFlintNPayment, createIframeElement, parseOrigin, sanitizeToken, validateConfig };