flintn-checkout 0.0.16 → 0.0.17
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 +11 -5
- package/dist/index.js +82 -15
- package/dist/index.mjs +82 -15
- package/dist/react.js +83 -16
- package/dist/react.mjs +83 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -331,7 +331,11 @@ function CheckoutForm() {
|
|
|
331
331
|
</div>
|
|
332
332
|
<div style={{ flex: 1 }}>
|
|
333
333
|
<label>CVV</label>
|
|
334
|
-
|
|
334
|
+
{/* position/zIndex let the CVV helper panel overlay content below */}
|
|
335
|
+
<div
|
|
336
|
+
ref={cvvRef}
|
|
337
|
+
style={{ height: 40, marginBottom: 4, position: 'relative', zIndex: 1 }}
|
|
338
|
+
/>
|
|
335
339
|
{fieldErrors['cvv'] && (
|
|
336
340
|
<span style={{ color: 'red' }}>{fieldErrors['cvv']}</span>
|
|
337
341
|
)}
|
|
@@ -379,7 +383,7 @@ const fields = createFlintNFields({
|
|
|
379
383
|
|
|
380
384
|
const cardNumber = fields.createField('card-number', { placeholder: '4111 1111 1111 1111' });
|
|
381
385
|
const expiry = fields.createField('expiry', { placeholder: 'MM/YY' });
|
|
382
|
-
const cvv = fields.createField('cvv'
|
|
386
|
+
const cvv = fields.createField('cvv');
|
|
383
387
|
|
|
384
388
|
cardNumber.mount('#card-number');
|
|
385
389
|
expiry.mount('#expiry');
|
|
@@ -435,7 +439,7 @@ Additional React-only options:
|
|
|
435
439
|
|
|
436
440
|
| Property | Type | Default | Description |
|
|
437
441
|
|----------|------|---------|-------------|
|
|
438
|
-
| `placeholder` | `string` |
|
|
442
|
+
| `placeholder` | `string` | `'1234 1234 1234 1234'` / `'MM/YY'` / `'***'` | Input placeholder text |
|
|
439
443
|
| `disabled` | `boolean` | `false` | Disable the input |
|
|
440
444
|
|
|
441
445
|
### React Hook Return Values
|
|
@@ -469,6 +473,7 @@ Validation mirrors react-hook-form `mode: 'onSubmit'`:
|
|
|
469
473
|
- After first submit: errors update on every field change (revalidation)
|
|
470
474
|
- Card number changes trigger CVV revalidation (CVV length depends on card brand)
|
|
471
475
|
- All three fields (card number, expiry, CVV) are required — submitting with a missing field returns a `MISSING_FIELDS` error
|
|
476
|
+
- The CVV field ships a built-in helper: tapping the card icon expands a “where to find CVV” panel below the input. The field iframe grows to fit it, so keep the CVV container at a fixed `height` with `position: relative; z-index: 1` — the open panel then overlays the content below (like a popover) instead of pushing your layout
|
|
472
477
|
|
|
473
478
|
### Field Types
|
|
474
479
|
|
|
@@ -476,7 +481,7 @@ Validation mirrors react-hook-form `mode: 'onSubmit'`:
|
|
|
476
481
|
|-------|-------------|
|
|
477
482
|
| `'card-number'` | Card number input with automatic formatting and brand detection |
|
|
478
483
|
| `'expiry'` | Expiry date input (MM/YY format) |
|
|
479
|
-
| `'cvv'` | CVV/CVC input (3 or 4 digits depending on card brand) |
|
|
484
|
+
| `'cvv'` | CVV/CVC input (3 or 4 digits depending on card brand). Includes a built-in “where to find CVV” helper: tapping the card icon expands an explainer panel below the input |
|
|
480
485
|
|
|
481
486
|
### Individual Field Methods (Vanilla JS)
|
|
482
487
|
|
|
@@ -632,7 +637,8 @@ Common `code` values:
|
|
|
632
637
|
| `CONFIG_ERROR` | error | Session config failed to load and the API gave no code |
|
|
633
638
|
| `NETWORK_ERROR` / `HTTP_<status>` / `INVALID_RESPONSE` | error or payment | Network failure / HTTP error without an API code / malformed response |
|
|
634
639
|
| `VALIDATION_ERROR` | payment | `submit()` blocked by field validation (details in `fieldErrors`) |
|
|
635
|
-
| `MISSING_FIELDS` / `NOT_INITIALIZED` / `SUBMIT_IN_PROGRESS` / `SUBMIT_TIMEOUT` | payment | Card `submit()` preconditions / duplicate call / no response in time (30s
|
|
640
|
+
| `MISSING_FIELDS` / `NOT_INITIALIZED` / `SUBMIT_IN_PROGRESS` / `SUBMIT_TIMEOUT` | payment | Card `submit()` preconditions / duplicate call / no response in time (30s) |
|
|
641
|
+
| `THREE_DS_TIMEOUT` | payment | A 3DS challenge stayed unresolved for 10 minutes |
|
|
636
642
|
| `PAYMENT_DECLINED` / processor decline codes | payment | Declined; the processor's own code is passed through when present |
|
|
637
643
|
| `PAYMENT_FAILED` | payment | Non-declined failure (including unexpected 3DS outcomes) |
|
|
638
644
|
| `APPLE_PAY_SDK_ERROR` / `GOOGLE_PAY_SDK_ERROR` / `PAYPAL_ERROR` | payment | The wallet SDK failed mid-flow |
|
package/dist/index.js
CHANGED
|
@@ -793,7 +793,7 @@ var injectAppleButtonStyle = () => {
|
|
|
793
793
|
if (document.getElementById(APPLE_STYLE_ID)) return;
|
|
794
794
|
const style = document.createElement("style");
|
|
795
795
|
style.id = APPLE_STYLE_ID;
|
|
796
|
-
style.textContent = `.${APPLE_BUTTON_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out
|
|
796
|
+
style.textContent = `.${APPLE_BUTTON_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out;}.${APPLE_BUTTON_CLASS}:hover{opacity:.8;}.${APPLE_BUTTON_CLASS}:active{opacity:.7;}.${APPLE_BUTTON_CLASS}:focus-visible{outline:2px solid #0071e3;outline-offset:2px;}`;
|
|
797
797
|
document.head.appendChild(style);
|
|
798
798
|
};
|
|
799
799
|
var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
@@ -818,17 +818,23 @@ var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
|
818
818
|
if (processing || !applePayConfig || version === null) return;
|
|
819
819
|
if (!callbacks.canStart()) return;
|
|
820
820
|
callbacks.onStart();
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
821
|
+
let session;
|
|
822
|
+
try {
|
|
823
|
+
session = new window.ApplePaySession(version, {
|
|
824
|
+
countryCode: applePayConfig.country_code,
|
|
825
|
+
currencyCode: config.session.currency_code,
|
|
826
|
+
supportedNetworks: applePayConfig.supported_networks,
|
|
827
|
+
merchantCapabilities: applePayConfig.merchant_capabilities,
|
|
828
|
+
total: {
|
|
829
|
+
label: applePayConfig.display_name,
|
|
830
|
+
amount: (config.session.amount / 100).toFixed(2),
|
|
831
|
+
type: "final"
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
} catch {
|
|
835
|
+
callbacks.onSdkError("Apple Pay is not available in this browser.");
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
832
838
|
processing = true;
|
|
833
839
|
let abortedDueToError = false;
|
|
834
840
|
session.onvalidatemerchant = async (event) => {
|
|
@@ -871,7 +877,13 @@ var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
|
871
877
|
}
|
|
872
878
|
callbacks.onCancel();
|
|
873
879
|
};
|
|
874
|
-
|
|
880
|
+
try {
|
|
881
|
+
session.begin();
|
|
882
|
+
} catch {
|
|
883
|
+
processing = false;
|
|
884
|
+
callbacks.onSdkError("Apple Pay is not available in this browser.");
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
875
887
|
styleApplePayModalWhenReady();
|
|
876
888
|
};
|
|
877
889
|
button.addEventListener("click", handleClick);
|
|
@@ -962,13 +974,15 @@ var detectGooglePay = async (config) => {
|
|
|
962
974
|
};
|
|
963
975
|
var GPAY_WRAP_CLASS = "flintn-gpay";
|
|
964
976
|
var GPAY_STYLE_ID = "flintn-gpay-style";
|
|
977
|
+
var gpaySurfaceSelectors = [".gpay-button", ".gpay-card-info-container"].flatMap((s) => [s, `${s}:hover`, `${s}:active`, `${s}.hover`, `${s}.active`]).map((s) => `.${GPAY_WRAP_CLASS} ${s}`).join(",");
|
|
965
978
|
var injectGpayStyle = () => {
|
|
966
979
|
if (document.getElementById(GPAY_STYLE_ID)) return;
|
|
967
980
|
const style = document.createElement("style");
|
|
968
981
|
style.id = GPAY_STYLE_ID;
|
|
969
|
-
style.textContent = `.gpay-button:focus:not(:focus-visible){outline:none;box-shadow:none;}.gpay-button-fill:focus-within:not(:has(:focus-visible)){outline:none;box-shadow:none;}.${GPAY_WRAP_CLASS}{cursor:pointer;}.${GPAY_WRAP_CLASS} button{height:100%;cursor:pointer;}.${GPAY_WRAP_CLASS} > *{width:100%;height:100%;}`;
|
|
982
|
+
style.textContent = `.gpay-button:focus:not(:focus-visible){outline:none;box-shadow:none;}.gpay-button-fill:focus-within:not(:has(:focus-visible)){outline:none;box-shadow:none;}.${GPAY_WRAP_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out;}.${GPAY_WRAP_CLASS}:hover{opacity:.8;}.${GPAY_WRAP_CLASS}:active{opacity:.7;}.${GPAY_WRAP_CLASS} button{height:100%;cursor:pointer;}${gpaySurfaceSelectors}{border:none!important;outline:none!important;box-shadow:none!important;background-color:#000!important;}.${GPAY_WRAP_CLASS} .gpay-card-info-container:focus-visible,.${GPAY_WRAP_CLASS} .gpay-button:focus-visible{outline:2px solid #0071e3!important;outline-offset:2px;}.${GPAY_WRAP_CLASS} .gpay-card-info-iframe{display:none!important;}.${GPAY_WRAP_CLASS} .gpay-button-fill,.${GPAY_WRAP_CLASS} .gpay-card-info-animation-container-fade-out{opacity:1!important;}.${GPAY_WRAP_CLASS} .gpay-button-static-content-container-loading::before,.${GPAY_WRAP_CLASS} .gpay-button-static-content-container-loading::after{display:none!important;}.${GPAY_WRAP_CLASS} .gpay-button.gpay-button-size-match-height{height:50%!important;}.${GPAY_WRAP_CLASS} > *{width:100%;height:100%;}`;
|
|
970
983
|
document.head.appendChild(style);
|
|
971
984
|
};
|
|
985
|
+
var SHEET_SETTLE_GRACE_MS = 3e3;
|
|
972
986
|
var mountGooglePayButton = (container, client, config, borderRadius, callbacks) => {
|
|
973
987
|
let processing = false;
|
|
974
988
|
const handleClick = () => {
|
|
@@ -977,7 +991,31 @@ var mountGooglePayButton = (container, client, config, borderRadius, callbacks)
|
|
|
977
991
|
if (!callbacks.canStart()) return;
|
|
978
992
|
processing = true;
|
|
979
993
|
callbacks.onStart();
|
|
994
|
+
let settled = false;
|
|
995
|
+
let graceTimer = null;
|
|
996
|
+
const disarmWatchdog = () => {
|
|
997
|
+
document.removeEventListener("visibilitychange", onVisibleAgain);
|
|
998
|
+
if (graceTimer !== null) clearTimeout(graceTimer);
|
|
999
|
+
};
|
|
1000
|
+
const trySettle = () => {
|
|
1001
|
+
if (settled) return false;
|
|
1002
|
+
settled = true;
|
|
1003
|
+
disarmWatchdog();
|
|
1004
|
+
return true;
|
|
1005
|
+
};
|
|
1006
|
+
const onVisibleAgain = () => {
|
|
1007
|
+
if (document.visibilityState !== "visible" || graceTimer !== null) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
graceTimer = setTimeout(() => {
|
|
1011
|
+
if (!trySettle()) return;
|
|
1012
|
+
processing = false;
|
|
1013
|
+
callbacks.onCancel();
|
|
1014
|
+
}, SHEET_SETTLE_GRACE_MS);
|
|
1015
|
+
};
|
|
1016
|
+
document.addEventListener("visibilitychange", onVisibleAgain);
|
|
980
1017
|
client.loadPaymentData(request2).then((paymentData) => {
|
|
1018
|
+
if (!trySettle()) return;
|
|
981
1019
|
const complete = (_success, errorMessage) => {
|
|
982
1020
|
processing = false;
|
|
983
1021
|
if (errorMessage) callbacks.onSdkError(errorMessage);
|
|
@@ -987,6 +1025,7 @@ var mountGooglePayButton = (container, client, config, borderRadius, callbacks)
|
|
|
987
1025
|
callbacks.handlers(complete)
|
|
988
1026
|
);
|
|
989
1027
|
}).catch((err) => {
|
|
1028
|
+
if (!trySettle()) return;
|
|
990
1029
|
processing = false;
|
|
991
1030
|
if (err?.statusCode === "CANCELED") callbacks.onCancel();
|
|
992
1031
|
else callbacks.onSdkError("Google Pay payment failed");
|
|
@@ -1462,6 +1501,9 @@ var openThreeDsDialog = (options) => {
|
|
|
1462
1501
|
overlay.id = OVERLAY_ID;
|
|
1463
1502
|
overlay.style.cssText = "position: fixed; inset: 0; z-index: 2147483647; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center;";
|
|
1464
1503
|
const panel = document.createElement("div");
|
|
1504
|
+
panel.setAttribute("role", "dialog");
|
|
1505
|
+
panel.setAttribute("aria-modal", "true");
|
|
1506
|
+
panel.setAttribute("aria-label", "3DS verification");
|
|
1465
1507
|
panel.style.cssText = `position: relative; background: #fff; border-radius: 8px; width: min(${PANEL_WIDTH}px, calc(100vw - 32px)); height: min(${PANEL_HEIGHT}px, calc(100vh - 32px)); box-shadow: 0 8px 40px rgba(0, 0, 0, 0.25); overflow: hidden;`;
|
|
1466
1508
|
const closeButton = document.createElement("button");
|
|
1467
1509
|
closeButton.type = "button";
|
|
@@ -1475,13 +1517,28 @@ var openThreeDsDialog = (options) => {
|
|
|
1475
1517
|
iframe.style.cssText = "width: 100%; height: 100%; border: none; display: block;";
|
|
1476
1518
|
panel.append(iframe, closeButton);
|
|
1477
1519
|
overlay.appendChild(panel);
|
|
1520
|
+
const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
1521
|
+
const trapFocus = (event) => {
|
|
1522
|
+
if (event.key !== "Tab") return;
|
|
1523
|
+
const active = document.activeElement;
|
|
1524
|
+
const inside = active !== null && overlay.contains(active);
|
|
1525
|
+
if (event.shiftKey && (!inside || active === iframe)) {
|
|
1526
|
+
event.preventDefault();
|
|
1527
|
+
closeButton.focus();
|
|
1528
|
+
} else if (!event.shiftKey && (!inside || active === closeButton)) {
|
|
1529
|
+
event.preventDefault();
|
|
1530
|
+
iframe.focus();
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1478
1533
|
let finished = false;
|
|
1479
1534
|
const finish = (status) => {
|
|
1480
1535
|
if (finished) return;
|
|
1481
1536
|
finished = true;
|
|
1482
1537
|
window.removeEventListener("message", handleMessage);
|
|
1538
|
+
window.removeEventListener("keydown", trapFocus);
|
|
1483
1539
|
overlay.remove();
|
|
1484
1540
|
closeCurrent = null;
|
|
1541
|
+
previouslyFocused?.focus();
|
|
1485
1542
|
options.onFinish(status);
|
|
1486
1543
|
};
|
|
1487
1544
|
const handleMessage = (event) => {
|
|
@@ -1494,7 +1551,9 @@ var openThreeDsDialog = (options) => {
|
|
|
1494
1551
|
};
|
|
1495
1552
|
closeButton.addEventListener("click", () => finish("CANCELLED"));
|
|
1496
1553
|
window.addEventListener("message", handleMessage);
|
|
1554
|
+
window.addEventListener("keydown", trapFocus);
|
|
1497
1555
|
document.body.appendChild(overlay);
|
|
1556
|
+
closeButton.focus();
|
|
1498
1557
|
closeCurrent = () => finish("CANCELLED");
|
|
1499
1558
|
};
|
|
1500
1559
|
|
|
@@ -1607,7 +1666,15 @@ function createFlintNFields(options) {
|
|
|
1607
1666
|
const pending = pendingSubmit;
|
|
1608
1667
|
pending.timer = setTimeout(() => {
|
|
1609
1668
|
if (pendingSubmit === pending) {
|
|
1610
|
-
|
|
1669
|
+
const timeoutResult = {
|
|
1670
|
+
status: "PAYMENT_ERROR",
|
|
1671
|
+
error: {
|
|
1672
|
+
code: "THREE_DS_TIMEOUT",
|
|
1673
|
+
message: "3DS challenge timed out"
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
pendingSubmit.resolve(timeoutResult);
|
|
1677
|
+
options.onPayment?.(timeoutResult);
|
|
1611
1678
|
pendingSubmit = null;
|
|
1612
1679
|
}
|
|
1613
1680
|
}, THREE_DS_TIMEOUT);
|
package/dist/index.mjs
CHANGED
|
@@ -745,7 +745,7 @@ var injectAppleButtonStyle = () => {
|
|
|
745
745
|
if (document.getElementById(APPLE_STYLE_ID)) return;
|
|
746
746
|
const style = document.createElement("style");
|
|
747
747
|
style.id = APPLE_STYLE_ID;
|
|
748
|
-
style.textContent = `.${APPLE_BUTTON_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out
|
|
748
|
+
style.textContent = `.${APPLE_BUTTON_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out;}.${APPLE_BUTTON_CLASS}:hover{opacity:.8;}.${APPLE_BUTTON_CLASS}:active{opacity:.7;}.${APPLE_BUTTON_CLASS}:focus-visible{outline:2px solid #0071e3;outline-offset:2px;}`;
|
|
749
749
|
document.head.appendChild(style);
|
|
750
750
|
};
|
|
751
751
|
var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
@@ -770,17 +770,23 @@ var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
|
770
770
|
if (processing || !applePayConfig || version === null) return;
|
|
771
771
|
if (!callbacks.canStart()) return;
|
|
772
772
|
callbacks.onStart();
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
773
|
+
let session;
|
|
774
|
+
try {
|
|
775
|
+
session = new window.ApplePaySession(version, {
|
|
776
|
+
countryCode: applePayConfig.country_code,
|
|
777
|
+
currencyCode: config.session.currency_code,
|
|
778
|
+
supportedNetworks: applePayConfig.supported_networks,
|
|
779
|
+
merchantCapabilities: applePayConfig.merchant_capabilities,
|
|
780
|
+
total: {
|
|
781
|
+
label: applePayConfig.display_name,
|
|
782
|
+
amount: (config.session.amount / 100).toFixed(2),
|
|
783
|
+
type: "final"
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
} catch {
|
|
787
|
+
callbacks.onSdkError("Apple Pay is not available in this browser.");
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
784
790
|
processing = true;
|
|
785
791
|
let abortedDueToError = false;
|
|
786
792
|
session.onvalidatemerchant = async (event) => {
|
|
@@ -823,7 +829,13 @@ var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
|
823
829
|
}
|
|
824
830
|
callbacks.onCancel();
|
|
825
831
|
};
|
|
826
|
-
|
|
832
|
+
try {
|
|
833
|
+
session.begin();
|
|
834
|
+
} catch {
|
|
835
|
+
processing = false;
|
|
836
|
+
callbacks.onSdkError("Apple Pay is not available in this browser.");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
827
839
|
styleApplePayModalWhenReady();
|
|
828
840
|
};
|
|
829
841
|
button.addEventListener("click", handleClick);
|
|
@@ -914,13 +926,15 @@ var detectGooglePay = async (config) => {
|
|
|
914
926
|
};
|
|
915
927
|
var GPAY_WRAP_CLASS = "flintn-gpay";
|
|
916
928
|
var GPAY_STYLE_ID = "flintn-gpay-style";
|
|
929
|
+
var gpaySurfaceSelectors = [".gpay-button", ".gpay-card-info-container"].flatMap((s) => [s, `${s}:hover`, `${s}:active`, `${s}.hover`, `${s}.active`]).map((s) => `.${GPAY_WRAP_CLASS} ${s}`).join(",");
|
|
917
930
|
var injectGpayStyle = () => {
|
|
918
931
|
if (document.getElementById(GPAY_STYLE_ID)) return;
|
|
919
932
|
const style = document.createElement("style");
|
|
920
933
|
style.id = GPAY_STYLE_ID;
|
|
921
|
-
style.textContent = `.gpay-button:focus:not(:focus-visible){outline:none;box-shadow:none;}.gpay-button-fill:focus-within:not(:has(:focus-visible)){outline:none;box-shadow:none;}.${GPAY_WRAP_CLASS}{cursor:pointer;}.${GPAY_WRAP_CLASS} button{height:100%;cursor:pointer;}.${GPAY_WRAP_CLASS} > *{width:100%;height:100%;}`;
|
|
934
|
+
style.textContent = `.gpay-button:focus:not(:focus-visible){outline:none;box-shadow:none;}.gpay-button-fill:focus-within:not(:has(:focus-visible)){outline:none;box-shadow:none;}.${GPAY_WRAP_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out;}.${GPAY_WRAP_CLASS}:hover{opacity:.8;}.${GPAY_WRAP_CLASS}:active{opacity:.7;}.${GPAY_WRAP_CLASS} button{height:100%;cursor:pointer;}${gpaySurfaceSelectors}{border:none!important;outline:none!important;box-shadow:none!important;background-color:#000!important;}.${GPAY_WRAP_CLASS} .gpay-card-info-container:focus-visible,.${GPAY_WRAP_CLASS} .gpay-button:focus-visible{outline:2px solid #0071e3!important;outline-offset:2px;}.${GPAY_WRAP_CLASS} .gpay-card-info-iframe{display:none!important;}.${GPAY_WRAP_CLASS} .gpay-button-fill,.${GPAY_WRAP_CLASS} .gpay-card-info-animation-container-fade-out{opacity:1!important;}.${GPAY_WRAP_CLASS} .gpay-button-static-content-container-loading::before,.${GPAY_WRAP_CLASS} .gpay-button-static-content-container-loading::after{display:none!important;}.${GPAY_WRAP_CLASS} .gpay-button.gpay-button-size-match-height{height:50%!important;}.${GPAY_WRAP_CLASS} > *{width:100%;height:100%;}`;
|
|
922
935
|
document.head.appendChild(style);
|
|
923
936
|
};
|
|
937
|
+
var SHEET_SETTLE_GRACE_MS = 3e3;
|
|
924
938
|
var mountGooglePayButton = (container, client, config, borderRadius, callbacks) => {
|
|
925
939
|
let processing = false;
|
|
926
940
|
const handleClick = () => {
|
|
@@ -929,7 +943,31 @@ var mountGooglePayButton = (container, client, config, borderRadius, callbacks)
|
|
|
929
943
|
if (!callbacks.canStart()) return;
|
|
930
944
|
processing = true;
|
|
931
945
|
callbacks.onStart();
|
|
946
|
+
let settled = false;
|
|
947
|
+
let graceTimer = null;
|
|
948
|
+
const disarmWatchdog = () => {
|
|
949
|
+
document.removeEventListener("visibilitychange", onVisibleAgain);
|
|
950
|
+
if (graceTimer !== null) clearTimeout(graceTimer);
|
|
951
|
+
};
|
|
952
|
+
const trySettle = () => {
|
|
953
|
+
if (settled) return false;
|
|
954
|
+
settled = true;
|
|
955
|
+
disarmWatchdog();
|
|
956
|
+
return true;
|
|
957
|
+
};
|
|
958
|
+
const onVisibleAgain = () => {
|
|
959
|
+
if (document.visibilityState !== "visible" || graceTimer !== null) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
graceTimer = setTimeout(() => {
|
|
963
|
+
if (!trySettle()) return;
|
|
964
|
+
processing = false;
|
|
965
|
+
callbacks.onCancel();
|
|
966
|
+
}, SHEET_SETTLE_GRACE_MS);
|
|
967
|
+
};
|
|
968
|
+
document.addEventListener("visibilitychange", onVisibleAgain);
|
|
932
969
|
client.loadPaymentData(request2).then((paymentData) => {
|
|
970
|
+
if (!trySettle()) return;
|
|
933
971
|
const complete = (_success, errorMessage) => {
|
|
934
972
|
processing = false;
|
|
935
973
|
if (errorMessage) callbacks.onSdkError(errorMessage);
|
|
@@ -939,6 +977,7 @@ var mountGooglePayButton = (container, client, config, borderRadius, callbacks)
|
|
|
939
977
|
callbacks.handlers(complete)
|
|
940
978
|
);
|
|
941
979
|
}).catch((err) => {
|
|
980
|
+
if (!trySettle()) return;
|
|
942
981
|
processing = false;
|
|
943
982
|
if (err?.statusCode === "CANCELED") callbacks.onCancel();
|
|
944
983
|
else callbacks.onSdkError("Google Pay payment failed");
|
|
@@ -1414,6 +1453,9 @@ var openThreeDsDialog = (options) => {
|
|
|
1414
1453
|
overlay.id = OVERLAY_ID;
|
|
1415
1454
|
overlay.style.cssText = "position: fixed; inset: 0; z-index: 2147483647; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center;";
|
|
1416
1455
|
const panel = document.createElement("div");
|
|
1456
|
+
panel.setAttribute("role", "dialog");
|
|
1457
|
+
panel.setAttribute("aria-modal", "true");
|
|
1458
|
+
panel.setAttribute("aria-label", "3DS verification");
|
|
1417
1459
|
panel.style.cssText = `position: relative; background: #fff; border-radius: 8px; width: min(${PANEL_WIDTH}px, calc(100vw - 32px)); height: min(${PANEL_HEIGHT}px, calc(100vh - 32px)); box-shadow: 0 8px 40px rgba(0, 0, 0, 0.25); overflow: hidden;`;
|
|
1418
1460
|
const closeButton = document.createElement("button");
|
|
1419
1461
|
closeButton.type = "button";
|
|
@@ -1427,13 +1469,28 @@ var openThreeDsDialog = (options) => {
|
|
|
1427
1469
|
iframe.style.cssText = "width: 100%; height: 100%; border: none; display: block;";
|
|
1428
1470
|
panel.append(iframe, closeButton);
|
|
1429
1471
|
overlay.appendChild(panel);
|
|
1472
|
+
const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
1473
|
+
const trapFocus = (event) => {
|
|
1474
|
+
if (event.key !== "Tab") return;
|
|
1475
|
+
const active = document.activeElement;
|
|
1476
|
+
const inside = active !== null && overlay.contains(active);
|
|
1477
|
+
if (event.shiftKey && (!inside || active === iframe)) {
|
|
1478
|
+
event.preventDefault();
|
|
1479
|
+
closeButton.focus();
|
|
1480
|
+
} else if (!event.shiftKey && (!inside || active === closeButton)) {
|
|
1481
|
+
event.preventDefault();
|
|
1482
|
+
iframe.focus();
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1430
1485
|
let finished = false;
|
|
1431
1486
|
const finish = (status) => {
|
|
1432
1487
|
if (finished) return;
|
|
1433
1488
|
finished = true;
|
|
1434
1489
|
window.removeEventListener("message", handleMessage);
|
|
1490
|
+
window.removeEventListener("keydown", trapFocus);
|
|
1435
1491
|
overlay.remove();
|
|
1436
1492
|
closeCurrent = null;
|
|
1493
|
+
previouslyFocused?.focus();
|
|
1437
1494
|
options.onFinish(status);
|
|
1438
1495
|
};
|
|
1439
1496
|
const handleMessage = (event) => {
|
|
@@ -1446,7 +1503,9 @@ var openThreeDsDialog = (options) => {
|
|
|
1446
1503
|
};
|
|
1447
1504
|
closeButton.addEventListener("click", () => finish("CANCELLED"));
|
|
1448
1505
|
window.addEventListener("message", handleMessage);
|
|
1506
|
+
window.addEventListener("keydown", trapFocus);
|
|
1449
1507
|
document.body.appendChild(overlay);
|
|
1508
|
+
closeButton.focus();
|
|
1450
1509
|
closeCurrent = () => finish("CANCELLED");
|
|
1451
1510
|
};
|
|
1452
1511
|
|
|
@@ -1559,7 +1618,15 @@ function createFlintNFields(options) {
|
|
|
1559
1618
|
const pending = pendingSubmit;
|
|
1560
1619
|
pending.timer = setTimeout(() => {
|
|
1561
1620
|
if (pendingSubmit === pending) {
|
|
1562
|
-
|
|
1621
|
+
const timeoutResult = {
|
|
1622
|
+
status: "PAYMENT_ERROR",
|
|
1623
|
+
error: {
|
|
1624
|
+
code: "THREE_DS_TIMEOUT",
|
|
1625
|
+
message: "3DS challenge timed out"
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
pendingSubmit.resolve(timeoutResult);
|
|
1629
|
+
options.onPayment?.(timeoutResult);
|
|
1563
1630
|
pendingSubmit = null;
|
|
1564
1631
|
}
|
|
1565
1632
|
}, THREE_DS_TIMEOUT);
|
package/dist/react.js
CHANGED
|
@@ -810,7 +810,7 @@ var injectAppleButtonStyle = () => {
|
|
|
810
810
|
if (document.getElementById(APPLE_STYLE_ID)) return;
|
|
811
811
|
const style = document.createElement("style");
|
|
812
812
|
style.id = APPLE_STYLE_ID;
|
|
813
|
-
style.textContent = `.${APPLE_BUTTON_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out
|
|
813
|
+
style.textContent = `.${APPLE_BUTTON_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out;}.${APPLE_BUTTON_CLASS}:hover{opacity:.8;}.${APPLE_BUTTON_CLASS}:active{opacity:.7;}.${APPLE_BUTTON_CLASS}:focus-visible{outline:2px solid #0071e3;outline-offset:2px;}`;
|
|
814
814
|
document.head.appendChild(style);
|
|
815
815
|
};
|
|
816
816
|
var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
@@ -835,17 +835,23 @@ var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
|
835
835
|
if (processing || !applePayConfig || version === null) return;
|
|
836
836
|
if (!callbacks.canStart()) return;
|
|
837
837
|
callbacks.onStart();
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
838
|
+
let session;
|
|
839
|
+
try {
|
|
840
|
+
session = new window.ApplePaySession(version, {
|
|
841
|
+
countryCode: applePayConfig.country_code,
|
|
842
|
+
currencyCode: config.session.currency_code,
|
|
843
|
+
supportedNetworks: applePayConfig.supported_networks,
|
|
844
|
+
merchantCapabilities: applePayConfig.merchant_capabilities,
|
|
845
|
+
total: {
|
|
846
|
+
label: applePayConfig.display_name,
|
|
847
|
+
amount: (config.session.amount / 100).toFixed(2),
|
|
848
|
+
type: "final"
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
} catch {
|
|
852
|
+
callbacks.onSdkError("Apple Pay is not available in this browser.");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
849
855
|
processing = true;
|
|
850
856
|
let abortedDueToError = false;
|
|
851
857
|
session.onvalidatemerchant = async (event) => {
|
|
@@ -888,7 +894,13 @@ var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
|
888
894
|
}
|
|
889
895
|
callbacks.onCancel();
|
|
890
896
|
};
|
|
891
|
-
|
|
897
|
+
try {
|
|
898
|
+
session.begin();
|
|
899
|
+
} catch {
|
|
900
|
+
processing = false;
|
|
901
|
+
callbacks.onSdkError("Apple Pay is not available in this browser.");
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
892
904
|
styleApplePayModalWhenReady();
|
|
893
905
|
};
|
|
894
906
|
button.addEventListener("click", handleClick);
|
|
@@ -979,13 +991,15 @@ var detectGooglePay = async (config) => {
|
|
|
979
991
|
};
|
|
980
992
|
var GPAY_WRAP_CLASS = "flintn-gpay";
|
|
981
993
|
var GPAY_STYLE_ID = "flintn-gpay-style";
|
|
994
|
+
var gpaySurfaceSelectors = [".gpay-button", ".gpay-card-info-container"].flatMap((s) => [s, `${s}:hover`, `${s}:active`, `${s}.hover`, `${s}.active`]).map((s) => `.${GPAY_WRAP_CLASS} ${s}`).join(",");
|
|
982
995
|
var injectGpayStyle = () => {
|
|
983
996
|
if (document.getElementById(GPAY_STYLE_ID)) return;
|
|
984
997
|
const style = document.createElement("style");
|
|
985
998
|
style.id = GPAY_STYLE_ID;
|
|
986
|
-
style.textContent = `.gpay-button:focus:not(:focus-visible){outline:none;box-shadow:none;}.gpay-button-fill:focus-within:not(:has(:focus-visible)){outline:none;box-shadow:none;}.${GPAY_WRAP_CLASS}{cursor:pointer;}.${GPAY_WRAP_CLASS} button{height:100%;cursor:pointer;}.${GPAY_WRAP_CLASS} > *{width:100%;height:100%;}`;
|
|
999
|
+
style.textContent = `.gpay-button:focus:not(:focus-visible){outline:none;box-shadow:none;}.gpay-button-fill:focus-within:not(:has(:focus-visible)){outline:none;box-shadow:none;}.${GPAY_WRAP_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out;}.${GPAY_WRAP_CLASS}:hover{opacity:.8;}.${GPAY_WRAP_CLASS}:active{opacity:.7;}.${GPAY_WRAP_CLASS} button{height:100%;cursor:pointer;}${gpaySurfaceSelectors}{border:none!important;outline:none!important;box-shadow:none!important;background-color:#000!important;}.${GPAY_WRAP_CLASS} .gpay-card-info-container:focus-visible,.${GPAY_WRAP_CLASS} .gpay-button:focus-visible{outline:2px solid #0071e3!important;outline-offset:2px;}.${GPAY_WRAP_CLASS} .gpay-card-info-iframe{display:none!important;}.${GPAY_WRAP_CLASS} .gpay-button-fill,.${GPAY_WRAP_CLASS} .gpay-card-info-animation-container-fade-out{opacity:1!important;}.${GPAY_WRAP_CLASS} .gpay-button-static-content-container-loading::before,.${GPAY_WRAP_CLASS} .gpay-button-static-content-container-loading::after{display:none!important;}.${GPAY_WRAP_CLASS} .gpay-button.gpay-button-size-match-height{height:50%!important;}.${GPAY_WRAP_CLASS} > *{width:100%;height:100%;}`;
|
|
987
1000
|
document.head.appendChild(style);
|
|
988
1001
|
};
|
|
1002
|
+
var SHEET_SETTLE_GRACE_MS = 3e3;
|
|
989
1003
|
var mountGooglePayButton = (container, client, config, borderRadius, callbacks) => {
|
|
990
1004
|
let processing = false;
|
|
991
1005
|
const handleClick = () => {
|
|
@@ -994,7 +1008,31 @@ var mountGooglePayButton = (container, client, config, borderRadius, callbacks)
|
|
|
994
1008
|
if (!callbacks.canStart()) return;
|
|
995
1009
|
processing = true;
|
|
996
1010
|
callbacks.onStart();
|
|
1011
|
+
let settled = false;
|
|
1012
|
+
let graceTimer = null;
|
|
1013
|
+
const disarmWatchdog = () => {
|
|
1014
|
+
document.removeEventListener("visibilitychange", onVisibleAgain);
|
|
1015
|
+
if (graceTimer !== null) clearTimeout(graceTimer);
|
|
1016
|
+
};
|
|
1017
|
+
const trySettle = () => {
|
|
1018
|
+
if (settled) return false;
|
|
1019
|
+
settled = true;
|
|
1020
|
+
disarmWatchdog();
|
|
1021
|
+
return true;
|
|
1022
|
+
};
|
|
1023
|
+
const onVisibleAgain = () => {
|
|
1024
|
+
if (document.visibilityState !== "visible" || graceTimer !== null) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
graceTimer = setTimeout(() => {
|
|
1028
|
+
if (!trySettle()) return;
|
|
1029
|
+
processing = false;
|
|
1030
|
+
callbacks.onCancel();
|
|
1031
|
+
}, SHEET_SETTLE_GRACE_MS);
|
|
1032
|
+
};
|
|
1033
|
+
document.addEventListener("visibilitychange", onVisibleAgain);
|
|
997
1034
|
client.loadPaymentData(request2).then((paymentData) => {
|
|
1035
|
+
if (!trySettle()) return;
|
|
998
1036
|
const complete = (_success, errorMessage) => {
|
|
999
1037
|
processing = false;
|
|
1000
1038
|
if (errorMessage) callbacks.onSdkError(errorMessage);
|
|
@@ -1004,6 +1042,7 @@ var mountGooglePayButton = (container, client, config, borderRadius, callbacks)
|
|
|
1004
1042
|
callbacks.handlers(complete)
|
|
1005
1043
|
);
|
|
1006
1044
|
}).catch((err) => {
|
|
1045
|
+
if (!trySettle()) return;
|
|
1007
1046
|
processing = false;
|
|
1008
1047
|
if (err?.statusCode === "CANCELED") callbacks.onCancel();
|
|
1009
1048
|
else callbacks.onSdkError("Google Pay payment failed");
|
|
@@ -1479,6 +1518,9 @@ var openThreeDsDialog = (options) => {
|
|
|
1479
1518
|
overlay.id = OVERLAY_ID;
|
|
1480
1519
|
overlay.style.cssText = "position: fixed; inset: 0; z-index: 2147483647; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center;";
|
|
1481
1520
|
const panel = document.createElement("div");
|
|
1521
|
+
panel.setAttribute("role", "dialog");
|
|
1522
|
+
panel.setAttribute("aria-modal", "true");
|
|
1523
|
+
panel.setAttribute("aria-label", "3DS verification");
|
|
1482
1524
|
panel.style.cssText = `position: relative; background: #fff; border-radius: 8px; width: min(${PANEL_WIDTH}px, calc(100vw - 32px)); height: min(${PANEL_HEIGHT}px, calc(100vh - 32px)); box-shadow: 0 8px 40px rgba(0, 0, 0, 0.25); overflow: hidden;`;
|
|
1483
1525
|
const closeButton = document.createElement("button");
|
|
1484
1526
|
closeButton.type = "button";
|
|
@@ -1492,13 +1534,28 @@ var openThreeDsDialog = (options) => {
|
|
|
1492
1534
|
iframe.style.cssText = "width: 100%; height: 100%; border: none; display: block;";
|
|
1493
1535
|
panel.append(iframe, closeButton);
|
|
1494
1536
|
overlay.appendChild(panel);
|
|
1537
|
+
const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
1538
|
+
const trapFocus = (event) => {
|
|
1539
|
+
if (event.key !== "Tab") return;
|
|
1540
|
+
const active = document.activeElement;
|
|
1541
|
+
const inside = active !== null && overlay.contains(active);
|
|
1542
|
+
if (event.shiftKey && (!inside || active === iframe)) {
|
|
1543
|
+
event.preventDefault();
|
|
1544
|
+
closeButton.focus();
|
|
1545
|
+
} else if (!event.shiftKey && (!inside || active === closeButton)) {
|
|
1546
|
+
event.preventDefault();
|
|
1547
|
+
iframe.focus();
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1495
1550
|
let finished = false;
|
|
1496
1551
|
const finish = (status) => {
|
|
1497
1552
|
if (finished) return;
|
|
1498
1553
|
finished = true;
|
|
1499
1554
|
window.removeEventListener("message", handleMessage);
|
|
1555
|
+
window.removeEventListener("keydown", trapFocus);
|
|
1500
1556
|
overlay.remove();
|
|
1501
1557
|
closeCurrent = null;
|
|
1558
|
+
previouslyFocused?.focus();
|
|
1502
1559
|
options.onFinish(status);
|
|
1503
1560
|
};
|
|
1504
1561
|
const handleMessage = (event) => {
|
|
@@ -1511,7 +1568,9 @@ var openThreeDsDialog = (options) => {
|
|
|
1511
1568
|
};
|
|
1512
1569
|
closeButton.addEventListener("click", () => finish("CANCELLED"));
|
|
1513
1570
|
window.addEventListener("message", handleMessage);
|
|
1571
|
+
window.addEventListener("keydown", trapFocus);
|
|
1514
1572
|
document.body.appendChild(overlay);
|
|
1573
|
+
closeButton.focus();
|
|
1515
1574
|
closeCurrent = () => finish("CANCELLED");
|
|
1516
1575
|
};
|
|
1517
1576
|
|
|
@@ -1624,7 +1683,15 @@ function createFlintNFields(options) {
|
|
|
1624
1683
|
const pending = pendingSubmit;
|
|
1625
1684
|
pending.timer = setTimeout(() => {
|
|
1626
1685
|
if (pendingSubmit === pending) {
|
|
1627
|
-
|
|
1686
|
+
const timeoutResult = {
|
|
1687
|
+
status: "PAYMENT_ERROR",
|
|
1688
|
+
error: {
|
|
1689
|
+
code: "THREE_DS_TIMEOUT",
|
|
1690
|
+
message: "3DS challenge timed out"
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
pendingSubmit.resolve(timeoutResult);
|
|
1694
|
+
options.onPayment?.(timeoutResult);
|
|
1628
1695
|
pendingSubmit = null;
|
|
1629
1696
|
}
|
|
1630
1697
|
}, THREE_DS_TIMEOUT);
|
|
@@ -1867,7 +1934,7 @@ var DEFAULT_CARD_OPTIONS = {
|
|
|
1867
1934
|
placeholder: "1234 1234 1234 1234"
|
|
1868
1935
|
};
|
|
1869
1936
|
var DEFAULT_EXPIRY_OPTIONS = { placeholder: "MM/YY" };
|
|
1870
|
-
var DEFAULT_CVV_OPTIONS = { placeholder: "
|
|
1937
|
+
var DEFAULT_CVV_OPTIONS = { placeholder: "***" };
|
|
1871
1938
|
function useFlintNFields(options) {
|
|
1872
1939
|
const cardNumberRef = (0, import_react2.useRef)(null);
|
|
1873
1940
|
const expiryRef = (0, import_react2.useRef)(null);
|
package/dist/react.mjs
CHANGED
|
@@ -783,7 +783,7 @@ var injectAppleButtonStyle = () => {
|
|
|
783
783
|
if (document.getElementById(APPLE_STYLE_ID)) return;
|
|
784
784
|
const style = document.createElement("style");
|
|
785
785
|
style.id = APPLE_STYLE_ID;
|
|
786
|
-
style.textContent = `.${APPLE_BUTTON_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out
|
|
786
|
+
style.textContent = `.${APPLE_BUTTON_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out;}.${APPLE_BUTTON_CLASS}:hover{opacity:.8;}.${APPLE_BUTTON_CLASS}:active{opacity:.7;}.${APPLE_BUTTON_CLASS}:focus-visible{outline:2px solid #0071e3;outline-offset:2px;}`;
|
|
787
787
|
document.head.appendChild(style);
|
|
788
788
|
};
|
|
789
789
|
var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
@@ -808,17 +808,23 @@ var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
|
808
808
|
if (processing || !applePayConfig || version === null) return;
|
|
809
809
|
if (!callbacks.canStart()) return;
|
|
810
810
|
callbacks.onStart();
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
811
|
+
let session;
|
|
812
|
+
try {
|
|
813
|
+
session = new window.ApplePaySession(version, {
|
|
814
|
+
countryCode: applePayConfig.country_code,
|
|
815
|
+
currencyCode: config.session.currency_code,
|
|
816
|
+
supportedNetworks: applePayConfig.supported_networks,
|
|
817
|
+
merchantCapabilities: applePayConfig.merchant_capabilities,
|
|
818
|
+
total: {
|
|
819
|
+
label: applePayConfig.display_name,
|
|
820
|
+
amount: (config.session.amount / 100).toFixed(2),
|
|
821
|
+
type: "final"
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
} catch {
|
|
825
|
+
callbacks.onSdkError("Apple Pay is not available in this browser.");
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
822
828
|
processing = true;
|
|
823
829
|
let abortedDueToError = false;
|
|
824
830
|
session.onvalidatemerchant = async (event) => {
|
|
@@ -861,7 +867,13 @@ var mountApplePayButton = (container, config, api, borderRadius, callbacks) => {
|
|
|
861
867
|
}
|
|
862
868
|
callbacks.onCancel();
|
|
863
869
|
};
|
|
864
|
-
|
|
870
|
+
try {
|
|
871
|
+
session.begin();
|
|
872
|
+
} catch {
|
|
873
|
+
processing = false;
|
|
874
|
+
callbacks.onSdkError("Apple Pay is not available in this browser.");
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
865
877
|
styleApplePayModalWhenReady();
|
|
866
878
|
};
|
|
867
879
|
button.addEventListener("click", handleClick);
|
|
@@ -952,13 +964,15 @@ var detectGooglePay = async (config) => {
|
|
|
952
964
|
};
|
|
953
965
|
var GPAY_WRAP_CLASS = "flintn-gpay";
|
|
954
966
|
var GPAY_STYLE_ID = "flintn-gpay-style";
|
|
967
|
+
var gpaySurfaceSelectors = [".gpay-button", ".gpay-card-info-container"].flatMap((s) => [s, `${s}:hover`, `${s}:active`, `${s}.hover`, `${s}.active`]).map((s) => `.${GPAY_WRAP_CLASS} ${s}`).join(",");
|
|
955
968
|
var injectGpayStyle = () => {
|
|
956
969
|
if (document.getElementById(GPAY_STYLE_ID)) return;
|
|
957
970
|
const style = document.createElement("style");
|
|
958
971
|
style.id = GPAY_STYLE_ID;
|
|
959
|
-
style.textContent = `.gpay-button:focus:not(:focus-visible){outline:none;box-shadow:none;}.gpay-button-fill:focus-within:not(:has(:focus-visible)){outline:none;box-shadow:none;}.${GPAY_WRAP_CLASS}{cursor:pointer;}.${GPAY_WRAP_CLASS} button{height:100%;cursor:pointer;}.${GPAY_WRAP_CLASS} > *{width:100%;height:100%;}`;
|
|
972
|
+
style.textContent = `.gpay-button:focus:not(:focus-visible){outline:none;box-shadow:none;}.gpay-button-fill:focus-within:not(:has(:focus-visible)){outline:none;box-shadow:none;}.${GPAY_WRAP_CLASS}{cursor:pointer;transition:opacity .15s ease-in-out;}.${GPAY_WRAP_CLASS}:hover{opacity:.8;}.${GPAY_WRAP_CLASS}:active{opacity:.7;}.${GPAY_WRAP_CLASS} button{height:100%;cursor:pointer;}${gpaySurfaceSelectors}{border:none!important;outline:none!important;box-shadow:none!important;background-color:#000!important;}.${GPAY_WRAP_CLASS} .gpay-card-info-container:focus-visible,.${GPAY_WRAP_CLASS} .gpay-button:focus-visible{outline:2px solid #0071e3!important;outline-offset:2px;}.${GPAY_WRAP_CLASS} .gpay-card-info-iframe{display:none!important;}.${GPAY_WRAP_CLASS} .gpay-button-fill,.${GPAY_WRAP_CLASS} .gpay-card-info-animation-container-fade-out{opacity:1!important;}.${GPAY_WRAP_CLASS} .gpay-button-static-content-container-loading::before,.${GPAY_WRAP_CLASS} .gpay-button-static-content-container-loading::after{display:none!important;}.${GPAY_WRAP_CLASS} .gpay-button.gpay-button-size-match-height{height:50%!important;}.${GPAY_WRAP_CLASS} > *{width:100%;height:100%;}`;
|
|
960
973
|
document.head.appendChild(style);
|
|
961
974
|
};
|
|
975
|
+
var SHEET_SETTLE_GRACE_MS = 3e3;
|
|
962
976
|
var mountGooglePayButton = (container, client, config, borderRadius, callbacks) => {
|
|
963
977
|
let processing = false;
|
|
964
978
|
const handleClick = () => {
|
|
@@ -967,7 +981,31 @@ var mountGooglePayButton = (container, client, config, borderRadius, callbacks)
|
|
|
967
981
|
if (!callbacks.canStart()) return;
|
|
968
982
|
processing = true;
|
|
969
983
|
callbacks.onStart();
|
|
984
|
+
let settled = false;
|
|
985
|
+
let graceTimer = null;
|
|
986
|
+
const disarmWatchdog = () => {
|
|
987
|
+
document.removeEventListener("visibilitychange", onVisibleAgain);
|
|
988
|
+
if (graceTimer !== null) clearTimeout(graceTimer);
|
|
989
|
+
};
|
|
990
|
+
const trySettle = () => {
|
|
991
|
+
if (settled) return false;
|
|
992
|
+
settled = true;
|
|
993
|
+
disarmWatchdog();
|
|
994
|
+
return true;
|
|
995
|
+
};
|
|
996
|
+
const onVisibleAgain = () => {
|
|
997
|
+
if (document.visibilityState !== "visible" || graceTimer !== null) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
graceTimer = setTimeout(() => {
|
|
1001
|
+
if (!trySettle()) return;
|
|
1002
|
+
processing = false;
|
|
1003
|
+
callbacks.onCancel();
|
|
1004
|
+
}, SHEET_SETTLE_GRACE_MS);
|
|
1005
|
+
};
|
|
1006
|
+
document.addEventListener("visibilitychange", onVisibleAgain);
|
|
970
1007
|
client.loadPaymentData(request2).then((paymentData) => {
|
|
1008
|
+
if (!trySettle()) return;
|
|
971
1009
|
const complete = (_success, errorMessage) => {
|
|
972
1010
|
processing = false;
|
|
973
1011
|
if (errorMessage) callbacks.onSdkError(errorMessage);
|
|
@@ -977,6 +1015,7 @@ var mountGooglePayButton = (container, client, config, borderRadius, callbacks)
|
|
|
977
1015
|
callbacks.handlers(complete)
|
|
978
1016
|
);
|
|
979
1017
|
}).catch((err) => {
|
|
1018
|
+
if (!trySettle()) return;
|
|
980
1019
|
processing = false;
|
|
981
1020
|
if (err?.statusCode === "CANCELED") callbacks.onCancel();
|
|
982
1021
|
else callbacks.onSdkError("Google Pay payment failed");
|
|
@@ -1452,6 +1491,9 @@ var openThreeDsDialog = (options) => {
|
|
|
1452
1491
|
overlay.id = OVERLAY_ID;
|
|
1453
1492
|
overlay.style.cssText = "position: fixed; inset: 0; z-index: 2147483647; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center;";
|
|
1454
1493
|
const panel = document.createElement("div");
|
|
1494
|
+
panel.setAttribute("role", "dialog");
|
|
1495
|
+
panel.setAttribute("aria-modal", "true");
|
|
1496
|
+
panel.setAttribute("aria-label", "3DS verification");
|
|
1455
1497
|
panel.style.cssText = `position: relative; background: #fff; border-radius: 8px; width: min(${PANEL_WIDTH}px, calc(100vw - 32px)); height: min(${PANEL_HEIGHT}px, calc(100vh - 32px)); box-shadow: 0 8px 40px rgba(0, 0, 0, 0.25); overflow: hidden;`;
|
|
1456
1498
|
const closeButton = document.createElement("button");
|
|
1457
1499
|
closeButton.type = "button";
|
|
@@ -1465,13 +1507,28 @@ var openThreeDsDialog = (options) => {
|
|
|
1465
1507
|
iframe.style.cssText = "width: 100%; height: 100%; border: none; display: block;";
|
|
1466
1508
|
panel.append(iframe, closeButton);
|
|
1467
1509
|
overlay.appendChild(panel);
|
|
1510
|
+
const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
1511
|
+
const trapFocus = (event) => {
|
|
1512
|
+
if (event.key !== "Tab") return;
|
|
1513
|
+
const active = document.activeElement;
|
|
1514
|
+
const inside = active !== null && overlay.contains(active);
|
|
1515
|
+
if (event.shiftKey && (!inside || active === iframe)) {
|
|
1516
|
+
event.preventDefault();
|
|
1517
|
+
closeButton.focus();
|
|
1518
|
+
} else if (!event.shiftKey && (!inside || active === closeButton)) {
|
|
1519
|
+
event.preventDefault();
|
|
1520
|
+
iframe.focus();
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1468
1523
|
let finished = false;
|
|
1469
1524
|
const finish = (status) => {
|
|
1470
1525
|
if (finished) return;
|
|
1471
1526
|
finished = true;
|
|
1472
1527
|
window.removeEventListener("message", handleMessage);
|
|
1528
|
+
window.removeEventListener("keydown", trapFocus);
|
|
1473
1529
|
overlay.remove();
|
|
1474
1530
|
closeCurrent = null;
|
|
1531
|
+
previouslyFocused?.focus();
|
|
1475
1532
|
options.onFinish(status);
|
|
1476
1533
|
};
|
|
1477
1534
|
const handleMessage = (event) => {
|
|
@@ -1484,7 +1541,9 @@ var openThreeDsDialog = (options) => {
|
|
|
1484
1541
|
};
|
|
1485
1542
|
closeButton.addEventListener("click", () => finish("CANCELLED"));
|
|
1486
1543
|
window.addEventListener("message", handleMessage);
|
|
1544
|
+
window.addEventListener("keydown", trapFocus);
|
|
1487
1545
|
document.body.appendChild(overlay);
|
|
1546
|
+
closeButton.focus();
|
|
1488
1547
|
closeCurrent = () => finish("CANCELLED");
|
|
1489
1548
|
};
|
|
1490
1549
|
|
|
@@ -1597,7 +1656,15 @@ function createFlintNFields(options) {
|
|
|
1597
1656
|
const pending = pendingSubmit;
|
|
1598
1657
|
pending.timer = setTimeout(() => {
|
|
1599
1658
|
if (pendingSubmit === pending) {
|
|
1600
|
-
|
|
1659
|
+
const timeoutResult = {
|
|
1660
|
+
status: "PAYMENT_ERROR",
|
|
1661
|
+
error: {
|
|
1662
|
+
code: "THREE_DS_TIMEOUT",
|
|
1663
|
+
message: "3DS challenge timed out"
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
pendingSubmit.resolve(timeoutResult);
|
|
1667
|
+
options.onPayment?.(timeoutResult);
|
|
1601
1668
|
pendingSubmit = null;
|
|
1602
1669
|
}
|
|
1603
1670
|
}, THREE_DS_TIMEOUT);
|
|
@@ -1840,7 +1907,7 @@ var DEFAULT_CARD_OPTIONS = {
|
|
|
1840
1907
|
placeholder: "1234 1234 1234 1234"
|
|
1841
1908
|
};
|
|
1842
1909
|
var DEFAULT_EXPIRY_OPTIONS = { placeholder: "MM/YY" };
|
|
1843
|
-
var DEFAULT_CVV_OPTIONS = { placeholder: "
|
|
1910
|
+
var DEFAULT_CVV_OPTIONS = { placeholder: "***" };
|
|
1844
1911
|
function useFlintNFields(options) {
|
|
1845
1912
|
const cardNumberRef = useRef2(null);
|
|
1846
1913
|
const expiryRef = useRef2(null);
|
package/package.json
CHANGED