@usethrottle/checkout-react 1.4.1 → 1.5.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/dist/index.cjs +315 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +203 -2
- package/dist/index.d.ts +203 -2
- package/dist/index.js +316 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
package/dist/index.cjs
CHANGED
|
@@ -20,10 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.tsx
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
CardNumberField: () => CardNumberField,
|
|
23
24
|
CheckoutEmbed: () => CheckoutEmbed,
|
|
25
|
+
ExpiryField: () => ExpiryField,
|
|
24
26
|
PaymentEmbed: () => PaymentEmbed,
|
|
27
|
+
PostalCodeField: () => PostalCodeField,
|
|
28
|
+
SecureCardProvider: () => SecureCardProvider,
|
|
29
|
+
SecurityCodeField: () => SecurityCodeField,
|
|
25
30
|
ThrottleCheckout: () => CheckoutEmbed,
|
|
26
31
|
useCartSession: () => useCartSession,
|
|
32
|
+
useSecureCard: () => useSecureCard,
|
|
27
33
|
useThrottleCheckout: () => useThrottleCheckout,
|
|
28
34
|
useThrottleEvents: () => useThrottleEvents
|
|
29
35
|
});
|
|
@@ -663,12 +669,321 @@ function useThrottleCheckout(options) {
|
|
|
663
669
|
reset
|
|
664
670
|
};
|
|
665
671
|
}
|
|
672
|
+
|
|
673
|
+
// src/useSecureCard.tsx
|
|
674
|
+
var import_react7 = require("react");
|
|
675
|
+
var import_secure_fields2 = require("@gr4vy/secure-fields");
|
|
676
|
+
|
|
677
|
+
// src/threeDSecure.ts
|
|
678
|
+
var import_secure_fields = require("@gr4vy/secure-fields");
|
|
679
|
+
function resolveChallenge({ instance, element, challengeWindowSize }) {
|
|
680
|
+
return new Promise((resolve) => {
|
|
681
|
+
const handleFinish = () => {
|
|
682
|
+
instance.removeEventListener?.(import_secure_fields.SecureFields.Events.THREE_DS_FINISH, handleFinish);
|
|
683
|
+
resolve();
|
|
684
|
+
};
|
|
685
|
+
instance.addEventListener(import_secure_fields.SecureFields.Events.THREE_DS_FINISH, handleFinish);
|
|
686
|
+
instance.addThreeDSecure(element, challengeWindowSize ? { challengeWindowSize } : void 0);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/useSecureCard.tsx
|
|
691
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
692
|
+
var SecureCardContext = (0, import_react7.createContext)(null);
|
|
693
|
+
var FIELDS_REQUIRED_TO_BE_READY = 3;
|
|
694
|
+
var DEFAULT_CHALLENGE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
695
|
+
function SecureCardProvider({
|
|
696
|
+
session,
|
|
697
|
+
checkoutSessionId,
|
|
698
|
+
children,
|
|
699
|
+
completeFn,
|
|
700
|
+
resolveChallengeFn,
|
|
701
|
+
challengeTimeoutMs = DEFAULT_CHALLENGE_TIMEOUT_MS,
|
|
702
|
+
apiBaseUrl,
|
|
703
|
+
onSuccess
|
|
704
|
+
}) {
|
|
705
|
+
const [status, setStatus] = (0, import_react7.useState)("idle");
|
|
706
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
707
|
+
const [mountedCount, setMountedCount] = (0, import_react7.useState)(0);
|
|
708
|
+
const vaultWaiter = (0, import_react7.useRef)(null);
|
|
709
|
+
const threeDSElement = (0, import_react7.useRef)(null);
|
|
710
|
+
const threeDSOptions = (0, import_react7.useRef)(void 0);
|
|
711
|
+
const challengeWaiter = (0, import_react7.useRef)(null);
|
|
712
|
+
const { gr4vyId, environment, sessionId } = session;
|
|
713
|
+
const [instance, setInstance] = (0, import_react7.useState)(null);
|
|
714
|
+
const [threeDSMount, setThreeDSMount] = (0, import_react7.useState)(null);
|
|
715
|
+
(0, import_react7.useEffect)(() => {
|
|
716
|
+
const created = new import_secure_fields2.SecureFields({ gr4vyId, environment, sessionId });
|
|
717
|
+
setInstance(created);
|
|
718
|
+
setMountedCount(0);
|
|
719
|
+
setStatus((current) => current === "idle" || current === "ready" ? "mounting" : current);
|
|
720
|
+
return () => {
|
|
721
|
+
created.controller?.remove();
|
|
722
|
+
setInstance(null);
|
|
723
|
+
};
|
|
724
|
+
}, [gr4vyId, environment, sessionId]);
|
|
725
|
+
(0, import_react7.useEffect)(() => {
|
|
726
|
+
if (!instance) return;
|
|
727
|
+
const handleSuccess = () => {
|
|
728
|
+
vaultWaiter.current?.resolve();
|
|
729
|
+
};
|
|
730
|
+
const handleFailure = (data) => {
|
|
731
|
+
vaultWaiter.current?.reject({
|
|
732
|
+
code: data?.code ?? "card_vault_failed",
|
|
733
|
+
message: data?.message ?? "The card could not be saved."
|
|
734
|
+
});
|
|
735
|
+
};
|
|
736
|
+
const handleThreeDSStart = () => setStatus("challenging");
|
|
737
|
+
const handleThreeDSFinish = () => {
|
|
738
|
+
setStatus((current) => current === "challenging" ? "vaulting" : current);
|
|
739
|
+
};
|
|
740
|
+
instance.addEventListener(import_secure_fields2.SecureFields.Events.CARD_VAULT_SUCCESS, handleSuccess);
|
|
741
|
+
instance.addEventListener(import_secure_fields2.SecureFields.Events.CARD_VAULT_FAILURE, handleFailure);
|
|
742
|
+
instance.addEventListener(import_secure_fields2.SecureFields.Events.THREE_DS_START, handleThreeDSStart);
|
|
743
|
+
instance.addEventListener(import_secure_fields2.SecureFields.Events.THREE_DS_FINISH, handleThreeDSFinish);
|
|
744
|
+
return () => {
|
|
745
|
+
instance.removeEventListener?.(import_secure_fields2.SecureFields.Events.CARD_VAULT_SUCCESS, handleSuccess);
|
|
746
|
+
instance.removeEventListener?.(import_secure_fields2.SecureFields.Events.CARD_VAULT_FAILURE, handleFailure);
|
|
747
|
+
instance.removeEventListener?.(import_secure_fields2.SecureFields.Events.THREE_DS_START, handleThreeDSStart);
|
|
748
|
+
instance.removeEventListener?.(import_secure_fields2.SecureFields.Events.THREE_DS_FINISH, handleThreeDSFinish);
|
|
749
|
+
};
|
|
750
|
+
}, [instance]);
|
|
751
|
+
const registerField = (0, import_react7.useCallback)(
|
|
752
|
+
(kind, element, options = {}) => {
|
|
753
|
+
if (!instance) return;
|
|
754
|
+
if (kind === "number") instance.addCardNumberField(element, options);
|
|
755
|
+
else if (kind === "expiry") instance.addExpiryDateField(element, options);
|
|
756
|
+
else if (kind === "cvv") instance.addSecurityCodeField(element, options);
|
|
757
|
+
else instance.addField(element, { type: "postalCode", ...options });
|
|
758
|
+
setMountedCount((n) => n + 1);
|
|
759
|
+
},
|
|
760
|
+
[instance]
|
|
761
|
+
);
|
|
762
|
+
(0, import_react7.useEffect)(() => {
|
|
763
|
+
if (mountedCount >= FIELDS_REQUIRED_TO_BE_READY) setStatus("ready");
|
|
764
|
+
}, [mountedCount]);
|
|
765
|
+
const registerThreeDSecure = (0, import_react7.useCallback)(
|
|
766
|
+
(element, options) => {
|
|
767
|
+
threeDSElement.current = element;
|
|
768
|
+
threeDSOptions.current = options;
|
|
769
|
+
setThreeDSMount(element);
|
|
770
|
+
},
|
|
771
|
+
[]
|
|
772
|
+
);
|
|
773
|
+
(0, import_react7.useEffect)(() => {
|
|
774
|
+
if (!instance || !threeDSMount) return;
|
|
775
|
+
try {
|
|
776
|
+
instance.addThreeDSecure(
|
|
777
|
+
threeDSMount,
|
|
778
|
+
threeDSOptions.current?.challengeWindowSize ? { challengeWindowSize: threeDSOptions.current.challengeWindowSize } : void 0
|
|
779
|
+
);
|
|
780
|
+
} catch (err) {
|
|
781
|
+
setError({
|
|
782
|
+
code: "three_ds_arm_failed",
|
|
783
|
+
message: err?.message ?? "Could not arm 3-D Secure."
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}, [instance, threeDSMount]);
|
|
787
|
+
const cancelChallenge = (0, import_react7.useCallback)(() => {
|
|
788
|
+
challengeWaiter.current?.reject({
|
|
789
|
+
code: "three_ds_abandoned",
|
|
790
|
+
message: "The 3-D Secure challenge was abandoned."
|
|
791
|
+
});
|
|
792
|
+
}, []);
|
|
793
|
+
const vault = (0, import_react7.useCallback)(() => {
|
|
794
|
+
if (!instance) {
|
|
795
|
+
return Promise.reject({
|
|
796
|
+
code: "not_ready",
|
|
797
|
+
message: "The card fields are not ready yet."
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
if (vaultWaiter.current) {
|
|
801
|
+
return Promise.reject({
|
|
802
|
+
code: "vault_in_progress",
|
|
803
|
+
message: "A card vault is already in progress."
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
setError(null);
|
|
807
|
+
setStatus("vaulting");
|
|
808
|
+
return new Promise((resolve, reject) => {
|
|
809
|
+
vaultWaiter.current = {
|
|
810
|
+
resolve: () => {
|
|
811
|
+
vaultWaiter.current = null;
|
|
812
|
+
resolve();
|
|
813
|
+
},
|
|
814
|
+
reject: (e) => {
|
|
815
|
+
vaultWaiter.current = null;
|
|
816
|
+
setError(e);
|
|
817
|
+
setStatus("error");
|
|
818
|
+
reject(e);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
instance.submit();
|
|
822
|
+
});
|
|
823
|
+
}, [instance]);
|
|
824
|
+
const complete = (0, import_react7.useCallback)(async () => {
|
|
825
|
+
setStatus("authorizing");
|
|
826
|
+
const doComplete = completeFn ?? ((args) => defaultComplete({ ...args, apiBaseUrl }));
|
|
827
|
+
const doResolveChallenge = resolveChallengeFn ?? resolveChallenge;
|
|
828
|
+
try {
|
|
829
|
+
let res = await doComplete({ sessionId: checkoutSessionId, gr4vySessionId: session.sessionId });
|
|
830
|
+
if (res?.status === "requires_action") {
|
|
831
|
+
if (!threeDSElement.current) {
|
|
832
|
+
throw Object.assign(new Error("No 3-D Secure mount element registered (call registerThreeDSecure)."), {
|
|
833
|
+
code: "three_ds_element_missing"
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
if (!instance) {
|
|
837
|
+
throw Object.assign(new Error("The card fields are not ready yet."), { code: "not_ready" });
|
|
838
|
+
}
|
|
839
|
+
setStatus("challenging");
|
|
840
|
+
await new Promise((resolve, reject) => {
|
|
841
|
+
const timeoutId = setTimeout(() => {
|
|
842
|
+
challengeWaiter.current?.reject({
|
|
843
|
+
code: "three_ds_timeout",
|
|
844
|
+
message: "The 3-D Secure challenge timed out."
|
|
845
|
+
});
|
|
846
|
+
}, challengeTimeoutMs);
|
|
847
|
+
challengeWaiter.current = {
|
|
848
|
+
resolve: () => {
|
|
849
|
+
clearTimeout(timeoutId);
|
|
850
|
+
challengeWaiter.current = null;
|
|
851
|
+
resolve();
|
|
852
|
+
},
|
|
853
|
+
reject: (e) => {
|
|
854
|
+
clearTimeout(timeoutId);
|
|
855
|
+
challengeWaiter.current = null;
|
|
856
|
+
reject(Object.assign(new Error(e.message), { code: e.code }));
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
doResolveChallenge({
|
|
860
|
+
instance,
|
|
861
|
+
element: threeDSElement.current,
|
|
862
|
+
...threeDSOptions.current ?? {}
|
|
863
|
+
}).then(
|
|
864
|
+
() => challengeWaiter.current?.resolve(),
|
|
865
|
+
// `resolveChallenge` calls `instance.addThreeDSecure()` synchronously
|
|
866
|
+
// inside its Promise executor, so an SDK throw rejects here; a
|
|
867
|
+
// merchant-supplied `resolveChallengeFn` can reject too. Without this
|
|
868
|
+
// arm the only exit was `challengeTimeoutMs` — five minutes stuck at
|
|
869
|
+
// `status: 'challenging'`, plus an unhandled rejection. Same
|
|
870
|
+
// code-passthrough convention as `complete()`'s catch: keep a `code`
|
|
871
|
+
// the resolver supplied, otherwise report `three_ds_failed`.
|
|
872
|
+
(err) => challengeWaiter.current?.reject({
|
|
873
|
+
code: err?.code ?? "three_ds_failed",
|
|
874
|
+
message: err?.message ?? "The 3-D Secure challenge could not be completed."
|
|
875
|
+
})
|
|
876
|
+
);
|
|
877
|
+
});
|
|
878
|
+
setStatus("authorizing");
|
|
879
|
+
res = await doComplete({ sessionId: checkoutSessionId, gr4vySessionId: session.sessionId });
|
|
880
|
+
if (res?.status === "requires_action") {
|
|
881
|
+
throw Object.assign(new Error("The payment still requires authentication after the 3-D Secure challenge."), {
|
|
882
|
+
code: "three_ds_unresolved"
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
setStatus("complete");
|
|
887
|
+
onSuccess?.(res);
|
|
888
|
+
return res;
|
|
889
|
+
} catch (err) {
|
|
890
|
+
setError({
|
|
891
|
+
code: err?.code ?? "complete_failed",
|
|
892
|
+
message: err?.message ?? "Payment could not be completed."
|
|
893
|
+
});
|
|
894
|
+
setStatus("error");
|
|
895
|
+
throw err;
|
|
896
|
+
}
|
|
897
|
+
}, [completeFn, resolveChallengeFn, challengeTimeoutMs, apiBaseUrl, instance, checkoutSessionId, session.sessionId, onSuccess]);
|
|
898
|
+
const pay = (0, import_react7.useCallback)(async () => {
|
|
899
|
+
await vault();
|
|
900
|
+
return complete();
|
|
901
|
+
}, [vault, complete]);
|
|
902
|
+
const value = {
|
|
903
|
+
instance,
|
|
904
|
+
registerField,
|
|
905
|
+
registerThreeDSecure,
|
|
906
|
+
cancelChallenge,
|
|
907
|
+
status,
|
|
908
|
+
error,
|
|
909
|
+
ready: status === "ready",
|
|
910
|
+
pay,
|
|
911
|
+
vault,
|
|
912
|
+
complete
|
|
913
|
+
};
|
|
914
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SecureCardContext.Provider, { value, children });
|
|
915
|
+
}
|
|
916
|
+
async function defaultComplete({
|
|
917
|
+
sessionId,
|
|
918
|
+
gr4vySessionId,
|
|
919
|
+
apiBaseUrl
|
|
920
|
+
}) {
|
|
921
|
+
const res = await fetch(joinApiUrl(apiBaseUrl, `/api/v1/checkout-sessions/${sessionId}/complete`), {
|
|
922
|
+
method: "POST",
|
|
923
|
+
headers: { "Content-Type": "application/json" },
|
|
924
|
+
body: JSON.stringify({ paymentMethod: "embedded", processorCheckoutSessionId: gr4vySessionId })
|
|
925
|
+
});
|
|
926
|
+
const body = await res.json();
|
|
927
|
+
if (!res.ok) {
|
|
928
|
+
throw Object.assign(new Error(body?.error?.message ?? "Payment could not be completed."), {
|
|
929
|
+
code: body?.error?.code ?? "payment_failed"
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
return body?.data ?? body;
|
|
933
|
+
}
|
|
934
|
+
function joinApiUrl(base, path) {
|
|
935
|
+
return `${base.replace(/\/+$/, "")}${path}`;
|
|
936
|
+
}
|
|
937
|
+
function useSecureCard() {
|
|
938
|
+
const ctx = (0, import_react7.useContext)(SecureCardContext);
|
|
939
|
+
if (!ctx) throw new Error("useSecureCard must be used inside <SecureCardProvider>");
|
|
940
|
+
return ctx;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/SecureCardFields.tsx
|
|
944
|
+
var import_react8 = require("react");
|
|
945
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
946
|
+
function useField(kind, options) {
|
|
947
|
+
const ctx = (0, import_react8.useContext)(SecureCardContext);
|
|
948
|
+
const hostRef = (0, import_react8.useRef)(null);
|
|
949
|
+
const instance = ctx?.instance ?? null;
|
|
950
|
+
(0, import_react8.useEffect)(() => {
|
|
951
|
+
const host = hostRef.current;
|
|
952
|
+
if (!host || !ctx || !instance) return;
|
|
953
|
+
const target = document.createElement("div");
|
|
954
|
+
target.style.height = "100%";
|
|
955
|
+
host.appendChild(target);
|
|
956
|
+
ctx.registerField(kind, target, options ?? {});
|
|
957
|
+
return () => {
|
|
958
|
+
while (host.firstChild) host.removeChild(host.firstChild);
|
|
959
|
+
};
|
|
960
|
+
}, [instance]);
|
|
961
|
+
return hostRef;
|
|
962
|
+
}
|
|
963
|
+
function CardNumberField({ className, style, options }) {
|
|
964
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: useField("number", options), className, style });
|
|
965
|
+
}
|
|
966
|
+
function ExpiryField({ className, style, options }) {
|
|
967
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: useField("expiry", options), className, style });
|
|
968
|
+
}
|
|
969
|
+
function SecurityCodeField({ className, style, options }) {
|
|
970
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: useField("cvv", options), className, style });
|
|
971
|
+
}
|
|
972
|
+
function PostalCodeField({ className, style, options }) {
|
|
973
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: useField("postalCode", options), className, style });
|
|
974
|
+
}
|
|
666
975
|
// Annotate the CommonJS export names for ESM import in node:
|
|
667
976
|
0 && (module.exports = {
|
|
977
|
+
CardNumberField,
|
|
668
978
|
CheckoutEmbed,
|
|
979
|
+
ExpiryField,
|
|
669
980
|
PaymentEmbed,
|
|
981
|
+
PostalCodeField,
|
|
982
|
+
SecureCardProvider,
|
|
983
|
+
SecurityCodeField,
|
|
670
984
|
ThrottleCheckout,
|
|
671
985
|
useCartSession,
|
|
986
|
+
useSecureCard,
|
|
672
987
|
useThrottleCheckout,
|
|
673
988
|
useThrottleEvents
|
|
674
989
|
});
|