@resira/ui 0.4.0 → 0.4.2

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.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { Product, WeightedBaseUrl, Resira, RefundRule, TimeSlotAvailability, DurationPrice, PublicResource, ValidatePromoCodeResponse, Reservation, AvailabilityParams, Availability, Dish, PaymentIntentResponse, CreatePaymentIntentRequest, CreateReservationRequest } from '@resira/sdk';
4
- export { ConfirmPaymentRequest, ConfirmPaymentResponse, CreatePaymentIntentRequest, Dish, DishListResponse, DishModel, DishResponse, PaymentIntentResponse, PricingModel, Product, ProductListResponse, PublicResource, ResourceListResponse } from '@resira/sdk';
3
+ import { Product, WeightedBaseUrl, Resira, RefundRule, TimeSlotAvailability, DurationPrice, PublicResource, ValidatePromoCodeResponse, Reservation, AvailabilityParams, Availability, CheckoutSession, Dish, PaymentIntentResponse, CreatePaymentIntentRequest, CreateReservationRequest } from '@resira/sdk';
4
+ export { CheckoutConfig, CheckoutSession, CheckoutSessionResponse, ConfirmPaymentRequest, ConfirmPaymentResponse, CreateCheckoutSessionRequest, CreateCheckoutSessionResponse, CreatePaymentIntentRequest, Dish, DishListResponse, DishModel, DishResponse, PaymentIntentResponse, PricingModel, Product, ProductListResponse, ProductVariant, PublicResource, ResourceListResponse } from '@resira/sdk';
5
5
 
6
6
  type ResiraDomain = "rental" | "restaurant" | "watersport" | "service";
7
7
  /** Layout direction for the product/service selector. */
@@ -320,6 +320,8 @@ interface ResiraProviderProps {
320
320
  domain: ResiraDomain;
321
321
  /** Configuration overrides. */
322
322
  config?: ResiraProviderConfig;
323
+ /** Checkout session token. When provided, skips service selection and opens directly into checkout. */
324
+ checkoutSessionToken?: string;
323
325
  /** Callback when the modal/widget should close. */
324
326
  onClose?: () => void;
325
327
  /** Child components. */
@@ -392,9 +394,11 @@ interface ResiraContextValue {
392
394
  }) => void;
393
395
  /** Called when an error occurs during the booking flow. */
394
396
  onError?: (code: string, message: string) => void;
397
+ /** Checkout session token — when provided, the widget skips to checkout. */
398
+ checkoutSessionToken?: string;
395
399
  }
396
400
  /** Steps in the booking flow. */
397
- type BookingStep = "resource" | "availability" | "details" | "terms" | "payment" | "confirmation";
401
+ type BookingStep = "resource" | "availability" | "details" | "terms" | "payment" | "confirmation" | "checkout";
398
402
  /** Selection state from the availability step. */
399
403
  interface BookingSelection {
400
404
  /** Selected start date (YYYY-MM-DD). */
@@ -438,7 +442,7 @@ declare function useResira(): ResiraContextValue;
438
442
  * </ResiraProvider>
439
443
  * ```
440
444
  */
441
- declare function ResiraProvider({ apiKey, resourceId, domain, config, onClose, children, }: ResiraProviderProps): react_jsx_runtime.JSX.Element;
445
+ declare function ResiraProvider({ apiKey, resourceId, domain, config, checkoutSessionToken, onClose, children, }: ResiraProviderProps): react_jsx_runtime.JSX.Element;
442
446
 
443
447
  declare function ResiraBookingWidget(): react_jsx_runtime.JSX.Element;
444
448
 
@@ -451,6 +455,8 @@ interface BookingModalProps {
451
455
  resourceId?: string;
452
456
  /** Configuration overrides. */
453
457
  config?: ResiraProviderConfig;
458
+ /** Checkout session token. When provided, skips to checkout. */
459
+ checkoutSessionToken?: string;
454
460
  /** Custom trigger button — if omitted, uses the default "Book Now" button. */
455
461
  trigger?: React.ReactNode;
456
462
  /** Custom button label. @default "Book Now" */
@@ -460,7 +466,7 @@ interface BookingModalProps {
460
466
  /** Custom button style. */
461
467
  buttonStyle?: React.CSSProperties;
462
468
  }
463
- declare function BookingModal({ apiKey, domain, resourceId, config, trigger, buttonLabel, buttonClassName, buttonStyle, }: BookingModalProps): react_jsx_runtime.JSX.Element;
469
+ declare function BookingModal({ apiKey, domain, resourceId, config, checkoutSessionToken, trigger, buttonLabel, buttonClassName, buttonStyle, }: BookingModalProps): react_jsx_runtime.JSX.Element;
464
470
 
465
471
  interface BookingCalendarProps {
466
472
  /** Set of blocked/unavailable dates (YYYY-MM-DD). */
@@ -759,6 +765,31 @@ interface UseDishesReturn {
759
765
  * ```
760
766
  */
761
767
  declare function useDishes(enabled?: boolean): UseDishesReturn;
768
+ /** Checkout session error codes for UX branching. */
769
+ type CheckoutSessionErrorCode = "not_found" | "consumed" | "expired" | "unknown";
770
+ interface UseCheckoutSessionReturn {
771
+ /** The hydrated checkout session. */
772
+ session: CheckoutSession | null;
773
+ /** Whether the session is loading. */
774
+ loading: boolean;
775
+ /** Error message if loading failed. */
776
+ error: string | null;
777
+ /** Structured error code for UX branching. */
778
+ errorCode: CheckoutSessionErrorCode | null;
779
+ }
780
+ /**
781
+ * Fetch and hydrate a checkout session by token.
782
+ *
783
+ * Handles the three error cases from the API:
784
+ * - 404 → "Invalid booking link"
785
+ * - "already consumed" → "This booking was already completed"
786
+ * - "expired" → "Session expired"
787
+ *
788
+ * ```tsx
789
+ * const { session, loading, error, errorCode } = useCheckoutSession("cs_ab12cd34...");
790
+ * ```
791
+ */
792
+ declare function useCheckoutSession(token: string | undefined): UseCheckoutSessionReturn;
762
793
 
763
794
  /** Default theme values. */
764
795
  declare const DEFAULT_THEME: Required<ResiraTheme>;
@@ -798,4 +829,4 @@ declare function TagIcon({ size, className }: IconProps): react_jsx_runtime.JSX.
798
829
  declare function CubeIcon({ size, className }: IconProps): react_jsx_runtime.JSX.Element;
799
830
  declare function ViewfinderIcon({ size, className }: IconProps): react_jsx_runtime.JSX.Element;
800
831
 
801
- export { AlertCircleIcon, BookingCalendar, BookingModal, type BookingSelection, type BookingStep, CalendarIcon, CheckCircleIcon, CheckIcon, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, type DeeplinkGuest, type DeeplinkSelection, DishShowcase, type DishShowcaseProps, type DomainConfig, GuestForm, type GuestFormErrors, type GuestFormValues, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, ResiraBookingWidget, type ResiraClassNames, type ResiraContextValue, type ResiraDomain, type ResiraLocale, ResiraProvider, type ResiraProviderConfig, type ResiraProviderProps, type ResiraTheme, ResourcePicker, type ServiceLayout, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, resolveTheme, themeToCSS, useAvailability, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, validateGuestForm };
832
+ export { AlertCircleIcon, BookingCalendar, BookingModal, type BookingSelection, type BookingStep, CalendarIcon, CheckCircleIcon, CheckIcon, type CheckoutSessionErrorCode, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, type DeeplinkGuest, type DeeplinkSelection, DishShowcase, type DishShowcaseProps, type DomainConfig, GuestForm, type GuestFormErrors, type GuestFormValues, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, ResiraBookingWidget, type ResiraClassNames, type ResiraContextValue, type ResiraDomain, type ResiraLocale, ResiraProvider, type ResiraProviderConfig, type ResiraProviderProps, type ResiraTheme, ResourcePicker, type ServiceLayout, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, validateGuestForm };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { Product, WeightedBaseUrl, Resira, RefundRule, TimeSlotAvailability, DurationPrice, PublicResource, ValidatePromoCodeResponse, Reservation, AvailabilityParams, Availability, Dish, PaymentIntentResponse, CreatePaymentIntentRequest, CreateReservationRequest } from '@resira/sdk';
4
- export { ConfirmPaymentRequest, ConfirmPaymentResponse, CreatePaymentIntentRequest, Dish, DishListResponse, DishModel, DishResponse, PaymentIntentResponse, PricingModel, Product, ProductListResponse, PublicResource, ResourceListResponse } from '@resira/sdk';
3
+ import { Product, WeightedBaseUrl, Resira, RefundRule, TimeSlotAvailability, DurationPrice, PublicResource, ValidatePromoCodeResponse, Reservation, AvailabilityParams, Availability, CheckoutSession, Dish, PaymentIntentResponse, CreatePaymentIntentRequest, CreateReservationRequest } from '@resira/sdk';
4
+ export { CheckoutConfig, CheckoutSession, CheckoutSessionResponse, ConfirmPaymentRequest, ConfirmPaymentResponse, CreateCheckoutSessionRequest, CreateCheckoutSessionResponse, CreatePaymentIntentRequest, Dish, DishListResponse, DishModel, DishResponse, PaymentIntentResponse, PricingModel, Product, ProductListResponse, ProductVariant, PublicResource, ResourceListResponse } from '@resira/sdk';
5
5
 
6
6
  type ResiraDomain = "rental" | "restaurant" | "watersport" | "service";
7
7
  /** Layout direction for the product/service selector. */
@@ -320,6 +320,8 @@ interface ResiraProviderProps {
320
320
  domain: ResiraDomain;
321
321
  /** Configuration overrides. */
322
322
  config?: ResiraProviderConfig;
323
+ /** Checkout session token. When provided, skips service selection and opens directly into checkout. */
324
+ checkoutSessionToken?: string;
323
325
  /** Callback when the modal/widget should close. */
324
326
  onClose?: () => void;
325
327
  /** Child components. */
@@ -392,9 +394,11 @@ interface ResiraContextValue {
392
394
  }) => void;
393
395
  /** Called when an error occurs during the booking flow. */
394
396
  onError?: (code: string, message: string) => void;
397
+ /** Checkout session token — when provided, the widget skips to checkout. */
398
+ checkoutSessionToken?: string;
395
399
  }
396
400
  /** Steps in the booking flow. */
397
- type BookingStep = "resource" | "availability" | "details" | "terms" | "payment" | "confirmation";
401
+ type BookingStep = "resource" | "availability" | "details" | "terms" | "payment" | "confirmation" | "checkout";
398
402
  /** Selection state from the availability step. */
399
403
  interface BookingSelection {
400
404
  /** Selected start date (YYYY-MM-DD). */
@@ -438,7 +442,7 @@ declare function useResira(): ResiraContextValue;
438
442
  * </ResiraProvider>
439
443
  * ```
440
444
  */
441
- declare function ResiraProvider({ apiKey, resourceId, domain, config, onClose, children, }: ResiraProviderProps): react_jsx_runtime.JSX.Element;
445
+ declare function ResiraProvider({ apiKey, resourceId, domain, config, checkoutSessionToken, onClose, children, }: ResiraProviderProps): react_jsx_runtime.JSX.Element;
442
446
 
443
447
  declare function ResiraBookingWidget(): react_jsx_runtime.JSX.Element;
444
448
 
@@ -451,6 +455,8 @@ interface BookingModalProps {
451
455
  resourceId?: string;
452
456
  /** Configuration overrides. */
453
457
  config?: ResiraProviderConfig;
458
+ /** Checkout session token. When provided, skips to checkout. */
459
+ checkoutSessionToken?: string;
454
460
  /** Custom trigger button — if omitted, uses the default "Book Now" button. */
455
461
  trigger?: React.ReactNode;
456
462
  /** Custom button label. @default "Book Now" */
@@ -460,7 +466,7 @@ interface BookingModalProps {
460
466
  /** Custom button style. */
461
467
  buttonStyle?: React.CSSProperties;
462
468
  }
463
- declare function BookingModal({ apiKey, domain, resourceId, config, trigger, buttonLabel, buttonClassName, buttonStyle, }: BookingModalProps): react_jsx_runtime.JSX.Element;
469
+ declare function BookingModal({ apiKey, domain, resourceId, config, checkoutSessionToken, trigger, buttonLabel, buttonClassName, buttonStyle, }: BookingModalProps): react_jsx_runtime.JSX.Element;
464
470
 
465
471
  interface BookingCalendarProps {
466
472
  /** Set of blocked/unavailable dates (YYYY-MM-DD). */
@@ -759,6 +765,31 @@ interface UseDishesReturn {
759
765
  * ```
760
766
  */
761
767
  declare function useDishes(enabled?: boolean): UseDishesReturn;
768
+ /** Checkout session error codes for UX branching. */
769
+ type CheckoutSessionErrorCode = "not_found" | "consumed" | "expired" | "unknown";
770
+ interface UseCheckoutSessionReturn {
771
+ /** The hydrated checkout session. */
772
+ session: CheckoutSession | null;
773
+ /** Whether the session is loading. */
774
+ loading: boolean;
775
+ /** Error message if loading failed. */
776
+ error: string | null;
777
+ /** Structured error code for UX branching. */
778
+ errorCode: CheckoutSessionErrorCode | null;
779
+ }
780
+ /**
781
+ * Fetch and hydrate a checkout session by token.
782
+ *
783
+ * Handles the three error cases from the API:
784
+ * - 404 → "Invalid booking link"
785
+ * - "already consumed" → "This booking was already completed"
786
+ * - "expired" → "Session expired"
787
+ *
788
+ * ```tsx
789
+ * const { session, loading, error, errorCode } = useCheckoutSession("cs_ab12cd34...");
790
+ * ```
791
+ */
792
+ declare function useCheckoutSession(token: string | undefined): UseCheckoutSessionReturn;
762
793
 
763
794
  /** Default theme values. */
764
795
  declare const DEFAULT_THEME: Required<ResiraTheme>;
@@ -798,4 +829,4 @@ declare function TagIcon({ size, className }: IconProps): react_jsx_runtime.JSX.
798
829
  declare function CubeIcon({ size, className }: IconProps): react_jsx_runtime.JSX.Element;
799
830
  declare function ViewfinderIcon({ size, className }: IconProps): react_jsx_runtime.JSX.Element;
800
831
 
801
- export { AlertCircleIcon, BookingCalendar, BookingModal, type BookingSelection, type BookingStep, CalendarIcon, CheckCircleIcon, CheckIcon, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, type DeeplinkGuest, type DeeplinkSelection, DishShowcase, type DishShowcaseProps, type DomainConfig, GuestForm, type GuestFormErrors, type GuestFormValues, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, ResiraBookingWidget, type ResiraClassNames, type ResiraContextValue, type ResiraDomain, type ResiraLocale, ResiraProvider, type ResiraProviderConfig, type ResiraProviderProps, type ResiraTheme, ResourcePicker, type ServiceLayout, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, resolveTheme, themeToCSS, useAvailability, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, validateGuestForm };
832
+ export { AlertCircleIcon, BookingCalendar, BookingModal, type BookingSelection, type BookingStep, CalendarIcon, CheckCircleIcon, CheckIcon, type CheckoutSessionErrorCode, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, type DeeplinkGuest, type DeeplinkSelection, DishShowcase, type DishShowcaseProps, type DomainConfig, GuestForm, type GuestFormErrors, type GuestFormValues, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, ResiraBookingWidget, type ResiraClassNames, type ResiraContextValue, type ResiraDomain, type ResiraLocale, ResiraProvider, type ResiraProviderConfig, type ResiraProviderProps, type ResiraTheme, ResourcePicker, type ServiceLayout, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, validateGuestForm };
package/dist/index.js CHANGED
@@ -171,6 +171,7 @@ function ResiraProvider({
171
171
  resourceId,
172
172
  domain,
173
173
  config,
174
+ checkoutSessionToken,
174
175
  onClose,
175
176
  children
176
177
  }) {
@@ -260,9 +261,10 @@ function ResiraProvider({
260
261
  deeplinkGuest,
261
262
  onStepChange,
262
263
  onBookingComplete,
263
- onError
264
+ onError,
265
+ checkoutSessionToken
264
266
  }),
265
- [client, resourceId, activeResourceId, setActiveResourceId, catalogMode, allowMultiSelect, domain, theme, locale, domainConfig, stripePublishableKey, termsText, waiverText, showWaiver, showTerms, showRemainingSpots, depositPercent, refundPolicy, onClose, classNames, serviceLayout, visibleServiceCount, groupServicesByCategory, renderServiceCard, showStepIndicator, deeplink, deeplinkGuest, onStepChange, onBookingComplete, onError]
267
+ [client, resourceId, activeResourceId, setActiveResourceId, catalogMode, allowMultiSelect, domain, theme, locale, domainConfig, stripePublishableKey, termsText, waiverText, showWaiver, showTerms, showRemainingSpots, depositPercent, refundPolicy, onClose, classNames, serviceLayout, visibleServiceCount, groupServicesByCategory, renderServiceCard, showStepIndicator, deeplink, deeplinkGuest, onStepChange, onBookingComplete, onError, checkoutSessionToken]
266
268
  );
267
269
  return /* @__PURE__ */ jsx(ResiraContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: "resira-root", style: cssVars, children }) });
268
270
  }
@@ -594,6 +596,60 @@ function useDishes(enabled = true) {
594
596
  }, [client, enabled]);
595
597
  return { dishes, loading, error };
596
598
  }
599
+ function useCheckoutSession(token) {
600
+ const { client } = useResira();
601
+ const [session, setSession] = useState(null);
602
+ const [loading, setLoading] = useState(!!token);
603
+ const [error, setError] = useState(null);
604
+ const [errorCode, setErrorCode] = useState(null);
605
+ useEffect(() => {
606
+ if (!token) {
607
+ setSession(null);
608
+ setLoading(false);
609
+ setError(null);
610
+ setErrorCode(null);
611
+ return;
612
+ }
613
+ let cancelled = false;
614
+ setLoading(true);
615
+ setError(null);
616
+ setErrorCode(null);
617
+ async function fetchSession() {
618
+ try {
619
+ const data = await client.getCheckoutSession(token);
620
+ if (!cancelled) {
621
+ setSession(data.session);
622
+ }
623
+ } catch (err) {
624
+ if (cancelled) return;
625
+ const apiErr = err;
626
+ const message = apiErr.body?.error ?? (err instanceof Error ? err.message : "Failed to load checkout session");
627
+ if (apiErr.status === 404) {
628
+ setError("Invalid booking link");
629
+ setErrorCode("not_found");
630
+ } else if (message.toLowerCase().includes("consumed")) {
631
+ setError("This booking was already completed");
632
+ setErrorCode("consumed");
633
+ } else if (message.toLowerCase().includes("expired")) {
634
+ setError("Session expired \u2014 please start a new booking");
635
+ setErrorCode("expired");
636
+ } else {
637
+ setError(message);
638
+ setErrorCode("unknown");
639
+ }
640
+ } finally {
641
+ if (!cancelled) {
642
+ setLoading(false);
643
+ }
644
+ }
645
+ }
646
+ fetchSession();
647
+ return () => {
648
+ cancelled = true;
649
+ };
650
+ }, [client, token]);
651
+ return { session, loading, error, errorCode };
652
+ }
597
653
  var defaultSize = 20;
598
654
  function CalendarIcon({ size = defaultSize, className }) {
599
655
  return /* @__PURE__ */ jsxs(
@@ -2675,7 +2731,8 @@ var STEP_LABELS = {
2675
2731
  details: "Details",
2676
2732
  terms: "Terms",
2677
2733
  payment: "Payment",
2678
- confirmation: "Done"
2734
+ confirmation: "Done",
2735
+ checkout: "Checkout"
2679
2736
  };
2680
2737
  function ResiraBookingWidget() {
2681
2738
  const {
@@ -2697,16 +2754,26 @@ function ResiraBookingWidget() {
2697
2754
  deeplinkGuest,
2698
2755
  onStepChange,
2699
2756
  onBookingComplete,
2700
- onError
2757
+ onError,
2758
+ checkoutSessionToken
2701
2759
  } = useResira();
2702
2760
  const isDateBased = domain === "rental";
2703
2761
  const isTimeBased = domain === "restaurant" || domain === "watersport" || domain === "service";
2704
2762
  const isServiceBased = domain === "watersport" || domain === "service";
2705
2763
  const hasPayment = !!stripePublishableKey;
2706
- const STEPS = useMemo(
2707
- () => buildSteps(domain, hasPayment, catalogMode),
2708
- [domain, hasPayment, catalogMode]
2709
- );
2764
+ const isCheckoutMode = !!checkoutSessionToken;
2765
+ const {
2766
+ session: checkoutSession,
2767
+ loading: checkoutSessionLoading,
2768
+ error: checkoutSessionError,
2769
+ errorCode: checkoutSessionErrorCode
2770
+ } = useCheckoutSession(checkoutSessionToken);
2771
+ const STEPS = useMemo(() => {
2772
+ if (isCheckoutMode) {
2773
+ return ["checkout", "payment", "confirmation"];
2774
+ }
2775
+ return buildSteps(domain, hasPayment, catalogMode);
2776
+ }, [domain, hasPayment, catalogMode, isCheckoutMode]);
2710
2777
  const initialStep = STEPS[0];
2711
2778
  const [step, setStep] = useState(initialStep);
2712
2779
  const [selectedResourceIds, setSelectedResourceIds] = useState([]);
@@ -2787,6 +2854,16 @@ function ResiraBookingWidget() {
2787
2854
  const [paymentFormReady, setPaymentFormReady] = useState(false);
2788
2855
  const [paymentFormSubmitting, setPaymentFormSubmitting] = useState(false);
2789
2856
  const paymentPayload = useMemo(() => {
2857
+ if (isCheckoutMode && checkoutSession) {
2858
+ return {
2859
+ checkoutSessionToken,
2860
+ guestName: guest.guestName.trim() || checkoutSession.guestName || "",
2861
+ guestEmail: guest.guestEmail.trim() || checkoutSession.guestEmail || void 0,
2862
+ guestPhone: guest.guestPhone.trim() || void 0,
2863
+ notes: guest.notes.trim() || void 0,
2864
+ termsAccepted: termsAccepted || void 0
2865
+ };
2866
+ }
2790
2867
  const resourceId = activeResourceId ?? selectedProduct?.equipmentIds?.[0] ?? "";
2791
2868
  return {
2792
2869
  productId: selectedProduct?.id ?? "",
@@ -2803,7 +2880,7 @@ function ResiraBookingWidget() {
2803
2880
  promoCode: discountCode.trim() || void 0,
2804
2881
  termsAccepted: termsAccepted || void 0
2805
2882
  };
2806
- }, [activeResourceId, selectedProduct, selection, guest, discountCode, termsAccepted]);
2883
+ }, [activeResourceId, selectedProduct, selection, guest, discountCode, termsAccepted, isCheckoutMode, checkoutSession, checkoutSessionToken]);
2807
2884
  const blockedDates = useMemo(() => {
2808
2885
  const dates = calendarData?.dates?.blockedDates ?? availability?.dates?.blockedDates ?? [];
2809
2886
  return new Set(dates);
@@ -2861,6 +2938,8 @@ function ResiraBookingWidget() {
2861
2938
  return locale.payment;
2862
2939
  case "confirmation":
2863
2940
  return locale.bookingConfirmed;
2941
+ case "checkout":
2942
+ return "Checkout";
2864
2943
  }
2865
2944
  }, [step, isDateBased, isServiceBased, locale, domain]);
2866
2945
  const stepSubtitle = useMemo(() => {
@@ -2879,8 +2958,10 @@ function ResiraBookingWidget() {
2879
2958
  return "Complete your booking securely";
2880
2959
  case "confirmation":
2881
2960
  return "";
2961
+ case "checkout":
2962
+ return checkoutSession?.productName ?? "Complete your booking";
2882
2963
  }
2883
- }, [step, domainConfig.label, locale, allowMultiSelect, isServiceBased, selectedProduct, domain]);
2964
+ }, [step, domainConfig.label, locale, allowMultiSelect, isServiceBased, selectedProduct, domain, checkoutSession]);
2884
2965
  const canContinue = useMemo(() => {
2885
2966
  if (step === "resource") {
2886
2967
  if (isServiceBased) return !!selectedProduct;
@@ -3058,6 +3139,35 @@ function ResiraBookingWidget() {
3058
3139
  const handlePaymentError = useCallback((msg) => {
3059
3140
  onError?.("payment_error", msg);
3060
3141
  }, [onError]);
3142
+ const handleCheckoutSubmit = useCallback(async () => {
3143
+ const contactMode = "email-required";
3144
+ const errors = validateGuestForm(guest, {
3145
+ required: locale.required,
3146
+ invalidEmail: locale.invalidEmail,
3147
+ contactRequired: locale.contactRequired
3148
+ }, contactMode);
3149
+ if (Object.keys(errors).length > 0) {
3150
+ setFormErrors(errors);
3151
+ return;
3152
+ }
3153
+ setFormErrors({});
3154
+ if (checkoutSession?.checkoutConfig.termsRequired && !termsAccepted) {
3155
+ setTermsError(locale.termsRequired);
3156
+ return;
3157
+ }
3158
+ setTermsError(null);
3159
+ const result = await createPayment(paymentPayload);
3160
+ if (!result) return;
3161
+ if (result.amountNow === 0) {
3162
+ if (result.reservationId) {
3163
+ const confirmed = await confirmPayment(result.paymentIntentId, result.reservationId);
3164
+ if (!confirmed) return;
3165
+ }
3166
+ setStep("confirmation");
3167
+ } else {
3168
+ setStep("payment");
3169
+ }
3170
+ }, [guest, locale, checkoutSession, termsAccepted, createPayment, paymentPayload, confirmPayment]);
3061
3171
  const handleSubmitNoPayment = useCallback(async () => {
3062
3172
  if (showTerms && !termsAccepted) {
3063
3173
  setTermsError(locale.termsRequired);
@@ -3088,6 +3198,17 @@ function ResiraBookingWidget() {
3088
3198
  const footerBusy = submitting || creatingPayment || confirmingPayment || paymentFormSubmitting;
3089
3199
  const paymentActionDisabled = !paymentIntent || !paymentFormReady || paymentFormSubmitting || confirmingPayment;
3090
3200
  const [deeplinkApplied, setDeeplinkApplied] = useState(false);
3201
+ const [checkoutGuestFilled, setCheckoutGuestFilled] = useState(false);
3202
+ useEffect(() => {
3203
+ if (checkoutGuestFilled || !checkoutSession) return;
3204
+ setGuest((prev) => ({
3205
+ guestName: checkoutSession.guestName ?? prev.guestName,
3206
+ guestEmail: checkoutSession.guestEmail ?? prev.guestEmail,
3207
+ guestPhone: prev.guestPhone,
3208
+ notes: prev.notes
3209
+ }));
3210
+ setCheckoutGuestFilled(true);
3211
+ }, [checkoutSession, checkoutGuestFilled]);
3091
3212
  useEffect(() => {
3092
3213
  if (deeplinkApplied || !deeplink) return;
3093
3214
  if (deeplink.productId && products.length > 0 && !selectedProduct) {
@@ -3323,6 +3444,93 @@ function ResiraBookingWidget() {
3323
3444
  /* @__PURE__ */ jsx("p", { className: "resira-error-message", children: paymentError })
3324
3445
  ] })
3325
3446
  ] }),
3447
+ step === "checkout" && /* @__PURE__ */ jsxs(Fragment, { children: [
3448
+ checkoutSessionLoading && /* @__PURE__ */ jsxs("div", { className: "resira-loading", role: "status", "aria-live": "polite", "aria-busy": "true", children: [
3449
+ /* @__PURE__ */ jsx("div", { className: "resira-spinner", "aria-hidden": "true" }),
3450
+ /* @__PURE__ */ jsx("span", { className: "resira-loading-text", children: locale.loading })
3451
+ ] }),
3452
+ checkoutSessionError && /* @__PURE__ */ jsxs("div", { className: "resira-error", role: "alert", "aria-live": "assertive", children: [
3453
+ /* @__PURE__ */ jsx(AlertCircleIcon, { size: 24 }),
3454
+ /* @__PURE__ */ jsx("p", { className: "resira-error-message", children: checkoutSessionError }),
3455
+ (checkoutSessionErrorCode === "not_found" || checkoutSessionErrorCode === "expired") && onClose && /* @__PURE__ */ jsx(
3456
+ "button",
3457
+ {
3458
+ type: "button",
3459
+ className: "resira-btn resira-btn--secondary resira-error-retry",
3460
+ onClick: onClose,
3461
+ children: "Start a new booking"
3462
+ }
3463
+ )
3464
+ ] }),
3465
+ checkoutSession && !checkoutSessionError && /* @__PURE__ */ jsxs(Fragment, { children: [
3466
+ /* @__PURE__ */ jsxs("div", { className: "resira-summary", style: { marginBottom: 16 }, children: [
3467
+ /* @__PURE__ */ jsxs("div", { className: "resira-summary-row", children: [
3468
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-label", children: "Service" }),
3469
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-value", children: checkoutSession.productName })
3470
+ ] }),
3471
+ /* @__PURE__ */ jsxs("div", { className: "resira-summary-row", children: [
3472
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-label", children: "Date" }),
3473
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-value", children: (/* @__PURE__ */ new Date(checkoutSession.date + "T00:00:00")).toLocaleDateString("default", { weekday: "short", month: "short", day: "numeric" }) })
3474
+ ] }),
3475
+ /* @__PURE__ */ jsxs("div", { className: "resira-summary-row", children: [
3476
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-label", children: "Time" }),
3477
+ /* @__PURE__ */ jsxs("span", { className: "resira-summary-value", children: [
3478
+ new Date(checkoutSession.startTime).toLocaleTimeString("default", { hour: "2-digit", minute: "2-digit", hour12: false }),
3479
+ " \u2013 ",
3480
+ new Date(checkoutSession.endTime).toLocaleTimeString("default", { hour: "2-digit", minute: "2-digit", hour12: false })
3481
+ ] })
3482
+ ] }),
3483
+ /* @__PURE__ */ jsxs("div", { className: "resira-summary-row", children: [
3484
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-label", children: "Duration" }),
3485
+ /* @__PURE__ */ jsxs("span", { className: "resira-summary-value", children: [
3486
+ checkoutSession.durationMinutes,
3487
+ " min"
3488
+ ] })
3489
+ ] }),
3490
+ /* @__PURE__ */ jsxs("div", { className: "resira-summary-row", children: [
3491
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-label", children: locale.guests }),
3492
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-value", children: checkoutSession.partySize })
3493
+ ] }),
3494
+ checkoutSession.promoCode && /* @__PURE__ */ jsxs("div", { className: "resira-summary-row", children: [
3495
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-label", children: locale.discountCode }),
3496
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-value", children: checkoutSession.promoCode })
3497
+ ] }),
3498
+ /* @__PURE__ */ jsx("div", { className: "resira-summary-divider" }),
3499
+ /* @__PURE__ */ jsxs("div", { className: "resira-summary-row", children: [
3500
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-label resira-summary-total", children: locale.total }),
3501
+ /* @__PURE__ */ jsx("span", { className: "resira-summary-value resira-summary-total", children: formatPrice5(checkoutSession.priceCents, checkoutSession.currency) })
3502
+ ] })
3503
+ ] }),
3504
+ /* @__PURE__ */ jsx(
3505
+ GuestForm,
3506
+ {
3507
+ values: guest,
3508
+ onChange: setGuest,
3509
+ errors: formErrors
3510
+ }
3511
+ ),
3512
+ checkoutSession.checkoutConfig.termsRequired && /* @__PURE__ */ jsx("div", { style: { marginTop: 14 }, children: /* @__PURE__ */ jsx(
3513
+ WaiverConsent,
3514
+ {
3515
+ termsAccepted,
3516
+ onTermsChange: setTermsAccepted,
3517
+ waiverAccepted: false,
3518
+ onWaiverChange: () => {
3519
+ },
3520
+ discountCode: "",
3521
+ onDiscountCodeChange: () => {
3522
+ },
3523
+ onPromoValidated: () => {
3524
+ },
3525
+ error: termsError ?? void 0
3526
+ }
3527
+ ) }),
3528
+ paymentError && /* @__PURE__ */ jsxs("div", { className: "resira-error", style: { padding: "12px 0" }, children: [
3529
+ /* @__PURE__ */ jsx(AlertCircleIcon, { size: 18 }),
3530
+ /* @__PURE__ */ jsx("p", { className: "resira-error-message", children: paymentError })
3531
+ ] })
3532
+ ] })
3533
+ ] }),
3326
3534
  step === "payment" && /* @__PURE__ */ jsxs(Fragment, { children: [
3327
3535
  paymentError && /* @__PURE__ */ jsxs("div", { className: "resira-error", children: [
3328
3536
  /* @__PURE__ */ jsx(AlertCircleIcon, { size: 24 }),
@@ -3408,31 +3616,45 @@ function ResiraBookingWidget() {
3408
3616
  /* @__PURE__ */ jsx("span", { className: "resira-summary-value resira-summary-total", children: formatPrice5(computedPrice.total, currency) })
3409
3617
  ] })
3410
3618
  ] })
3411
- ] }) }),
3412
- onClose && /* @__PURE__ */ jsx(
3413
- "button",
3414
- {
3415
- type: "button",
3416
- className: "resira-btn resira-btn--primary",
3417
- style: { marginTop: 20, width: "100%" },
3418
- onClick: onClose,
3419
- children: locale.done
3420
- }
3421
- )
3619
+ ] }) })
3422
3620
  ] })
3423
3621
  ] }),
3424
- step !== "confirmation" && /* @__PURE__ */ jsxs("div", { className: `resira-widget-footer${currentIndex > 0 ? "" : " resira-widget-footer--single"}`, children: [
3425
- currentIndex > 0 && /* @__PURE__ */ jsx(
3622
+ /* @__PURE__ */ jsxs("div", { className: "resira-widget-footer", children: [
3623
+ /* @__PURE__ */ jsx(
3426
3624
  "button",
3427
3625
  {
3428
3626
  type: "button",
3429
3627
  className: "resira-btn resira-btn--secondary",
3430
3628
  onClick: handleBack,
3431
- disabled: footerBusy,
3629
+ disabled: footerBusy || currentIndex === 0,
3630
+ style: currentIndex === 0 || step === "confirmation" ? { visibility: "hidden" } : void 0,
3631
+ "aria-hidden": currentIndex === 0 || step === "confirmation",
3432
3632
  children: locale.back
3433
3633
  }
3434
3634
  ),
3435
- (step === "resource" || step === "availability") && /* @__PURE__ */ jsx(
3635
+ step === "confirmation" ? onClose ? /* @__PURE__ */ jsx(
3636
+ "button",
3637
+ {
3638
+ type: "button",
3639
+ className: "resira-btn resira-btn--primary",
3640
+ onClick: onClose,
3641
+ children: locale.done
3642
+ }
3643
+ ) : /* @__PURE__ */ jsx("span", { style: { visibility: "hidden" }, className: "resira-btn resira-btn--primary", children: locale.continue }) : step === "checkout" ? /* @__PURE__ */ jsx(
3644
+ "button",
3645
+ {
3646
+ type: "button",
3647
+ className: "resira-btn resira-btn--primary",
3648
+ onClick: () => {
3649
+ void handleCheckoutSubmit();
3650
+ },
3651
+ disabled: footerBusy || checkoutSessionLoading || !!checkoutSessionError,
3652
+ children: creatingPayment ? /* @__PURE__ */ jsxs(Fragment, { children: [
3653
+ /* @__PURE__ */ jsx("div", { className: "resira-spinner", style: { width: 16, height: 16 } }),
3654
+ locale.processingPayment
3655
+ ] }) : checkoutSession ? `${locale.payNow} \u2014 ${formatPrice5(checkoutSession.priceCents, checkoutSession.currency)}` : locale.continue
3656
+ }
3657
+ ) : step === "resource" || step === "availability" ? /* @__PURE__ */ jsx(
3436
3658
  "button",
3437
3659
  {
3438
3660
  type: "button",
@@ -3443,8 +3665,7 @@ function ResiraBookingWidget() {
3443
3665
  disabled: !canContinue || footerBusy,
3444
3666
  children: locale.continue
3445
3667
  }
3446
- ),
3447
- step === "details" && /* @__PURE__ */ jsx(
3668
+ ) : step === "details" ? /* @__PURE__ */ jsx(
3448
3669
  "button",
3449
3670
  {
3450
3671
  type: "button",
@@ -3456,8 +3677,7 @@ function ResiraBookingWidget() {
3456
3677
  locale.loading
3457
3678
  ] }) : domain === "restaurant" && !hasPayment ? locale.reserveTable : locale.continue
3458
3679
  }
3459
- ),
3460
- step === "terms" && /* @__PURE__ */ jsx(
3680
+ ) : step === "terms" ? /* @__PURE__ */ jsx(
3461
3681
  "button",
3462
3682
  {
3463
3683
  type: "button",
@@ -3474,8 +3694,7 @@ function ResiraBookingWidget() {
3474
3694
  locale.loading
3475
3695
  ] }) : hasPayment ? locale.continue : locale.bookNow
3476
3696
  }
3477
- ),
3478
- step === "payment" && !paymentIntent && /* @__PURE__ */ jsx(
3697
+ ) : step === "payment" && !paymentIntent ? /* @__PURE__ */ jsx(
3479
3698
  "button",
3480
3699
  {
3481
3700
  type: "button",
@@ -3489,8 +3708,7 @@ function ResiraBookingWidget() {
3489
3708
  locale.processingPayment
3490
3709
  ] }) : computedPrice ? `${locale.payNow} \u2014 ${formatPrice5(computedPrice.amountNow, currency)}` : locale.payNow
3491
3710
  }
3492
- ),
3493
- step === "payment" && paymentIntent?.amountNow != null && paymentIntent.amountNow > 0 && /* @__PURE__ */ jsx(
3711
+ ) : step === "payment" && paymentIntent?.amountNow != null && paymentIntent.amountNow > 0 ? /* @__PURE__ */ jsx(
3494
3712
  "button",
3495
3713
  {
3496
3714
  type: "button",
@@ -3502,6 +3720,9 @@ function ResiraBookingWidget() {
3502
3720
  locale.processingPayment
3503
3721
  ] }) : `${locale.payNow} \u2014 ${formatPrice5(paymentIntent.amountNow, paymentIntent.currency)}`
3504
3722
  }
3723
+ ) : (
3724
+ /* Fallback placeholder to hold space (e.g. zero-amount payment auto-advancing) */
3725
+ /* @__PURE__ */ jsx("span", { style: { visibility: "hidden" }, className: "resira-btn resira-btn--primary", children: locale.continue })
3505
3726
  )
3506
3727
  ] })
3507
3728
  ] });
@@ -3511,6 +3732,7 @@ function BookingModal({
3511
3732
  domain,
3512
3733
  resourceId,
3513
3734
  config,
3735
+ checkoutSessionToken,
3514
3736
  trigger,
3515
3737
  buttonLabel = "Book Now",
3516
3738
  buttonClassName,
@@ -3634,6 +3856,7 @@ function BookingModal({
3634
3856
  domain,
3635
3857
  resourceId,
3636
3858
  config,
3859
+ checkoutSessionToken,
3637
3860
  onClose: handleClose,
3638
3861
  children: /* @__PURE__ */ jsx(ResiraBookingWidget, {})
3639
3862
  }
@@ -3984,6 +4207,6 @@ function DishShowcase({
3984
4207
  ] });
3985
4208
  }
3986
4209
 
3987
- export { AlertCircleIcon, BookingCalendar, BookingModal, CalendarIcon, CheckCircleIcon, CheckIcon, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, DishShowcase, GuestForm, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, ResiraBookingWidget, ResiraProvider, ResourcePicker, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, resolveTheme, themeToCSS, useAvailability, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, validateGuestForm };
4210
+ export { AlertCircleIcon, BookingCalendar, BookingModal, CalendarIcon, CheckCircleIcon, CheckIcon, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, DishShowcase, GuestForm, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, ResiraBookingWidget, ResiraProvider, ResourcePicker, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, validateGuestForm };
3988
4211
  //# sourceMappingURL=index.js.map
3989
4212
  //# sourceMappingURL=index.js.map