flintn-checkout 0.0.18 → 0.0.19
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 +101 -4
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +17 -12
- package/dist/index.mjs +17 -12
- package/dist/react.d.mts +20 -0
- package/dist/react.d.ts +20 -0
- package/dist/react.js +284 -243
- package/dist/react.mjs +284 -243
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install flintn-checkout
|
|
|
10
10
|
|
|
11
11
|
## Iframe Checkout
|
|
12
12
|
|
|
13
|
-
Full checkout UI rendered inside a single iframe. Includes card form, express payments (Apple Pay, PayPal), and 3DS handling.
|
|
13
|
+
Full checkout UI rendered inside a single iframe. Includes card form, express payments (Apple Pay, Google Pay, PayPal), and 3DS handling.
|
|
14
14
|
|
|
15
15
|
**React**
|
|
16
16
|
|
|
@@ -108,6 +108,92 @@ payment.mount('#payment-container');
|
|
|
108
108
|
</html>
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
### External express buttons (`expressButtons: false`)
|
|
112
|
+
|
|
113
|
+
By default the iframe renders express buttons (Apple Pay / Google Pay /
|
|
114
|
+
PayPal) inside the checkout form. Set `config.expressButtons: false` to keep
|
|
115
|
+
the iframe card-form only and render the wallet buttons directly in your own
|
|
116
|
+
page — full control over placement and layout, and wallet payment sheets open
|
|
117
|
+
from the top-level page instead of inside an iframe.
|
|
118
|
+
|
|
119
|
+
**React** — the hook mounts the buttons for you and merges wallet results
|
|
120
|
+
into the same `onPayment` / `onEvent` callbacks as the card form:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { useFlintNPayment } from 'flintn-checkout/react';
|
|
124
|
+
|
|
125
|
+
function Checkout() {
|
|
126
|
+
const {
|
|
127
|
+
containerRef,
|
|
128
|
+
applePayRef,
|
|
129
|
+
googlePayRef,
|
|
130
|
+
payPalRef,
|
|
131
|
+
expressAvailable,
|
|
132
|
+
paymentResult,
|
|
133
|
+
} = useFlintNPayment({
|
|
134
|
+
config: {
|
|
135
|
+
clientSessionId: 'your_client_session_id',
|
|
136
|
+
expressButtons: false,
|
|
137
|
+
},
|
|
138
|
+
// Single callback for wallets AND the card form
|
|
139
|
+
onPayment: (result) => console.log('Payment:', result),
|
|
140
|
+
// Single per-attempt stream for wallets AND the card form
|
|
141
|
+
onEvent: (event) => console.log('Event:', event),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div style={{ maxWidth: 440 }}>
|
|
146
|
+
{/* Your layout, your styling. Hide with CSS while unavailable —
|
|
147
|
+
do not conditionally unmount the refs. */}
|
|
148
|
+
<div style={{ display: expressAvailable ? 'block' : 'none' }}>
|
|
149
|
+
<div ref={applePayRef} />
|
|
150
|
+
<div ref={googlePayRef} />
|
|
151
|
+
<div ref={payPalRef} />
|
|
152
|
+
<hr />
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Card form iframe — no express buttons inside */}
|
|
156
|
+
<div ref={containerRef} style={{ width: '100%' }} />
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Vanilla JavaScript** — pair `createFlintNPayment` with the standalone
|
|
163
|
+
express module:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
import { createFlintNPayment, createFlintNExpressButtons } from 'flintn-checkout';
|
|
167
|
+
|
|
168
|
+
const payment = createFlintNPayment({
|
|
169
|
+
config: { clientSessionId: 'your_client_session_id', expressButtons: false },
|
|
170
|
+
onPayment: (result) => console.log('Card payment:', result),
|
|
171
|
+
});
|
|
172
|
+
payment.mount('#payment-container');
|
|
173
|
+
|
|
174
|
+
const express = createFlintNExpressButtons({
|
|
175
|
+
config: { clientSessionId: 'your_client_session_id' },
|
|
176
|
+
onPayment: (result) => console.log('Wallet payment:', result),
|
|
177
|
+
onCapability: (cap) => {
|
|
178
|
+
// Show/hide your express section based on device availability
|
|
179
|
+
document.getElementById('express-row').hidden =
|
|
180
|
+
!(cap.applePay || cap.googlePay || cap.paypal);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
express.mountExpressButtons([
|
|
184
|
+
{ method: 'APPLE_PAY', elementSelector: '#apple-pay' },
|
|
185
|
+
{ method: 'GOOGLE_PAY', elementSelector: '#google-pay' },
|
|
186
|
+
{ method: 'PAYPAL', elementSelector: '#paypal' },
|
|
187
|
+
]);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Notes:
|
|
191
|
+
|
|
192
|
+
- `formFields.formVariant` `LIST`/`RADIO` are ignored with
|
|
193
|
+
`expressButtons: false` — the express/card switch lives in your page now.
|
|
194
|
+
- After a successful wallet payment, disable or hide the card form — the
|
|
195
|
+
session is completed and further card submits will fail.
|
|
196
|
+
|
|
111
197
|
## Events — terminal vs per-attempt
|
|
112
198
|
|
|
113
199
|
The SDK fires two streams of events. Use the one that fits your use case:
|
|
@@ -246,6 +332,7 @@ Do **not** set a fixed `height` on the container — it will leave empty space b
|
|
|
246
332
|
| `styles` | `FormStyles` | No | Custom styles (colors, border radius, text) for the checkout form |
|
|
247
333
|
| `placeholders` | `FormPlaceholders` | No | Per-field placeholder text overrides (see `FormPlaceholders`) |
|
|
248
334
|
| `successRedirectUrl` | `string` | No | Redirect URL after successful payment |
|
|
335
|
+
| `expressButtons` | `boolean` | No | Default `true` — express buttons (Apple Pay / Google Pay / PayPal) render inside the iframe form. Set `false` to render the card form only and mount express buttons in your own page via `createFlintNExpressButtons`; `formFields.formVariant` `LIST`/`RADIO` are ignored in that case |
|
|
249
336
|
|
|
250
337
|
### React Hook Return Values
|
|
251
338
|
|
|
@@ -253,9 +340,13 @@ Do **not** set a fixed `height` on the container — it will leave empty space b
|
|
|
253
340
|
|-------|------|-------------|
|
|
254
341
|
| `containerRef` | `RefObject<HTMLDivElement>` | Ref to attach to container element |
|
|
255
342
|
| `isReady` | `boolean` | Widget is loaded and ready |
|
|
256
|
-
| `paymentResult` | `PaymentResult \| null` | Result after terminal `onPayment` event |
|
|
343
|
+
| `paymentResult` | `PaymentResult \| null` | Result after terminal `onPayment` event (card or wallet) |
|
|
257
344
|
| `events` | `CheckoutEvent[]` | Per-attempt event log (accumulated since mount) |
|
|
258
345
|
| `error` | `PaymentError \| null` | SDK error if any |
|
|
346
|
+
| `applePayRef` | `RefObject<HTMLDivElement>` | Apple Pay mount point — used with `expressButtons: false` |
|
|
347
|
+
| `googlePayRef` | `RefObject<HTMLDivElement>` | Google Pay mount point — used with `expressButtons: false` |
|
|
348
|
+
| `payPalRef` | `RefObject<HTMLDivElement>` | PayPal mount point — used with `expressButtons: false` |
|
|
349
|
+
| `expressAvailable` | `boolean` | At least one wallet is available on this device/session (`expressButtons: false` only) |
|
|
259
350
|
|
|
260
351
|
---
|
|
261
352
|
|
|
@@ -480,7 +571,8 @@ Validation mirrors react-hook-form `mode: 'onSubmit'`:
|
|
|
480
571
|
|
|
481
572
|
### Postal code (BIN-driven)
|
|
482
573
|
|
|
483
|
-
Some issuers require a postal code for online payments
|
|
574
|
+
Some issuers require a postal code for online payments (currently US-issued
|
|
575
|
+
cards — the requirement is resolved server-side by BIN). Once the buyer has
|
|
484
576
|
typed enough digits, the card-number field checks the BIN against the payments
|
|
485
577
|
API and reports whether a postal code is required — `postalCodeRequired`
|
|
486
578
|
(React) / `onPostalCodeRequirement` (vanilla). Show your own postal code input
|
|
@@ -646,6 +738,8 @@ interface PaymentError {
|
|
|
646
738
|
|
|
647
739
|
```typescript
|
|
648
740
|
interface SubmitOptions {
|
|
741
|
+
// Required by default — the API rejects card payments without it unless the
|
|
742
|
+
// merchant disabled the cardholder name field in the client session request.
|
|
649
743
|
cardholderName?: string;
|
|
650
744
|
email?: string;
|
|
651
745
|
billingAddress?: {
|
|
@@ -914,7 +1008,10 @@ on `createField` (or the `fields` prop of `useFlintNFields`) instead.
|
|
|
914
1008
|
|
|
915
1009
|
## Form Variants
|
|
916
1010
|
|
|
917
|
-
The iframe checkout supports three layout variants via `formFields.formVariant
|
|
1011
|
+
The iframe checkout supports three layout variants via `formFields.formVariant`.
|
|
1012
|
+
Variants only apply when express buttons render inside the iframe — with
|
|
1013
|
+
`expressButtons: false` the variant is ignored and the iframe always shows the
|
|
1014
|
+
plain card form.
|
|
918
1015
|
|
|
919
1016
|
- **DEFAULT** — Express payment methods shown above the card form with dividers.
|
|
920
1017
|
- **LIST** — Express payment methods shown first; users tap a "Credit or Debit Card" button to navigate to the card form, with a back arrow to return to the express view. Toggling emits `FORM_ENTERED` / `FORM_EXITED` UI events.
|
package/dist/index.d.mts
CHANGED
|
@@ -81,6 +81,14 @@ interface FlintNConfig {
|
|
|
81
81
|
styles?: FormStyles;
|
|
82
82
|
placeholders?: FormPlaceholders;
|
|
83
83
|
successRedirectUrl?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the iframe renders express buttons (Apple Pay / Google Pay /
|
|
86
|
+
* PayPal) inside the checkout form. Default true. Set false to render the
|
|
87
|
+
* card form only and mount express buttons in your own page via
|
|
88
|
+
* `createFlintNExpressButtons` — note `formFields.formVariant` LIST/RADIO
|
|
89
|
+
* are ignored in that case.
|
|
90
|
+
*/
|
|
91
|
+
expressButtons?: boolean;
|
|
84
92
|
}
|
|
85
93
|
declare const PaymentMethod: {
|
|
86
94
|
readonly PAYMENT_CARD: "PAYMENT_CARD";
|
package/dist/index.d.ts
CHANGED
|
@@ -81,6 +81,14 @@ interface FlintNConfig {
|
|
|
81
81
|
styles?: FormStyles;
|
|
82
82
|
placeholders?: FormPlaceholders;
|
|
83
83
|
successRedirectUrl?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the iframe renders express buttons (Apple Pay / Google Pay /
|
|
86
|
+
* PayPal) inside the checkout form. Default true. Set false to render the
|
|
87
|
+
* card form only and mount express buttons in your own page via
|
|
88
|
+
* `createFlintNExpressButtons` — note `formFields.formVariant` LIST/RADIO
|
|
89
|
+
* are ignored in that case.
|
|
90
|
+
*/
|
|
91
|
+
expressButtons?: boolean;
|
|
84
92
|
}
|
|
85
93
|
declare const PaymentMethod: {
|
|
86
94
|
readonly PAYMENT_CARD: "PAYMENT_CARD";
|
package/dist/index.js
CHANGED
|
@@ -590,14 +590,12 @@ var ExpressApi = class {
|
|
|
590
590
|
} else if (res.status === AuthorizedStatus.THREE_DS_INITIATED && res.required_action?.type === "REDIRECT_TO_URL") {
|
|
591
591
|
handlers.onThreeDs(res.required_action.redirect_url, res.id);
|
|
592
592
|
} else {
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
res.status === AuthorizedStatus.DECLINED ? "PAYMENT_DECLINED" : "PAYMENT_FAILED"
|
|
596
|
-
);
|
|
593
|
+
const fallback = res.status === AuthorizedStatus.DECLINED ? "PAYMENT_DECLINED" : "PAYMENT_FAILED";
|
|
594
|
+
const code = declineCodeOf(res.status_reason?.decline_code, fallback);
|
|
597
595
|
const isHard = res.status_reason?.decline_type === "HARD_DECLINE" || res.status_reason?.decline_type == null && res.session_retryable === false;
|
|
598
596
|
handlers.onFailed(
|
|
599
597
|
code,
|
|
600
|
-
res.status_reason?.message ??
|
|
598
|
+
res.status_reason?.message ?? (fallback === "PAYMENT_DECLINED" ? "Payment declined." : "Payment failed."),
|
|
601
599
|
res.id,
|
|
602
600
|
res.status === AuthorizedStatus.DECLINED && !isHard ? PaymentAttemptResult.SOFT_DECLINE : PaymentAttemptResult.HARD_DECLINE
|
|
603
601
|
);
|
|
@@ -1302,17 +1300,24 @@ function createFlintNExpressButtons(options) {
|
|
|
1302
1300
|
if (status === "AUTHORIZED") succeed(threeDsMethod, paymentId);
|
|
1303
1301
|
else {
|
|
1304
1302
|
const code = declineCode ?? (status === "DECLINED" ? "PAYMENT_DECLINED" : "PAYMENT_FAILED");
|
|
1305
|
-
|
|
1303
|
+
const message = status === "DECLINED" ? "Payment declined." : "Payment failed.";
|
|
1304
|
+
fail(threeDsMethod, code, message, { paymentId });
|
|
1306
1305
|
}
|
|
1307
1306
|
},
|
|
1308
|
-
onClose: () => fail(
|
|
1309
|
-
|
|
1310
|
-
|
|
1307
|
+
onClose: () => fail(
|
|
1308
|
+
threeDsMethod,
|
|
1309
|
+
"THREE_DS_CANCELLED",
|
|
1310
|
+
"3DS verification was cancelled by the buyer.",
|
|
1311
|
+
{ event: PaymentAttemptResult.CANCELLED }
|
|
1312
|
+
),
|
|
1311
1313
|
// Routed through fail() so analytics get a terminal attempt event too
|
|
1312
1314
|
// (NETWORK_ERROR — a client-side technical failure, not a decline).
|
|
1313
|
-
onPopupBlocked: () => fail(
|
|
1314
|
-
|
|
1315
|
-
|
|
1315
|
+
onPopupBlocked: () => fail(
|
|
1316
|
+
threeDsMethod,
|
|
1317
|
+
"THREE_DS_POPUP_BLOCKED",
|
|
1318
|
+
"The 3DS verification window was blocked by the browser.",
|
|
1319
|
+
{ event: PaymentAttemptResult.NETWORK_ERROR }
|
|
1320
|
+
)
|
|
1316
1321
|
});
|
|
1317
1322
|
const walletHandlers = (method) => (complete) => ({
|
|
1318
1323
|
onAuthorized: (paymentId) => {
|
package/dist/index.mjs
CHANGED
|
@@ -542,14 +542,12 @@ var ExpressApi = class {
|
|
|
542
542
|
} else if (res.status === AuthorizedStatus.THREE_DS_INITIATED && res.required_action?.type === "REDIRECT_TO_URL") {
|
|
543
543
|
handlers.onThreeDs(res.required_action.redirect_url, res.id);
|
|
544
544
|
} else {
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
res.status === AuthorizedStatus.DECLINED ? "PAYMENT_DECLINED" : "PAYMENT_FAILED"
|
|
548
|
-
);
|
|
545
|
+
const fallback = res.status === AuthorizedStatus.DECLINED ? "PAYMENT_DECLINED" : "PAYMENT_FAILED";
|
|
546
|
+
const code = declineCodeOf(res.status_reason?.decline_code, fallback);
|
|
549
547
|
const isHard = res.status_reason?.decline_type === "HARD_DECLINE" || res.status_reason?.decline_type == null && res.session_retryable === false;
|
|
550
548
|
handlers.onFailed(
|
|
551
549
|
code,
|
|
552
|
-
res.status_reason?.message ??
|
|
550
|
+
res.status_reason?.message ?? (fallback === "PAYMENT_DECLINED" ? "Payment declined." : "Payment failed."),
|
|
553
551
|
res.id,
|
|
554
552
|
res.status === AuthorizedStatus.DECLINED && !isHard ? PaymentAttemptResult.SOFT_DECLINE : PaymentAttemptResult.HARD_DECLINE
|
|
555
553
|
);
|
|
@@ -1254,17 +1252,24 @@ function createFlintNExpressButtons(options) {
|
|
|
1254
1252
|
if (status === "AUTHORIZED") succeed(threeDsMethod, paymentId);
|
|
1255
1253
|
else {
|
|
1256
1254
|
const code = declineCode ?? (status === "DECLINED" ? "PAYMENT_DECLINED" : "PAYMENT_FAILED");
|
|
1257
|
-
|
|
1255
|
+
const message = status === "DECLINED" ? "Payment declined." : "Payment failed.";
|
|
1256
|
+
fail(threeDsMethod, code, message, { paymentId });
|
|
1258
1257
|
}
|
|
1259
1258
|
},
|
|
1260
|
-
onClose: () => fail(
|
|
1261
|
-
|
|
1262
|
-
|
|
1259
|
+
onClose: () => fail(
|
|
1260
|
+
threeDsMethod,
|
|
1261
|
+
"THREE_DS_CANCELLED",
|
|
1262
|
+
"3DS verification was cancelled by the buyer.",
|
|
1263
|
+
{ event: PaymentAttemptResult.CANCELLED }
|
|
1264
|
+
),
|
|
1263
1265
|
// Routed through fail() so analytics get a terminal attempt event too
|
|
1264
1266
|
// (NETWORK_ERROR — a client-side technical failure, not a decline).
|
|
1265
|
-
onPopupBlocked: () => fail(
|
|
1266
|
-
|
|
1267
|
-
|
|
1267
|
+
onPopupBlocked: () => fail(
|
|
1268
|
+
threeDsMethod,
|
|
1269
|
+
"THREE_DS_POPUP_BLOCKED",
|
|
1270
|
+
"The 3DS verification window was blocked by the browser.",
|
|
1271
|
+
{ event: PaymentAttemptResult.NETWORK_ERROR }
|
|
1272
|
+
)
|
|
1268
1273
|
});
|
|
1269
1274
|
const walletHandlers = (method) => (complete) => ({
|
|
1270
1275
|
onAuthorized: (paymentId) => {
|
package/dist/react.d.mts
CHANGED
|
@@ -80,6 +80,14 @@ interface FlintNConfig {
|
|
|
80
80
|
styles?: FormStyles;
|
|
81
81
|
placeholders?: FormPlaceholders;
|
|
82
82
|
successRedirectUrl?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Whether the iframe renders express buttons (Apple Pay / Google Pay /
|
|
85
|
+
* PayPal) inside the checkout form. Default true. Set false to render the
|
|
86
|
+
* card form only and mount express buttons in your own page via
|
|
87
|
+
* `createFlintNExpressButtons` — note `formFields.formVariant` LIST/RADIO
|
|
88
|
+
* are ignored in that case.
|
|
89
|
+
*/
|
|
90
|
+
expressButtons?: boolean;
|
|
83
91
|
}
|
|
84
92
|
declare const PaymentMethod: {
|
|
85
93
|
readonly PAYMENT_CARD: "PAYMENT_CARD";
|
|
@@ -231,6 +239,18 @@ interface UseFlintNPaymentReturn {
|
|
|
231
239
|
paymentResult: PaymentResult | null;
|
|
232
240
|
events: Array<CheckoutEvent>;
|
|
233
241
|
error: PaymentError | null;
|
|
242
|
+
/**
|
|
243
|
+
* Express-button mount points — used only with
|
|
244
|
+
* `config.expressButtons: false`: the iframe renders the card form alone
|
|
245
|
+
* and the hook mounts wallet buttons into these refs on the merchant page.
|
|
246
|
+
* Wallet results and attempt events arrive through the same
|
|
247
|
+
* `onPayment` / `onEvent` as the card form.
|
|
248
|
+
*/
|
|
249
|
+
applePayRef: React.RefObject<HTMLDivElement | null>;
|
|
250
|
+
googlePayRef: React.RefObject<HTMLDivElement | null>;
|
|
251
|
+
payPalRef: React.RefObject<HTMLDivElement | null>;
|
|
252
|
+
/** True once at least one wallet is available on this device/session. */
|
|
253
|
+
expressAvailable: boolean;
|
|
234
254
|
}
|
|
235
255
|
declare function useFlintNPayment(options: UseFlintNPaymentOptions): UseFlintNPaymentReturn;
|
|
236
256
|
|
package/dist/react.d.ts
CHANGED
|
@@ -80,6 +80,14 @@ interface FlintNConfig {
|
|
|
80
80
|
styles?: FormStyles;
|
|
81
81
|
placeholders?: FormPlaceholders;
|
|
82
82
|
successRedirectUrl?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Whether the iframe renders express buttons (Apple Pay / Google Pay /
|
|
85
|
+
* PayPal) inside the checkout form. Default true. Set false to render the
|
|
86
|
+
* card form only and mount express buttons in your own page via
|
|
87
|
+
* `createFlintNExpressButtons` — note `formFields.formVariant` LIST/RADIO
|
|
88
|
+
* are ignored in that case.
|
|
89
|
+
*/
|
|
90
|
+
expressButtons?: boolean;
|
|
83
91
|
}
|
|
84
92
|
declare const PaymentMethod: {
|
|
85
93
|
readonly PAYMENT_CARD: "PAYMENT_CARD";
|
|
@@ -231,6 +239,18 @@ interface UseFlintNPaymentReturn {
|
|
|
231
239
|
paymentResult: PaymentResult | null;
|
|
232
240
|
events: Array<CheckoutEvent>;
|
|
233
241
|
error: PaymentError | null;
|
|
242
|
+
/**
|
|
243
|
+
* Express-button mount points — used only with
|
|
244
|
+
* `config.expressButtons: false`: the iframe renders the card form alone
|
|
245
|
+
* and the hook mounts wallet buttons into these refs on the merchant page.
|
|
246
|
+
* Wallet results and attempt events arrive through the same
|
|
247
|
+
* `onPayment` / `onEvent` as the card form.
|
|
248
|
+
*/
|
|
249
|
+
applePayRef: React.RefObject<HTMLDivElement | null>;
|
|
250
|
+
googlePayRef: React.RefObject<HTMLDivElement | null>;
|
|
251
|
+
payPalRef: React.RefObject<HTMLDivElement | null>;
|
|
252
|
+
/** True once at least one wallet is available on this device/session. */
|
|
253
|
+
expressAvailable: boolean;
|
|
234
254
|
}
|
|
235
255
|
declare function useFlintNPayment(options: UseFlintNPaymentOptions): UseFlintNPaymentReturn;
|
|
236
256
|
|