@usethrottle/checkout-react 1.4.0 → 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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { CSSProperties, RefObject } from 'react';
2
+ import { CSSProperties, RefObject, ReactNode } from 'react';
3
3
  import { CartSessionCart, AddLineItemInput, UpdateLineItemInput, SelectShippingInput, CheckoutHandoffInput, CheckoutHandoff, SelectedShipping } from '@usethrottle/cart';
4
4
 
5
5
  /**
@@ -361,4 +361,205 @@ interface UseThrottleCheckoutResult {
361
361
  */
362
362
  declare function useThrottleCheckout(options: UseThrottleCheckoutOptions): UseThrottleCheckoutResult;
363
363
 
364
- export { CheckoutEmbed, type CheckoutEmbedExtraProps, type CheckoutTotals, PaymentEmbed, type RecurringIntent, CheckoutEmbed as ThrottleCheckout, type ThrottleCheckoutStatus, type ThrottleCompletedLineItem, type ThrottleEmbedProps, type ThrottleEnvelope, type ThrottleEvent, type ThrottleMessage, type ThrottleParentCommand, type ThrottleParentEnvelope, type ThrottleParentMessage, type UseCartSessionOptions, type UseCartSessionResult, type UseThrottleCheckoutOptions, type UseThrottleCheckoutResult, useCartSession, useThrottleCheckout, useThrottleEvents };
364
+ /**
365
+ * Subset of the Secure Fields instance this module needs — real or mocked.
366
+ *
367
+ * Declared structurally rather than as `Pick<SecureFields, …>`: any reference
368
+ * to the SDK's own type from an exported declaration makes tsup emit
369
+ * `import { SecureFields } from '@gr4vy/secure-fields'` into
370
+ * `dist/index.d.ts`, and that package's `exports` map carries no `types`
371
+ * condition — so a consumer on `moduleResolution: bundler` gets TS7016 under
372
+ * `skipLibCheck: false`, or silently degrades the member to `any` under
373
+ * `skipLibCheck: true` (the Next default). The `paths` override in this
374
+ * package's tsconfig repairs our own compile only; it does not ship.
375
+ *
376
+ * Members use method syntax deliberately: TypeScript checks method parameters
377
+ * bivariantly, so the real class's narrower `CombinedEvents` event parameter
378
+ * stays assignable to the `string` declared here.
379
+ */
380
+ interface ThreeDSecureInstance {
381
+ addThreeDSecure(element: string | HTMLElement, options?: {
382
+ challengeWindowSize?: '01' | '02' | '03' | '04' | '05';
383
+ }): void;
384
+ addEventListener(event: string, callback: (...args: any[]) => void): void;
385
+ removeEventListener?(event: string, callback: (...args: any[]) => void): void;
386
+ }
387
+ interface ResolveChallengeArgs {
388
+ /** The `SecureFields` instance already constructed for this session. */
389
+ instance: ThreeDSecureInstance;
390
+ /** Mount point for Gr4vy's own 3DS challenge iframe. */
391
+ element: HTMLElement | string;
392
+ challengeWindowSize?: '01' | '02' | '03' | '04' | '05';
393
+ }
394
+
395
+ /**
396
+ * Lifecycle status for the headless Secure Fields state machine.
397
+ * - `idle` — provider mounted, fields not yet registered
398
+ * - `mounting` — Secure Fields instance constructed, fields registering
399
+ * - `ready` — all required fields registered, submit() may be called
400
+ * - `vaulting` — submit() called, waiting on CARD_VAULT_SUCCESS/FAILURE
401
+ * - `authorizing` — card vaulted, awaiting the merchant's complete() call
402
+ * - `challenging` — complete() returned `requires_action`; Gr4vy's own 3DS
403
+ * iframe (addThreeDSecure) is mounted and running
404
+ * - `complete` — order created
405
+ * - `error` — vault, challenge, or completion failed (see `error`)
406
+ */
407
+ type SecureCardStatus = 'idle' | 'mounting' | 'ready' | 'vaulting' | 'authorizing' | 'challenging' | 'complete' | 'error';
408
+ /** The Task 2 `POST /checkout-sessions/:id/card-session` response shape. */
409
+ interface SecureCardSession {
410
+ sessionId: string;
411
+ gr4vyId: string;
412
+ environment: 'sandbox' | 'production';
413
+ }
414
+ interface SecureCardError {
415
+ code: string;
416
+ message: string;
417
+ }
418
+ type SecureCardFieldKind = 'number' | 'expiry' | 'cvv' | 'postalCode';
419
+ /**
420
+ * Everything this package uses off a `SecureFields` instance, declared
421
+ * structurally instead of re-exporting the SDK's own class type.
422
+ *
423
+ * `@gr4vy/secure-fields`'s `exports` map has no `types` condition, so naming
424
+ * its type in an exported declaration makes tsup emit
425
+ * `import { SecureFields } from '@gr4vy/secure-fields'` into
426
+ * `dist/index.d.ts` — which is a hard TS7016 for any consumer on
427
+ * `moduleResolution: bundler` with `skipLibCheck: false`, and a silent
428
+ * degradation of `instance` to `any` with `skipLibCheck: true` (the Next
429
+ * default). That hits every installer of this package, not only Secure Fields
430
+ * users. The `paths` override in this package's tsconfig repairs our own
431
+ * compile; it is monorepo-only and does not ship. Same approach as
432
+ * `ThreeDSecureInstance` in `threeDSecure.ts`.
433
+ *
434
+ * Members use method syntax deliberately: TypeScript checks method parameters
435
+ * bivariantly, so the real class's narrower option and event types stay
436
+ * assignable to the looser ones declared here.
437
+ */
438
+ interface SecureFieldsInstance {
439
+ /** Gr4vy's hidden controller iframe, removed when the provider unmounts. */
440
+ controller?: HTMLIFrameElement;
441
+ addCardNumberField(element: string | HTMLElement, options?: Record<string, unknown>): unknown;
442
+ addExpiryDateField(element: string | HTMLElement, options?: Record<string, unknown>): unknown;
443
+ addSecurityCodeField(element: string | HTMLElement, options?: Record<string, unknown>): unknown;
444
+ addField(element: string | HTMLElement, options: Record<string, unknown>): unknown;
445
+ addThreeDSecure(element: string | HTMLElement, options?: {
446
+ challengeWindowSize?: '01' | '02' | '03' | '04' | '05';
447
+ }): void;
448
+ addEventListener(event: string, callback: (...args: any[]) => void): void;
449
+ removeEventListener?(event: string, callback: (...args: any[]) => void): void;
450
+ submit(): void;
451
+ }
452
+ interface SecureCardContextValue {
453
+ /**
454
+ * The underlying Secure Fields instance — Task 5's field primitives register
455
+ * against it.
456
+ *
457
+ * `null` until the provider's mount effect has constructed it. It cannot be
458
+ * built during render: `new SecureFields()` mutates the document (see the
459
+ * effect in `SecureCardProvider`), and React does not promise a render-phase
460
+ * factory runs exactly once. Consumers that reach for it directly must
461
+ * null-check; the field primitives already wait for it.
462
+ */
463
+ instance: SecureFieldsInstance | null;
464
+ registerField: (kind: SecureCardFieldKind, element: HTMLElement, options?: Record<string, unknown>) => void;
465
+ /**
466
+ * Mount point for Gr4vy's own 3DS challenge iframe (`addThreeDSecure`).
467
+ * Register this once, alongside the card fields; it is only actually
468
+ * mounted if `complete()` gets `requires_action`.
469
+ */
470
+ registerThreeDSecure: (element: HTMLElement | string, options?: {
471
+ challengeWindowSize?: '01' | '02' | '03' | '04' | '05';
472
+ }) => void;
473
+ /**
474
+ * Abandons an in-progress challenge (e.g. the buyer closed the challenge
475
+ * UI). Only meaningful while `status === 'challenging'`; settles
476
+ * `complete()`'s promise with `error.code === 'three_ds_abandoned'`, kept
477
+ * distinct from a processor decline code.
478
+ */
479
+ cancelChallenge: () => void;
480
+ status: SecureCardStatus;
481
+ error: SecureCardError | null;
482
+ /** True once all required fields are registered and submit() may be called. */
483
+ ready: boolean;
484
+ /** vault() then complete(). */
485
+ pay: () => Promise<unknown>;
486
+ vault: () => Promise<void>;
487
+ complete: () => Promise<unknown>;
488
+ }
489
+ interface SecureCardProviderProps {
490
+ session: SecureCardSession;
491
+ /**
492
+ * Throttle's own checkout session id — the `:id` already used to mint
493
+ * `session` via `POST /checkout-sessions/:id/card-session`. NOT the same
494
+ * value as `session.sessionId`: that field is Gr4vy's own checkout session
495
+ * id (required to construct the `SecureFields` instance — see
496
+ * `packages/platform-api/src/routes/card-session.ts`, which returns
497
+ * `{ sessionId: checkoutSessionId, ... }` from `adapter.createCardSession()`,
498
+ * a Gr4vy-minted id). The public `POST /checkout-sessions/:id/complete`
499
+ * route looks `:id` up directly against Throttle's own `checkoutSessions`
500
+ * table (`checkout-sessions.ts`), so `defaultComplete` needs this id, not
501
+ * `session.sessionId`, to call the right URL.
502
+ */
503
+ checkoutSessionId: string;
504
+ children: ReactNode;
505
+ /** Injected for tests / custom completion flows; defaults to a real POST /complete call. */
506
+ completeFn?: (args: {
507
+ sessionId: string;
508
+ gr4vySessionId: string;
509
+ }) => Promise<any>;
510
+ /** Injected for tests / custom 3DS UI; defaults to mounting Gr4vy's own 3DS iframe via addThreeDSecure. */
511
+ resolveChallengeFn?: (args: ResolveChallengeArgs) => Promise<void>;
512
+ /**
513
+ * Bounds how long `complete()` waits on an unfinished challenge before
514
+ * settling to a terminal `error` (code `three_ds_timeout`) on its own — the
515
+ * only other exits are Gr4vy's own `THREE_DS_FINISH` and an explicit
516
+ * `cancelChallenge()` call, neither of which is guaranteed to ever fire if
517
+ * the buyer just abandons the tab. Not sourced from Gr4vy documentation
518
+ * (no challenge-duration limit is published in the SDK); 5 minutes is a
519
+ * conservative, overridable default.
520
+ */
521
+ challengeTimeoutMs?: number;
522
+ /**
523
+ * Base URL of the Throttle API (e.g. `https://api.usethrottle.dev`).
524
+ * Required — not optional-with-a-same-origin-default. Secure Fields is the
525
+ * headless surface for merchants who mount the card form on THEIR OWN
526
+ * origin, which by definition is not same-origin with the Throttle API, so
527
+ * `defaultComplete`'s `fetch()` can never resolve a relative path (it 404s
528
+ * against the merchant's own domain at payment time instead). Making this
529
+ * required turns that into a compile error instead of a silent runtime
530
+ * failure. Joined onto the request path regardless of a trailing slash.
531
+ */
532
+ apiBaseUrl: string;
533
+ onSuccess?: (result: SecureCardCompleteResult) => void;
534
+ }
535
+ /**
536
+ * The real `POST /checkout-sessions/:id/complete` success body for the
537
+ * Secure Fields flow (`checkout-session-service.ts` `complete()`,
538
+ * proxy-mode-embedded return, ~line 2338 and its idempotent-retry twin at
539
+ * ~line 1849). There is no `order` key.
540
+ */
541
+ interface SecureCardCompleteResult {
542
+ orderId: string;
543
+ orderNumber: string;
544
+ paymentId: string;
545
+ /** Minor units (cents). */
546
+ total: number;
547
+ currency: string;
548
+ status: string;
549
+ paymentStatus: string;
550
+ }
551
+ declare function SecureCardProvider({ session, checkoutSessionId, children, completeFn, resolveChallengeFn, challengeTimeoutMs, apiBaseUrl, onSuccess, }: SecureCardProviderProps): react_jsx_runtime.JSX.Element;
552
+ declare function useSecureCard(): SecureCardContextValue;
553
+
554
+ interface SecureCardFieldProps {
555
+ className?: string;
556
+ style?: CSSProperties;
557
+ /** Gr4vy field options (styles, autoFocus, maskInput, pattern…) passed through unchanged. */
558
+ options?: Record<string, unknown>;
559
+ }
560
+ declare function CardNumberField({ className, style, options }: SecureCardFieldProps): react_jsx_runtime.JSX.Element;
561
+ declare function ExpiryField({ className, style, options }: SecureCardFieldProps): react_jsx_runtime.JSX.Element;
562
+ declare function SecurityCodeField({ className, style, options }: SecureCardFieldProps): react_jsx_runtime.JSX.Element;
563
+ declare function PostalCodeField({ className, style, options }: SecureCardFieldProps): react_jsx_runtime.JSX.Element;
564
+
565
+ export { CardNumberField, CheckoutEmbed, type CheckoutEmbedExtraProps, type CheckoutTotals, ExpiryField, PaymentEmbed, PostalCodeField, type RecurringIntent, type ResolveChallengeArgs, type SecureCardCompleteResult, type SecureCardContextValue, type SecureCardError, type SecureCardFieldKind, type SecureCardFieldProps, SecureCardProvider, type SecureCardProviderProps, type SecureCardSession, type SecureCardStatus, type SecureFieldsInstance, SecurityCodeField, type ThreeDSecureInstance, CheckoutEmbed as ThrottleCheckout, type ThrottleCheckoutStatus, type ThrottleCompletedLineItem, type ThrottleEmbedProps, type ThrottleEnvelope, type ThrottleEvent, type ThrottleMessage, type ThrottleParentCommand, type ThrottleParentEnvelope, type ThrottleParentMessage, type UseCartSessionOptions, type UseCartSessionResult, type UseThrottleCheckoutOptions, type UseThrottleCheckoutResult, useCartSession, useSecureCard, useThrottleCheckout, useThrottleEvents };
package/dist/index.js CHANGED
@@ -638,11 +638,327 @@ function useThrottleCheckout(options) {
638
638
  reset
639
639
  };
640
640
  }
641
+
642
+ // src/useSecureCard.tsx
643
+ import {
644
+ createContext,
645
+ useCallback as useCallback5,
646
+ useContext,
647
+ useEffect as useEffect5,
648
+ useRef as useRef4,
649
+ useState as useState6
650
+ } from "react";
651
+ import { SecureFields as SecureFields2 } from "@gr4vy/secure-fields";
652
+
653
+ // src/threeDSecure.ts
654
+ import { SecureFields } from "@gr4vy/secure-fields";
655
+ function resolveChallenge({ instance, element, challengeWindowSize }) {
656
+ return new Promise((resolve) => {
657
+ const handleFinish = () => {
658
+ instance.removeEventListener?.(SecureFields.Events.THREE_DS_FINISH, handleFinish);
659
+ resolve();
660
+ };
661
+ instance.addEventListener(SecureFields.Events.THREE_DS_FINISH, handleFinish);
662
+ instance.addThreeDSecure(element, challengeWindowSize ? { challengeWindowSize } : void 0);
663
+ });
664
+ }
665
+
666
+ // src/useSecureCard.tsx
667
+ import { jsx as jsx3 } from "react/jsx-runtime";
668
+ var SecureCardContext = createContext(null);
669
+ var FIELDS_REQUIRED_TO_BE_READY = 3;
670
+ var DEFAULT_CHALLENGE_TIMEOUT_MS = 5 * 60 * 1e3;
671
+ function SecureCardProvider({
672
+ session,
673
+ checkoutSessionId,
674
+ children,
675
+ completeFn,
676
+ resolveChallengeFn,
677
+ challengeTimeoutMs = DEFAULT_CHALLENGE_TIMEOUT_MS,
678
+ apiBaseUrl,
679
+ onSuccess
680
+ }) {
681
+ const [status, setStatus] = useState6("idle");
682
+ const [error, setError] = useState6(null);
683
+ const [mountedCount, setMountedCount] = useState6(0);
684
+ const vaultWaiter = useRef4(null);
685
+ const threeDSElement = useRef4(null);
686
+ const threeDSOptions = useRef4(void 0);
687
+ const challengeWaiter = useRef4(null);
688
+ const { gr4vyId, environment, sessionId } = session;
689
+ const [instance, setInstance] = useState6(null);
690
+ const [threeDSMount, setThreeDSMount] = useState6(null);
691
+ useEffect5(() => {
692
+ const created = new SecureFields2({ gr4vyId, environment, sessionId });
693
+ setInstance(created);
694
+ setMountedCount(0);
695
+ setStatus((current) => current === "idle" || current === "ready" ? "mounting" : current);
696
+ return () => {
697
+ created.controller?.remove();
698
+ setInstance(null);
699
+ };
700
+ }, [gr4vyId, environment, sessionId]);
701
+ useEffect5(() => {
702
+ if (!instance) return;
703
+ const handleSuccess = () => {
704
+ vaultWaiter.current?.resolve();
705
+ };
706
+ const handleFailure = (data) => {
707
+ vaultWaiter.current?.reject({
708
+ code: data?.code ?? "card_vault_failed",
709
+ message: data?.message ?? "The card could not be saved."
710
+ });
711
+ };
712
+ const handleThreeDSStart = () => setStatus("challenging");
713
+ const handleThreeDSFinish = () => {
714
+ setStatus((current) => current === "challenging" ? "vaulting" : current);
715
+ };
716
+ instance.addEventListener(SecureFields2.Events.CARD_VAULT_SUCCESS, handleSuccess);
717
+ instance.addEventListener(SecureFields2.Events.CARD_VAULT_FAILURE, handleFailure);
718
+ instance.addEventListener(SecureFields2.Events.THREE_DS_START, handleThreeDSStart);
719
+ instance.addEventListener(SecureFields2.Events.THREE_DS_FINISH, handleThreeDSFinish);
720
+ return () => {
721
+ instance.removeEventListener?.(SecureFields2.Events.CARD_VAULT_SUCCESS, handleSuccess);
722
+ instance.removeEventListener?.(SecureFields2.Events.CARD_VAULT_FAILURE, handleFailure);
723
+ instance.removeEventListener?.(SecureFields2.Events.THREE_DS_START, handleThreeDSStart);
724
+ instance.removeEventListener?.(SecureFields2.Events.THREE_DS_FINISH, handleThreeDSFinish);
725
+ };
726
+ }, [instance]);
727
+ const registerField = useCallback5(
728
+ (kind, element, options = {}) => {
729
+ if (!instance) return;
730
+ if (kind === "number") instance.addCardNumberField(element, options);
731
+ else if (kind === "expiry") instance.addExpiryDateField(element, options);
732
+ else if (kind === "cvv") instance.addSecurityCodeField(element, options);
733
+ else instance.addField(element, { type: "postalCode", ...options });
734
+ setMountedCount((n) => n + 1);
735
+ },
736
+ [instance]
737
+ );
738
+ useEffect5(() => {
739
+ if (mountedCount >= FIELDS_REQUIRED_TO_BE_READY) setStatus("ready");
740
+ }, [mountedCount]);
741
+ const registerThreeDSecure = useCallback5(
742
+ (element, options) => {
743
+ threeDSElement.current = element;
744
+ threeDSOptions.current = options;
745
+ setThreeDSMount(element);
746
+ },
747
+ []
748
+ );
749
+ useEffect5(() => {
750
+ if (!instance || !threeDSMount) return;
751
+ try {
752
+ instance.addThreeDSecure(
753
+ threeDSMount,
754
+ threeDSOptions.current?.challengeWindowSize ? { challengeWindowSize: threeDSOptions.current.challengeWindowSize } : void 0
755
+ );
756
+ } catch (err) {
757
+ setError({
758
+ code: "three_ds_arm_failed",
759
+ message: err?.message ?? "Could not arm 3-D Secure."
760
+ });
761
+ }
762
+ }, [instance, threeDSMount]);
763
+ const cancelChallenge = useCallback5(() => {
764
+ challengeWaiter.current?.reject({
765
+ code: "three_ds_abandoned",
766
+ message: "The 3-D Secure challenge was abandoned."
767
+ });
768
+ }, []);
769
+ const vault = useCallback5(() => {
770
+ if (!instance) {
771
+ return Promise.reject({
772
+ code: "not_ready",
773
+ message: "The card fields are not ready yet."
774
+ });
775
+ }
776
+ if (vaultWaiter.current) {
777
+ return Promise.reject({
778
+ code: "vault_in_progress",
779
+ message: "A card vault is already in progress."
780
+ });
781
+ }
782
+ setError(null);
783
+ setStatus("vaulting");
784
+ return new Promise((resolve, reject) => {
785
+ vaultWaiter.current = {
786
+ resolve: () => {
787
+ vaultWaiter.current = null;
788
+ resolve();
789
+ },
790
+ reject: (e) => {
791
+ vaultWaiter.current = null;
792
+ setError(e);
793
+ setStatus("error");
794
+ reject(e);
795
+ }
796
+ };
797
+ instance.submit();
798
+ });
799
+ }, [instance]);
800
+ const complete = useCallback5(async () => {
801
+ setStatus("authorizing");
802
+ const doComplete = completeFn ?? ((args) => defaultComplete({ ...args, apiBaseUrl }));
803
+ const doResolveChallenge = resolveChallengeFn ?? resolveChallenge;
804
+ try {
805
+ let res = await doComplete({ sessionId: checkoutSessionId, gr4vySessionId: session.sessionId });
806
+ if (res?.status === "requires_action") {
807
+ if (!threeDSElement.current) {
808
+ throw Object.assign(new Error("No 3-D Secure mount element registered (call registerThreeDSecure)."), {
809
+ code: "three_ds_element_missing"
810
+ });
811
+ }
812
+ if (!instance) {
813
+ throw Object.assign(new Error("The card fields are not ready yet."), { code: "not_ready" });
814
+ }
815
+ setStatus("challenging");
816
+ await new Promise((resolve, reject) => {
817
+ const timeoutId = setTimeout(() => {
818
+ challengeWaiter.current?.reject({
819
+ code: "three_ds_timeout",
820
+ message: "The 3-D Secure challenge timed out."
821
+ });
822
+ }, challengeTimeoutMs);
823
+ challengeWaiter.current = {
824
+ resolve: () => {
825
+ clearTimeout(timeoutId);
826
+ challengeWaiter.current = null;
827
+ resolve();
828
+ },
829
+ reject: (e) => {
830
+ clearTimeout(timeoutId);
831
+ challengeWaiter.current = null;
832
+ reject(Object.assign(new Error(e.message), { code: e.code }));
833
+ }
834
+ };
835
+ doResolveChallenge({
836
+ instance,
837
+ element: threeDSElement.current,
838
+ ...threeDSOptions.current ?? {}
839
+ }).then(
840
+ () => challengeWaiter.current?.resolve(),
841
+ // `resolveChallenge` calls `instance.addThreeDSecure()` synchronously
842
+ // inside its Promise executor, so an SDK throw rejects here; a
843
+ // merchant-supplied `resolveChallengeFn` can reject too. Without this
844
+ // arm the only exit was `challengeTimeoutMs` — five minutes stuck at
845
+ // `status: 'challenging'`, plus an unhandled rejection. Same
846
+ // code-passthrough convention as `complete()`'s catch: keep a `code`
847
+ // the resolver supplied, otherwise report `three_ds_failed`.
848
+ (err) => challengeWaiter.current?.reject({
849
+ code: err?.code ?? "three_ds_failed",
850
+ message: err?.message ?? "The 3-D Secure challenge could not be completed."
851
+ })
852
+ );
853
+ });
854
+ setStatus("authorizing");
855
+ res = await doComplete({ sessionId: checkoutSessionId, gr4vySessionId: session.sessionId });
856
+ if (res?.status === "requires_action") {
857
+ throw Object.assign(new Error("The payment still requires authentication after the 3-D Secure challenge."), {
858
+ code: "three_ds_unresolved"
859
+ });
860
+ }
861
+ }
862
+ setStatus("complete");
863
+ onSuccess?.(res);
864
+ return res;
865
+ } catch (err) {
866
+ setError({
867
+ code: err?.code ?? "complete_failed",
868
+ message: err?.message ?? "Payment could not be completed."
869
+ });
870
+ setStatus("error");
871
+ throw err;
872
+ }
873
+ }, [completeFn, resolveChallengeFn, challengeTimeoutMs, apiBaseUrl, instance, checkoutSessionId, session.sessionId, onSuccess]);
874
+ const pay = useCallback5(async () => {
875
+ await vault();
876
+ return complete();
877
+ }, [vault, complete]);
878
+ const value = {
879
+ instance,
880
+ registerField,
881
+ registerThreeDSecure,
882
+ cancelChallenge,
883
+ status,
884
+ error,
885
+ ready: status === "ready",
886
+ pay,
887
+ vault,
888
+ complete
889
+ };
890
+ return /* @__PURE__ */ jsx3(SecureCardContext.Provider, { value, children });
891
+ }
892
+ async function defaultComplete({
893
+ sessionId,
894
+ gr4vySessionId,
895
+ apiBaseUrl
896
+ }) {
897
+ const res = await fetch(joinApiUrl(apiBaseUrl, `/api/v1/checkout-sessions/${sessionId}/complete`), {
898
+ method: "POST",
899
+ headers: { "Content-Type": "application/json" },
900
+ body: JSON.stringify({ paymentMethod: "embedded", processorCheckoutSessionId: gr4vySessionId })
901
+ });
902
+ const body = await res.json();
903
+ if (!res.ok) {
904
+ throw Object.assign(new Error(body?.error?.message ?? "Payment could not be completed."), {
905
+ code: body?.error?.code ?? "payment_failed"
906
+ });
907
+ }
908
+ return body?.data ?? body;
909
+ }
910
+ function joinApiUrl(base, path) {
911
+ return `${base.replace(/\/+$/, "")}${path}`;
912
+ }
913
+ function useSecureCard() {
914
+ const ctx = useContext(SecureCardContext);
915
+ if (!ctx) throw new Error("useSecureCard must be used inside <SecureCardProvider>");
916
+ return ctx;
917
+ }
918
+
919
+ // src/SecureCardFields.tsx
920
+ import { useContext as useContext2, useEffect as useEffect6, useRef as useRef5 } from "react";
921
+ import { jsx as jsx4 } from "react/jsx-runtime";
922
+ function useField(kind, options) {
923
+ const ctx = useContext2(SecureCardContext);
924
+ const hostRef = useRef5(null);
925
+ const instance = ctx?.instance ?? null;
926
+ useEffect6(() => {
927
+ const host = hostRef.current;
928
+ if (!host || !ctx || !instance) return;
929
+ const target = document.createElement("div");
930
+ target.style.height = "100%";
931
+ host.appendChild(target);
932
+ ctx.registerField(kind, target, options ?? {});
933
+ return () => {
934
+ while (host.firstChild) host.removeChild(host.firstChild);
935
+ };
936
+ }, [instance]);
937
+ return hostRef;
938
+ }
939
+ function CardNumberField({ className, style, options }) {
940
+ return /* @__PURE__ */ jsx4("div", { ref: useField("number", options), className, style });
941
+ }
942
+ function ExpiryField({ className, style, options }) {
943
+ return /* @__PURE__ */ jsx4("div", { ref: useField("expiry", options), className, style });
944
+ }
945
+ function SecurityCodeField({ className, style, options }) {
946
+ return /* @__PURE__ */ jsx4("div", { ref: useField("cvv", options), className, style });
947
+ }
948
+ function PostalCodeField({ className, style, options }) {
949
+ return /* @__PURE__ */ jsx4("div", { ref: useField("postalCode", options), className, style });
950
+ }
641
951
  export {
952
+ CardNumberField,
642
953
  CheckoutEmbed,
954
+ ExpiryField,
643
955
  PaymentEmbed,
956
+ PostalCodeField,
957
+ SecureCardProvider,
958
+ SecurityCodeField,
644
959
  CheckoutEmbed as ThrottleCheckout,
645
960
  useCartSession,
961
+ useSecureCard,
646
962
  useThrottleCheckout,
647
963
  useThrottleEvents
648
964
  };