@riverbankcms/sdk 0.141.0 → 0.143.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.
Files changed (123) hide show
  1. package/dist/{PreviewEditorSidebar-Q-fy9GYw.mjs → PreviewEditorSidebar-DROGfMKi.mjs} +24 -15
  2. package/dist/{PreviewEditorUI-LYGzgk85.mjs → PreviewEditorUI-BaRjhFu-.mjs} +3 -3
  3. package/dist/{PreviewQueryProvider-CN0ewic5.mjs → PreviewQueryProvider-BCYICImX.mjs} +1 -1
  4. package/dist/{SdkPreviewModeRuntime-CoPYI5Jq.mjs → SdkPreviewModeRuntime-OIABYLES.mjs} +16 -11
  5. package/dist/{SiteChromeCustomizeContext-D8LePX8x.mjs → SiteChromeCustomizeContext-ohjTvZbc.mjs} +607 -545
  6. package/dist/_dts/api/src/billing.d.ts +6 -1
  7. package/dist/_dts/api/src/bookingConfig.d.ts +1 -1
  8. package/dist/_dts/api/src/commercePortalEndpoints.d.ts +2 -2
  9. package/dist/_dts/api/src/endpoints.d.ts +7 -27
  10. package/dist/_dts/api/src/index.d.ts +1 -1
  11. package/dist/_dts/api/src/publicBookingService.d.ts +1 -0
  12. package/dist/_dts/api/src/publicEventEndpointDefinitions.d.ts +7 -0
  13. package/dist/_dts/api/src/publicExperience/eventEndpoints.d.ts +48 -41
  14. package/dist/_dts/api/src/sdkConfig/layout.d.ts +13 -0
  15. package/dist/_dts/api/src/sdkContracts.d.ts +3 -8
  16. package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +1 -31
  17. package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +6 -1
  18. package/dist/_dts/billing/src/site-arrangements.d.ts +9 -5
  19. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +56 -0
  20. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +8 -0
  21. package/dist/_dts/blocks/src/system/runtime/api/client.d.ts +2 -1
  22. package/dist/_dts/blocks/src/system/runtime/api/endpoints.d.ts +8 -0
  23. package/dist/_dts/blocks/src/system/runtime/api/types/events.d.ts +95 -35
  24. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/PaymentStatusStep.d.ts +10 -0
  25. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/TicketSelectionStep.d.ts +0 -2
  26. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +12 -1
  27. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.d.ts +2 -2
  28. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationCompletion.d.ts +1 -4
  29. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationDerivations.d.ts +12 -0
  30. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.d.ts +1 -1
  31. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/sessionStorage.d.ts +1 -0
  32. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.d.ts +4 -5
  33. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +9 -11
  34. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +56 -2
  35. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +10 -4
  36. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.d.ts +23 -0
  37. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/utils.d.ts +1 -1
  38. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +56 -0
  39. package/dist/_dts/commerce/src/purchase/event-registration-errors.d.ts +4 -4
  40. package/dist/_dts/commerce/src/purchase/eventCheckoutRecoveryWindow.d.ts +8 -0
  41. package/dist/_dts/commerce/src/purchase/index.d.ts +2 -1
  42. package/dist/_dts/commerce/src/purchase/stateTransitions.d.ts +1 -13
  43. package/dist/_dts/commerce/src/purchase/stripeCommissionMetadata.d.ts +1 -2
  44. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +58 -45
  45. package/dist/_dts/editor-blocks/src/block-editor/ColourFieldOptionsProvider.d.ts +7 -0
  46. package/dist/_dts/editor-blocks/src/block-editor/colourFieldVisibility.d.ts +7 -0
  47. package/dist/_dts/editor-blocks/src/block-editor/preparedManifest.d.ts +2 -0
  48. package/dist/_dts/editor-blocks/src/index.d.ts +1 -0
  49. package/dist/_dts/preview-next/src/client/preview/PreviewEditorSidebar.d.ts +2 -2
  50. package/dist/_dts/preview-next/src/client/preview/PreviewFooterEditorPanel.d.ts +3 -6
  51. package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +2 -2
  52. package/dist/_dts/preview-next/src/client/preview/PreviewShellLayout.d.ts +2 -2
  53. package/dist/_dts/preview-next/src/client/preview/footerInspection.d.ts +16 -0
  54. package/dist/_dts/preview-next/src/client/preview/sdkConfig.d.ts +6 -0
  55. package/dist/_dts/sdk/src/config/types.d.ts +2 -18
  56. package/dist/_dts/sdk/src/config/validation.d.ts +6 -0
  57. package/dist/_dts/sdk/src/public-api/contracts.d.ts +3 -0
  58. package/dist/_dts/sdk/src/version.d.ts +1 -1
  59. package/dist/_dts/theme-core/src/footer/mode.d.ts +10 -1
  60. package/dist/_dts/theme-core/src/schema.d.ts +1 -7
  61. package/dist/_dts/theme-core/src/site-styles/effectivePageDesign.d.ts +32 -0
  62. package/dist/_dts/theme-core/src/site-styles/index.d.ts +1 -0
  63. package/dist/client/{BookingConsentAgreement-xJeYNdnb.mjs → BookingConsentAgreement-BoCtvzzu.mjs} +2 -2
  64. package/dist/client/{DiscountCodeDisclosure-DdT6uzl9.mjs → DiscountCodeDisclosure-B5S30nZk.mjs} +1 -1
  65. package/dist/client/{EventCombined.client-Cp28uYuM.mjs → EventCombined.client-CeZ2-zSt.mjs} +5 -5
  66. package/dist/client/{EventListing.client-CD6AJsMr.mjs → EventListing.client-DJN3j8B3.mjs} +2 -2
  67. package/dist/client/{LeafletStaticMap-DBsAi9-e.mjs → LeafletStaticMap-RjtGzDQT.mjs} +1 -1
  68. package/dist/client/{accordion-6dwbOKYR.mjs → accordion-DOmnDoyg.mjs} +1 -1
  69. package/dist/client/{bookingForm-CZE9_qPQ.mjs → bookingForm-C0vk7pgS.mjs} +5 -5
  70. package/dist/client/bookings.mjs +468 -534
  71. package/dist/client/{carousel-7VHGQARz.mjs → carousel-BZJ7pno3.mjs} +1 -1
  72. package/dist/client/{cart-Qg_mvQhh.mjs → cart-CaeVAHJl.mjs} +3 -3
  73. package/dist/client/{checkout-D84rpgdr.mjs → checkout-7wmoNDtG.mjs} +3 -3
  74. package/dist/client/client.mjs +559 -593
  75. package/dist/client/{context-Ctp3T0Wr.mjs → context-BOimxE3U.mjs} +12 -1
  76. package/dist/client/{dates-DJKFcI_N.mjs → dates-BnzVksi4.mjs} +2 -2
  77. package/dist/client/{eventCalendar-CK31z3vs.mjs → eventCalendar-l-fpaslC.mjs} +7 -7
  78. package/dist/client/{eventCombined-DiXd-ewn.mjs → eventCombined-DGGvssK6.mjs} +3 -3
  79. package/dist/client/{eventDetails-CTRgt2DK.mjs → eventDetails-DRiCm_hL.mjs} +4 -4
  80. package/dist/client/{eventListing-Dy9vi_j_.mjs → eventListing-dph8loYX.mjs} +3 -3
  81. package/dist/client/{eventRegistration-CnalYMr2.mjs → eventRegistration-ClBohmbY.mjs} +472 -531
  82. package/dist/client/{form-eUiMveTT.mjs → form-BLVVAnd3.mjs} +2 -2
  83. package/dist/client/{headerSection-Dy9kQpB1.mjs → headerSection-DHy4C8Ix.mjs} +2 -2
  84. package/dist/client/hooks.mjs +39 -13
  85. package/dist/client/{locationMap-DalCmWU0.mjs → locationMap-6HuS28ck.mjs} +2 -2
  86. package/dist/client/{newsletterForm-SDDPvhS0.mjs → newsletterForm-xOdcOhZP.mjs} +2 -2
  87. package/dist/client/{pricing-OcMz4SnX.mjs → pricing-ChinRYFt.mjs} +1 -1
  88. package/dist/client/{productDetail-BQrgHElM.mjs → productDetail-0chA5gbK.mjs} +3 -3
  89. package/dist/client/{productList-OnPn_UJj.mjs → productList-OoINA0h1.mjs} +3 -3
  90. package/dist/client/{propsDecoder-zBdsnS-N.mjs → propsDecoder-CIZ-hbIG.mjs} +33 -9
  91. package/dist/client/rendering/client.mjs +536 -550
  92. package/dist/client/rendering/islands.mjs +17 -17
  93. package/dist/client/rendering.mjs +602 -596
  94. package/dist/client/{scrollToBlock-BNM4Ob2Y.mjs → scrollToBlock-BPZl0gPZ.mjs} +1 -1
  95. package/dist/client/{shop-Bv1fPTwQ.mjs → shop-CLJFqNIq.mjs} +6 -6
  96. package/dist/client/{shop-commerce-D4KNGaW8.mjs → shop-commerce-PpANg5wB.mjs} +5 -5
  97. package/dist/client/{shop-commerce-store-2N5EV0Rc.mjs → shop-commerce-store-TA_Kz2ku.mjs} +1 -1
  98. package/dist/client/{shopShared-DvJGdHTV.mjs → shopShared-DzcFcA5g.mjs} +3 -3
  99. package/dist/client/{useEventFilters-BBBvGDHQ.mjs → useEventFilters-CFU21QZo.mjs} +5 -5
  100. package/dist/preview-next/before-render.mjs +11 -37
  101. package/dist/preview-next/client/runtime.mjs +4 -4
  102. package/dist/preview-next/keepAlive.mjs +11 -37
  103. package/dist/preview-next/middleware.mjs +11 -37
  104. package/dist/{previewAuthStore-Gjb6r6vz.mjs → previewAuthStore-JA6-I_sL.mjs} +11 -37
  105. package/dist/{sdk-runtime-Defnbfzw.mjs → sdk-runtime-CDKl0Fr7.mjs} +2 -2
  106. package/dist/{separator-CZXO3Sb0.mjs → separator-BTp96X32.mjs} +1 -1
  107. package/dist/server/components.mjs +80 -51
  108. package/dist/server/config-validation.mjs +43 -14
  109. package/dist/server/config.mjs +43 -14
  110. package/dist/server/data.mjs +39 -13
  111. package/dist/server/index.mjs +12 -38
  112. package/dist/server/next.mjs +40 -53
  113. package/dist/server/page-converter.mjs +20 -8
  114. package/dist/server/prebuild.mjs +1 -1
  115. package/dist/server/rendering/server.mjs +80 -51
  116. package/dist/server/rendering.mjs +80 -51
  117. package/dist/server/routing.mjs +52 -17
  118. package/dist/server/server.mjs +40 -51
  119. package/dist/server/theme-bridge.mjs +10 -8
  120. package/dist/server/theme.mjs +10 -8
  121. package/package.json +1 -1
  122. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/checkoutRecovery.d.ts +0 -14
  123. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useCheckoutRecovery.d.ts +0 -8
@@ -1,11 +1,11 @@
1
1
  "use client";
2
- import { a as buildBookingPaymentSelectionViewModel, i as usePolling, n as StepIndicator$1, o as resolveBookingPaymentDueNowCents, r as SpinnerNode, s as formatPrice, t as BookingConsentAgreement } from "./BookingConsentAgreement-xJeYNdnb.mjs";
3
- import { C as resolveRegistrationTarget, E as getWizardStepsForContext, Gt as formatBookingDate, Pt as assertNever, S as resolveEventRequiresPayment, T as getStepNumber, Yt as formatBookingTime, _ as toRegistrationEvents, b as findPreSelectedEvent, dn as publicEventPricingType, dt as themeButtonClassName, fn as formatCurrencyAmount, g as getRegistrationStartAt, h as buildEventRegistrationDisplayFromScopedEvents, k as resolveEventRegistrationScopedEvents, ln as publicEventPricingOptions, ot as groupPublicEventsByRun, pn as parseCurrencyCode, qt as formatBookingDateTime, rn as toScheduledBookingDateTime, sn as publicEventPricingCurrency, t as defineIslandPropsDecoder, tn as toBookingTimeZone, un as publicEventPricingStrategy, v as toWholeSeriesRegistrationEvents, w as WIZARD_STEPS, x as resolveDiscountContext, y as findEventByOccurrenceId } from "./propsDecoder-zBdsnS-N.mjs";
4
- import { a as canJoinEventWaitlistOnAnyMode, c as formatTicketTypeRemaining, f as getRemainingPlaces, h as selectMostOpenCapacitySource, i as canJoinEventWaitlist, m as selectEventCapacitySource, o as formatEventRemainingPlaces, p as isEventFull, r as formatEventPriceDisplay, s as formatRemainingPlaces, u as getEventAvailability } from "./pricing-OcMz4SnX.mjs";
5
- import { n as useBlockApi, r as BlockApiError, t as BlockApiProvider } from "./context-Ctp3T0Wr.mjs";
2
+ import { a as buildBookingPaymentSelectionViewModel, i as usePolling, n as StepIndicator$1, o as resolveBookingPaymentDueNowCents, r as SpinnerNode, s as formatPrice, t as BookingConsentAgreement } from "./BookingConsentAgreement-BoCtvzzu.mjs";
3
+ import { A as resolveEventRegistrationScopedEvents, C as resolveRegistrationTarget, D as getWizardStepsForContext, E as getStepNumber, Ft as assertNever, Jt as formatBookingDateTime, Kt as formatBookingDate, S as resolveEventRequiresPayment, T as WIZARD_STEPS, Xt as formatBookingTime, _ as toRegistrationEvents, b as findPreSelectedEvent, cn as publicEventPricingCurrency, dn as publicEventPricingStrategy, fn as publicEventPricingType, ft as themeButtonClassName, g as getRegistrationStartAt, h as buildEventRegistrationDisplayFromScopedEvents, in as toScheduledBookingDateTime, mn as parseCurrencyCode, nn as toBookingTimeZone, pn as formatCurrencyAmount, st as groupPublicEventsByRun, t as defineIslandPropsDecoder, un as publicEventPricingOptions, v as toWholeSeriesRegistrationEvents, w as toRegistrationRequestSubject, x as resolveDiscountContext, y as findEventByOccurrenceId } from "./propsDecoder-CIZ-hbIG.mjs";
4
+ import { a as canJoinEventWaitlistOnAnyMode, c as formatTicketTypeRemaining, f as getRemainingPlaces, h as selectMostOpenCapacitySource, i as canJoinEventWaitlist, m as selectEventCapacitySource, o as formatEventRemainingPlaces, p as isEventFull, r as formatEventPriceDisplay, s as formatRemainingPlaces, u as getEventAvailability } from "./pricing-ChinRYFt.mjs";
5
+ import { n as useBlockApi, r as BlockApiError, t as BlockApiProvider } from "./context-BOimxE3U.mjs";
6
6
  import { n as isSpamProtectionEnabled, r as AltchaWidget, t as getChallengeUrl } from "./src-B833rYRr.mjs";
7
- import { a as CheckIcon, i as StateIcon, n as useDiscountCodeUi, r as TerminalPanel, t as scrollToBlock } from "./scrollToBlock-BNM4Ob2Y.mjs";
8
- import { t as DiscountCodeDisclosure } from "./DiscountCodeDisclosure-DdT6uzl9.mjs";
7
+ import { a as CheckIcon, i as StateIcon, n as useDiscountCodeUi, r as TerminalPanel, t as scrollToBlock } from "./scrollToBlock-BPZl0gPZ.mjs";
8
+ import { t as DiscountCodeDisclosure } from "./DiscountCodeDisclosure-B5S30nZk.mjs";
9
9
  import { t as FeedbackMessage } from "./FeedbackMessage-CtLvvS_d.mjs";
10
10
  import { n as getFieldValidationControlProps, t as FieldValidationMessage } from "./FieldValidationMessage-AYanWFb-.mjs";
11
11
  import { t as buildCheckoutReturnUrls } from "./checkoutUrls-FL6lRkrt.mjs";
@@ -276,7 +276,7 @@ function resolveEventRegistrationDirectPaymentSelection(input) {
276
276
  //#endregion
277
277
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationStateMachine.ts
278
278
  function hasBlockingExistingRegistration(input) {
279
- return !input.hasWaitlistClaim && input.isAuthenticated && input.registration.kind !== "none";
279
+ return !input.hasWaitlistClaim && input.isAuthenticated && input.registration.kind === "waitlisted";
280
280
  }
281
281
  function createInitialRegistrationFlowState(hasPreSelectedContext) {
282
282
  return {
@@ -328,7 +328,7 @@ function computeAutoSelection(input) {
328
328
  return { method: null };
329
329
  }
330
330
  function computeDirectPaymentSelection(eligibility) {
331
- if (!eligibility?.canPayDirectly) return { method: null };
331
+ if (!eligibility?.requiresPayment) return { method: null };
332
332
  const approvedKinds = getApprovedDirectPaymentCollectionOptionKinds(eligibility.paymentOptions);
333
333
  const collectionOptionKind = approvedKinds.includes("upfront") ? "upfront" : approvedKinds[0];
334
334
  return collectionOptionKind ? {
@@ -342,7 +342,7 @@ function isSelectionValid(input) {
342
342
  if (!eligibility) return selection.method === "stripe" || selection.method === "pass_bundle" || selection.method === "membership_bundle";
343
343
  switch (selection.method) {
344
344
  case "free": return eligibility.isFree;
345
- case "stripe": return Boolean(eligibility.canPayDirectly && eligibility.price);
345
+ case "stripe": return Boolean(eligibility.requiresPayment && eligibility.price);
346
346
  case "pass": return isAuthenticated && ticketsCount === 1 && !!selection.passId && eligibility.eligiblePasses.some((pass) => pass.id === selection.passId);
347
347
  case "membership": return isAuthenticated && ticketsCount === 1 && !!selection.membershipId && eligibility.eligibleMemberships.some((membership) => membership.id === selection.membershipId);
348
348
  case "pass_bundle": return ticketsCount === 1 && !!selection.bundlePassId && eligibility.availablePasses.some((pass) => pass.id === selection.bundlePassId);
@@ -361,7 +361,7 @@ function normalizeRegistrationFlowState(state) {
361
361
  ticketsCount: state.ticketsCount,
362
362
  passOptOut: nextPassOptOut
363
363
  });
364
- const canKeepManualSelection = state.step === "select-payment" && state.selectionSource === "manual" && isSelectionValid({
364
+ const canKeepManualSelection = state.step !== "select-tickets" && state.selectionSource === "manual" && isSelectionValid({
365
365
  selection: state.paymentSelection,
366
366
  isAuthenticated: state.isAuthenticated,
367
367
  eligibility: state.eligibility,
@@ -444,7 +444,7 @@ function resolveEventRegistrationSubmitPlan(input) {
444
444
  const selection = input.paymentSelection.method ? input.paymentSelection : void 0;
445
445
  if (input.canJoinWaitlist) return {
446
446
  ok: true,
447
- args: withOptionalSelection({ method: "free" }, selection)
447
+ args: { method: "free" }
448
448
  };
449
449
  switch (input.paymentSelection.method) {
450
450
  case "pass": return input.paymentSelection.passId ? {
@@ -503,88 +503,53 @@ function missingPaymentSelection() {
503
503
  };
504
504
  }
505
505
  //#endregion
506
- //#region ../blocks/src/system/runtime/nodes/event-registration/checkoutRecovery.ts
507
- /** Recovery may select held places; checkout still owns admission and payment validation. */
508
- function checkoutRecoveryOptionAvailability(availability) {
509
- if (availability.kind === "available" || availability.reason === "sold_out" || availability.reason === "overallCapacityUnavailable") return {
510
- kind: "available",
511
- remainingPlaces: null
512
- };
513
- return availability;
506
+ //#region ../blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.ts
507
+ /** Ticket selection requests a quote; only the server admits a booking. */
508
+ function canRequestTicketOption(availability) {
509
+ return availability.kind === "available" || availability.reason === "sold_out" || availability.reason === "overallCapacityUnavailable";
514
510
  }
515
- /**
516
- * A form-only projection for the explicit existing-checkout path. It permits
517
- * collecting the original selection without claiming a new place is available.
518
- * The public checkout service identifies the customer and remains the admission
519
- * authority; no reservation/customer data is exposed through this projection.
520
- */
521
- function getCheckoutRecoveryFormEligibility(eligibility) {
522
- if (!eligibility || eligibility.registration.kind !== "none" && eligibility.registration.kind !== "pending_payment" || eligibility.availability.kind === "unavailable" && eligibility.availability.reason !== "full" && eligibility.availability.reason !== "selectionRequired") return eligibility;
523
- const amountCents = eligibility.price ?? 0;
524
- if (amountCents <= 0 || !eligibility.currency || eligibility.availability.kind === "unavailable" && eligibility.availability.reason === "selectionRequired") return eligibility.registration.kind === "pending_payment" ? {
525
- ...eligibility,
526
- registration: { kind: "none" },
527
- ticketTypes: eligibility.ticketTypes.map((option) => ({
528
- ...option,
529
- availability: checkoutRecoveryOptionAvailability(option.availability)
530
- }))
531
- } : eligibility;
532
- const currency = eligibility.currency;
533
- const paymentOptions = eligibility.paymentTerms.allowedOptions.map((option) => {
534
- switch (option.kind) {
535
- case "upfront": return {
536
- kind: "payInFull",
537
- amountCents,
538
- currency
539
- };
540
- case "deferred_manual": return {
541
- kind: "deferredManual",
542
- amountCents,
543
- currency
544
- };
545
- case "instalment_plan": return {
546
- kind: "instalmentPlan",
547
- amountCents,
548
- currency
549
- };
550
- case "flexible_balance": return {
551
- kind: "flexibleBalance",
552
- amountCents,
553
- currency
554
- };
555
- }
556
- });
557
- return {
558
- ...eligibility,
559
- registration: { kind: "none" },
560
- availability: {
561
- kind: "available",
562
- remainingPlaces: null,
563
- waitlistEnabled: eligibility.waitlistEnabled
564
- },
565
- eligible: paymentOptions.length > 0,
566
- isFull: false,
567
- canJoinWaitlist: false,
568
- isFree: false,
569
- requiresPayment: true,
570
- canPayDirectly: paymentOptions.length > 0,
571
- paymentOptions,
572
- eligiblePasses: [],
573
- eligibleMemberships: [],
574
- ticketTypes: eligibility.ticketTypes.map((option) => ({
575
- ...option,
576
- availability: checkoutRecoveryOptionAvailability(option.availability)
577
- }))
578
- };
511
+ function normalizeTicketSelectionLines(lines) {
512
+ const quantitiesByTicketType = /* @__PURE__ */ new Map();
513
+ for (const line of lines) {
514
+ const quantity = Math.floor(line.quantity);
515
+ if (quantity <= 0) continue;
516
+ const key = line.pricingOptionId ?? null;
517
+ quantitiesByTicketType.set(key, (quantitiesByTicketType.get(key) ?? 0) + quantity);
518
+ }
519
+ return [...quantitiesByTicketType.entries()].map(([pricingOptionId, quantity]) => ({
520
+ pricingOptionId,
521
+ quantity
522
+ }));
579
523
  }
580
- function isSoldOutEventSelectable(event, waitlistAvailable) {
581
- return waitlistAvailable || canRecoverEventCheckout(event);
524
+ function getTicketSelectionTotal(lines) {
525
+ return normalizeTicketSelectionLines(lines).reduce((total, line) => total + line.quantity, 0);
582
526
  }
583
- function canJoinWaitlistOutsideRecovery(event, attendanceMode, checkoutRecovery) {
584
- return !checkoutRecovery && event !== null && canJoinEventWaitlist(event, { attendanceMode });
527
+ function getTicketLineQuantity(lines, pricingOptionId) {
528
+ return normalizeTicketSelectionLines(lines).find((line) => line.pricingOptionId === pricingOptionId)?.quantity ?? 0;
529
+ }
530
+ function getSingleSelectedPricingOptionId(lines) {
531
+ const normalized = normalizeTicketSelectionLines(lines);
532
+ return normalized.length === 1 ? normalized[0]?.pricingOptionId ?? null : null;
585
533
  }
586
- function canRecoverEventCheckout(event) {
587
- return event !== null && (event.registrationSubject.kind === "wholeSeries" || event.pricing.kind !== "free" && event.pricing.kind !== "external");
534
+ function hasValidPricingOptionSelection(input) {
535
+ if (!input.selectionRequired) return true;
536
+ if (input.lines.length === 0) return false;
537
+ const availableOptionIds = new Set(input.visibleOptions.filter((option) => canRequestTicketOption(option.availability)).map((option) => option.pricingOptionId));
538
+ return input.lines.every((line) => line.pricingOptionId !== null && availableOptionIds.has(line.pricingOptionId));
539
+ }
540
+ function setTicketLineQuantity(input) {
541
+ const nextQuantity = Math.max(0, Math.floor(input.quantity));
542
+ const normalized = normalizeTicketSelectionLines(input.lines).filter((line) => line.pricingOptionId !== input.pricingOptionId);
543
+ if (nextQuantity <= 0) return normalized;
544
+ return [...normalized, {
545
+ pricingOptionId: input.pricingOptionId,
546
+ quantity: nextQuantity
547
+ }];
548
+ }
549
+ function getTicketTypeLineMax(input) {
550
+ const otherSelectedQuantity = input.totalSelectedQuantity - input.currentQuantity;
551
+ const maxByBlock = input.maxTickets - otherSelectedQuantity;
552
+ return Math.max(0, maxByBlock);
588
553
  }
589
554
  //#endregion
590
555
  //#region ../blocks/src/system/runtime/nodes/event-registration/utils.ts
@@ -651,8 +616,6 @@ function formatPassCreditUsageLabel(creditsRemaining) {
651
616
  return `Uses 1 of ${creditsRemaining} credits`;
652
617
  }
653
618
  function transformRegistrationOptionsResponse(response) {
654
- const canJoinWaitlist = response.availability.kind === "waitlist";
655
- const isFull = response.availability.remainingPlaces === 0 || response.availability.kind === "unavailable" && response.availability.reason === "full";
656
619
  const eligiblePasses = response.entitlements.passes.map((pass) => ({
657
620
  id: pass.customerPassId,
658
621
  passName: pass.passName,
@@ -665,7 +628,6 @@ function transformRegistrationOptionsResponse(response) {
665
628
  const eventAcceptsPasses = response.entitlements.requiresLoginForPasses || eligiblePasses.length > 0 || response.entitlements.availableForPurchase.passes.length > 0;
666
629
  const eventAcceptsMemberships = response.entitlements.requiresLoginForMemberships || eligibleMemberships.length > 0 || response.entitlements.availableForPurchase.memberships.length > 0;
667
630
  const monetaryPaymentAvailable = hasMonetaryPaymentOption(response);
668
- const registrationAvailable = response.availability.kind === "available" || response.availability.kind === "waitlist";
669
631
  return {
670
632
  availability: response.availability,
671
633
  registration: response.registration,
@@ -680,7 +642,6 @@ function transformRegistrationOptionsResponse(response) {
680
642
  },
681
643
  transactions: []
682
644
  } : null,
683
- eligible: response.registration.kind === "none" && registrationAvailable,
684
645
  requiresPayment: monetaryPaymentAvailable,
685
646
  pricingStrategy: response.pricing.strategy,
686
647
  paymentTerms: response.pricing.paymentTerms,
@@ -695,16 +656,12 @@ function transformRegistrationOptionsResponse(response) {
695
656
  currency: response.pricing.currency
696
657
  })),
697
658
  ticketTypes: response.pricing.options,
698
- isFull,
699
- waitlistEnabled: response.availability.waitlistEnabled,
700
- canJoinWaitlist,
701
659
  price: response.pricing.subtotalCents,
702
660
  currency: response.pricing.currency,
703
661
  requiresAuthForPasses: response.entitlements.requiresLoginForPasses,
704
662
  requiresAuthForMemberships: response.entitlements.requiresLoginForMemberships,
705
663
  eligiblePasses,
706
664
  eligibleMemberships,
707
- canPayDirectly: monetaryPaymentAvailable,
708
665
  eventAcceptsPasses,
709
666
  eventAcceptsMemberships,
710
667
  availablePasses: [...response.entitlements.availableForPurchase.passes],
@@ -734,8 +691,8 @@ function getVisibleTicketTypes(input) {
734
691
  })) : [];
735
692
  return (input.eligibility?.ticketTypes ?? eventTicketTypes).filter((ticketType) => ticketType.isActive && (!input.selectedAttendanceMode || (ticketType.attendanceScope ?? "both") === "both" || (ticketType.attendanceScope ?? "both") === input.selectedAttendanceMode));
736
693
  }
737
- function getImplicitSingleAvailableTicketType(ticketTypes) {
738
- return ticketTypes.length === 1 && ticketTypes[0]?.availability.kind === "available" ? ticketTypes[0] : null;
694
+ function getImplicitSingleTicketType(ticketTypes) {
695
+ return ticketTypes.length === 1 && ticketTypes[0] && canRequestTicketOption(ticketTypes[0].availability) ? ticketTypes[0] : null;
739
696
  }
740
697
  function findSelectedTicketType(input) {
741
698
  if (!input.selectedPricingOptionId) return null;
@@ -772,25 +729,19 @@ const EventSelectionStep = ({ events, selectedEvent, onSelect, showVenue, showCa
772
729
  const remaining = getRemainingPlaces(selectMostOpenCapacitySource(event));
773
730
  const isSoldOut = isEventFull(event);
774
731
  const canJoinWaitlist = canJoinEventWaitlistOnAnyMode(event);
775
- const isUnavailable = isSoldOut && !isSoldOutEventSelectable(event, canJoinWaitlist);
776
732
  const isLowCapacity = remaining.kind === "low";
777
733
  const capacityLabel = formatEventRemainingPlaces(event, { full: "Sold out" });
778
734
  const timeZone = event.timeZone ?? null;
779
- const cardClasses = [
780
- "er-event-card",
781
- isSelected && "er-event-card--selected",
782
- isUnavailable && "er-event-card--disabled"
783
- ].filter(Boolean).join(" ");
735
+ const cardClasses = ["er-event-card", isSelected && "er-event-card--selected"].filter(Boolean).join(" ");
784
736
  const badgeClasses = [
785
737
  "er-capacity-badge",
786
- isUnavailable && "er-capacity-badge--soldout",
738
+ isSoldOut && "er-capacity-badge--soldout",
787
739
  isLowCapacity && "er-capacity-badge--low",
788
- (canJoinWaitlist || !isUnavailable && !isLowCapacity) && "er-capacity-badge--available"
740
+ (canJoinWaitlist || !isSoldOut && !isLowCapacity) && "er-capacity-badge--available"
789
741
  ].filter(Boolean).join(" ");
790
742
  return /* @__PURE__ */ jsx("button", {
791
743
  type: "button",
792
- onClick: () => !isUnavailable && onSelect(event),
793
- disabled: isUnavailable,
744
+ onClick: () => onSelect(event),
794
745
  className: cardClasses,
795
746
  children: /* @__PURE__ */ jsxs("div", {
796
747
  className: "er-event-option",
@@ -817,41 +768,15 @@ const EventSelectionStep = ({ events, selectedEvent, onSelect, showVenue, showCa
817
768
  });
818
769
  };
819
770
  //#endregion
820
- //#region ../blocks/src/system/runtime/nodes/event-registration/useCheckoutRecovery.ts
821
- /** Recovery belongs to the selected booking subject; changing events resets its scope. */
822
- function useCheckoutRecovery(event, setStep) {
823
- const [recoveryEventId, setRecoveryEventId] = useState(null);
824
- return {
825
- checkoutRecovery: event !== null && recoveryEventId === event.selectionId,
826
- startCheckoutRecovery: useCallback(() => {
827
- if (!event) return;
828
- setRecoveryEventId(event.selectionId);
829
- setStep("select-tickets");
830
- }, [event, setStep])
831
- };
832
- }
833
- /** Authenticated pending bookings enter the same form-only recovery path as guests. */
834
- function useCheckoutRecoveryEligibility(eligibility, step, checkoutRecovery, startCheckoutRecovery) {
835
- const registrationKind = eligibility?.registration.kind;
836
- useEffect(() => {
837
- if ((step === "select-event" || step === "select-tickets" || step === "enter-details" || step === "select-payment") && registrationKind === "pending_payment" && !checkoutRecovery) startCheckoutRecovery();
838
- }, [
839
- registrationKind,
840
- step,
841
- checkoutRecovery,
842
- startCheckoutRecovery
843
- ]);
844
- return useMemo(() => checkoutRecovery ? getCheckoutRecoveryFormEligibility(eligibility) : eligibility, [checkoutRecovery, eligibility]);
845
- }
846
- //#endregion
847
771
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.ts
848
772
  function resolveRegistrationRecoveryAction(step, action) {
849
773
  if (step === "payment-failed") return "payment-options";
850
- if (step === "verifying-payment" || step === "verifying-timeout" || step === "redirecting") return "payment-status";
774
+ if (step === "payment-status") return "none";
775
+ if (step === "verifying-payment" || step === "verifying-timeout" || step === "redirecting") return "none";
851
776
  return action;
852
777
  }
853
778
  function canRecoverToWaitlist(eligibility) {
854
- return Boolean(eligibility?.isFull && eligibility.canJoinWaitlist);
779
+ return eligibility?.availability.kind === "waitlist" && eligibility.registration.kind !== "pending_payment";
855
780
  }
856
781
  function shouldRefreshAfterRegistrationError(step, action, enabled) {
857
782
  return enabled || step === "error" && action === "places";
@@ -868,19 +793,19 @@ function resetRegistrationAfterExpiry(input) {
868
793
  input.setErrorMessage("");
869
794
  input.setPendingRegistrationReference(null);
870
795
  }
871
- /** Recovery never starts another payment. Unknown payment outcomes go to the portal. */
796
+ /** Retry the retained submission in place; explicit payment uncertainty stays status-only. */
872
797
  function recoverRegistrationError(action, handlers) {
873
798
  switch (action) {
874
799
  case "none": return;
875
800
  case "portal":
876
- case "payment-status":
877
801
  window.location.assign("/portal");
878
802
  return;
879
803
  case "event":
880
804
  window.location.assign(window.location.pathname);
881
805
  return;
882
806
  case "retry":
883
- window.location.reload();
807
+ handlers.clearError();
808
+ handlers.setStep("select-payment");
884
809
  return;
885
810
  case "details":
886
811
  handlers.clearError();
@@ -918,53 +843,6 @@ function validateFormData(formData, requiresConsent = false) {
918
843
  return errors;
919
844
  }
920
845
  //#endregion
921
- //#region ../blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.ts
922
- function normalizeTicketSelectionLines(lines) {
923
- const quantitiesByTicketType = /* @__PURE__ */ new Map();
924
- for (const line of lines) {
925
- const quantity = Math.floor(line.quantity);
926
- if (quantity <= 0) continue;
927
- const key = line.pricingOptionId ?? null;
928
- quantitiesByTicketType.set(key, (quantitiesByTicketType.get(key) ?? 0) + quantity);
929
- }
930
- return [...quantitiesByTicketType.entries()].map(([pricingOptionId, quantity]) => ({
931
- pricingOptionId,
932
- quantity
933
- }));
934
- }
935
- function getTicketSelectionTotal(lines) {
936
- return normalizeTicketSelectionLines(lines).reduce((total, line) => total + line.quantity, 0);
937
- }
938
- function getTicketLineQuantity(lines, pricingOptionId) {
939
- return normalizeTicketSelectionLines(lines).find((line) => line.pricingOptionId === pricingOptionId)?.quantity ?? 0;
940
- }
941
- function getSingleSelectedPricingOptionId(lines) {
942
- const normalized = normalizeTicketSelectionLines(lines);
943
- return normalized.length === 1 ? normalized[0]?.pricingOptionId ?? null : null;
944
- }
945
- function hasValidPricingOptionSelection(input) {
946
- if (!input.selectionRequired) return true;
947
- if (input.lines.length === 0) return false;
948
- const availableOptionIds = new Set(input.visibleOptions.filter((option) => option.availability.kind === "available").map((option) => option.pricingOptionId));
949
- return input.lines.every((line) => line.pricingOptionId !== null && availableOptionIds.has(line.pricingOptionId));
950
- }
951
- function setTicketLineQuantity(input) {
952
- const nextQuantity = Math.max(0, Math.floor(input.quantity));
953
- const normalized = normalizeTicketSelectionLines(input.lines).filter((line) => line.pricingOptionId !== input.pricingOptionId);
954
- if (nextQuantity <= 0) return normalized;
955
- return [...normalized, {
956
- pricingOptionId: input.pricingOptionId,
957
- quantity: nextQuantity
958
- }];
959
- }
960
- function getTicketTypeLineMax(input) {
961
- const otherSelectedQuantity = input.totalSelectedQuantity - input.currentQuantity;
962
- const maxByBlock = input.maxTickets - otherSelectedQuantity;
963
- const maxByEvent = input.availableSpots == null ? input.maxTickets : input.availableSpots - otherSelectedQuantity;
964
- const maxByTicketType = input.ticketTypeRemainingCapacity ?? input.maxTickets;
965
- return Math.max(0, Math.min(maxByBlock, maxByEvent, maxByTicketType));
966
- }
967
- //#endregion
968
846
  //#region ../blocks/src/system/runtime/nodes/event-registration/hooks.state.ts
969
847
  const initialEligibilityLookupState = {
970
848
  tag: "idle",
@@ -1082,17 +960,6 @@ function usePortalAuthWithDeps(deps) {
1082
960
  logout
1083
961
  };
1084
962
  }
1085
- function toRegistrationOptionsSubject(subject, attendanceMode) {
1086
- return subject.kind === "occurrence" ? {
1087
- kind: "occurrence",
1088
- occurrenceId: subject.occurrenceId,
1089
- attendanceMode: attendanceMode ?? void 0
1090
- } : {
1091
- kind: "wholeSeries",
1092
- eventSeriesId: subject.eventSeriesId,
1093
- attendanceMode: attendanceMode ?? void 0
1094
- };
1095
- }
1096
963
  function useEligibility(params) {
1097
964
  return useEligibilityWithDeps(params, eventRegistrationHooksDeps);
1098
965
  }
@@ -1133,7 +1000,7 @@ function useEligibilityWithDeps(params, deps) {
1133
1000
  requestIdRef.current = requestId;
1134
1001
  dispatch({ type: "loading-started" });
1135
1002
  try {
1136
- const subject = toRegistrationOptionsSubject(params.subject, params.attendanceMode);
1003
+ const subject = toRegistrationRequestSubject(params.subject, params.attendanceMode);
1137
1004
  const result = await api.getRegistrationOptions({
1138
1005
  subject,
1139
1006
  selections: ticketSelections.map((selection) => ({
@@ -1423,7 +1290,6 @@ const STRIPE_COMMISSION_FREE_REASONS = [
1423
1290
  "no_commission_plan",
1424
1291
  "billing_evidence_invalid",
1425
1292
  "fee_rounds_to_zero",
1426
- "non_commissionable_scope",
1427
1293
  "payment_not_stripe"
1428
1294
  ];
1429
1295
  const historicalContext = {
@@ -1501,9 +1367,9 @@ const eventRegistrationErrors = {
1501
1367
  message: "Booking is currently closed for this event. Please contact the organiser if you’d like to find out more.",
1502
1368
  action: "none"
1503
1369
  },
1504
- paymentUncertain: {
1505
- message: "We’re checking your booking and payment. Please don’t try to book again yet. If you’ve already paid, you don’t need to pay again.",
1506
- action: "payment-status"
1370
+ paymentInvestigationRequired: {
1371
+ message: "We still can’t confirm your payment. Please don’t make another payment. Contact the organiser so they can check your booking.",
1372
+ action: "none"
1507
1373
  },
1508
1374
  paymentProviderUnavailable: {
1509
1375
  message: "We couldn’t reach the payment provider. Please try again in a minute.",
@@ -2001,7 +1867,30 @@ function checkoutReturnCommand(params) {
2001
1867
  };
2002
1868
  return null;
2003
1869
  }
2004
- async function handleCheckoutReturn(options, deps, checkoutAttemptIdRef) {
1870
+ function handleCheckoutReturnOutcome(options, deps, context, outcome) {
1871
+ const { pending, params } = context;
1872
+ if (outcome.kind === "resolved") {
1873
+ deps.clearReturnParams();
1874
+ options.setPendingRegistrationReference(outcome.reference);
1875
+ options.setStep("verifying-payment");
1876
+ return "settled";
1877
+ }
1878
+ if (outcome.kind === "abandoned") {
1879
+ deps.clearReturnParams();
1880
+ options.setErrorMessage("Your payment was cancelled. You can choose a payment option and try again.");
1881
+ options.setStep("select-payment");
1882
+ return "settled";
1883
+ }
1884
+ if (outcome.kind === "notFound" && params.registration === "cancelled" && (options.registrationSubject?.kind === "occurrence" || pending?.reference.scope === "occurrence")) {
1885
+ deps.clearReturnParams();
1886
+ options.setErrorMessage("Your payment was cancelled. You can choose a payment option and try again.");
1887
+ options.setStep("select-payment");
1888
+ return "settled";
1889
+ }
1890
+ showReturnReconciliation(options, "We’re checking your booking and payment. Please don’t try to book again yet. If you’ve already paid, you don’t need to pay again.");
1891
+ return outcome.kind === "notFound" ? "settled" : "retryable";
1892
+ }
1893
+ async function handleCheckoutReturn(options, deps, returnContextRef) {
2005
1894
  deps.cleanupStaleRegistrations();
2006
1895
  const params = deps.readReturnParams();
2007
1896
  if (params.registration !== "cancelled" && params.registration !== "success") return "settled";
@@ -2015,34 +1904,15 @@ async function handleCheckoutReturn(options, deps, checkoutAttemptIdRef) {
2015
1904
  }
2016
1905
  const { checkoutAttemptId } = command;
2017
1906
  const pending = deps.getPendingRegistration(siteId, checkoutAttemptId);
2018
- checkoutAttemptIdRef.current = checkoutAttemptId;
1907
+ returnContextRef.current = { checkoutAttemptId };
2019
1908
  if (pending) restorePendingPresentation(options, pending);
2020
1909
  options.setStep("verifying-payment");
2021
1910
  try {
2022
1911
  const outcome = await options.api.resolveEventCheckoutReturn(command);
2023
- switch (outcome.kind) {
2024
- case "resolved":
2025
- deps.clearReturnParams();
2026
- options.setPendingRegistrationReference(outcome.reference);
2027
- options.setStep("verifying-payment");
2028
- return "settled";
2029
- case "abandoned":
2030
- deps.clearReturnParams();
2031
- options.setErrorMessage("Your payment was cancelled. You can choose a payment option and try again.");
2032
- options.setStep("select-payment");
2033
- return "settled";
2034
- case "notFound":
2035
- if (params.registration === "cancelled" && (options.registrationSubject?.kind === "occurrence" || pending?.reference.scope === "occurrence")) {
2036
- deps.clearReturnParams();
2037
- options.setErrorMessage("Your payment was cancelled. You can choose a payment option and try again.");
2038
- options.setStep("select-payment");
2039
- return "settled";
2040
- }
2041
- showReturnReconciliation(options, "We’re checking your booking and payment. Please don’t try to book again yet. If you’ve already paid, you don’t need to pay again.");
2042
- return "settled";
2043
- case "reconciliationRequired": break;
2044
- }
2045
- showReturnReconciliation(options, "We’re checking your booking and payment. Please don’t try to book again yet. If you’ve already paid, you don’t need to pay again.");
1912
+ return handleCheckoutReturnOutcome(options, deps, {
1913
+ pending,
1914
+ params
1915
+ }, outcome);
2046
1916
  } catch {
2047
1917
  showReturnReconciliation(options, "We couldn’t check your payment just now. Please check again in a moment. Don’t make another booking while we’re checking.");
2048
1918
  }
@@ -2051,9 +1921,16 @@ async function handleCheckoutReturn(options, deps, checkoutAttemptIdRef) {
2051
1921
  function useEventRegistrationStripeReturn(options) {
2052
1922
  return useEventRegistrationStripeReturnWithDeps(options, eventRegistrationStripeReturnDeps);
2053
1923
  }
1924
+ function completeVerifiedRegistration(options, deps, returnContext) {
1925
+ const siteId = options.api.config.siteId;
1926
+ const checkoutAttemptId = returnContext?.checkoutAttemptId;
1927
+ if (siteId && checkoutAttemptId) deps.clearPendingRegistration(siteId, checkoutAttemptId);
1928
+ options.setRegistrationResult({ status: "confirmed" });
1929
+ options.setStep("success");
1930
+ }
2054
1931
  function useEventRegistrationStripeReturnWithDeps(options, deps) {
2055
1932
  const { api, pendingRegistrationReference, setErrorMessage, setRegistrationResult, setStep, step } = options;
2056
- const checkoutAttemptIdRef = useRef(null);
1933
+ const returnContextRef = useRef(null);
2057
1934
  const lookupInFlightRef = useRef(false);
2058
1935
  const [lookupState, setLookupState] = useState("settled");
2059
1936
  const resolveReturn = useCallback(async () => {
@@ -2061,7 +1938,7 @@ function useEventRegistrationStripeReturnWithDeps(options, deps) {
2061
1938
  lookupInFlightRef.current = true;
2062
1939
  setLookupState("loading");
2063
1940
  try {
2064
- setLookupState(await handleCheckoutReturn(options, deps, checkoutAttemptIdRef));
1941
+ setLookupState(await handleCheckoutReturn(options, deps, returnContextRef));
2065
1942
  } finally {
2066
1943
  lookupInFlightRef.current = false;
2067
1944
  }
@@ -2084,13 +1961,8 @@ function useEventRegistrationStripeReturnWithDeps(options, deps) {
2084
1961
  resolveReturn();
2085
1962
  }, []);
2086
1963
  useEffect(() => {
2087
- if (verification.status === "completed") {
2088
- const siteId = api.config.siteId;
2089
- const checkoutAttemptId = checkoutAttemptIdRef.current;
2090
- if (siteId && checkoutAttemptId) deps.clearPendingRegistration(siteId, checkoutAttemptId);
2091
- setRegistrationResult({ status: "confirmed" });
2092
- setStep("success");
2093
- } else if (verification.status === "failed") {
1964
+ if (verification.status === "completed") completeVerifiedRegistration(options, deps, returnContextRef.current);
1965
+ else if (verification.status === "failed") {
2094
1966
  setErrorMessage("Please try again or choose another payment option. If this keeps happening, contact the organiser for help.");
2095
1967
  setStep("payment-failed");
2096
1968
  } else if (verification.status === "timeout") setStep("verifying-timeout");
@@ -2119,7 +1991,10 @@ function registrationErrorPresentation(error) {
2119
1991
  const reason = error instanceof BlockApiError ? parseEventRegistrationErrorReason(error.tag) : null;
2120
1992
  if (reason) return eventRegistrationErrors[reason];
2121
1993
  if (error instanceof BlockApiError && error.status < 500) return eventRegistrationErrors.invalidCommitInput;
2122
- return eventRegistrationErrors.paymentUncertain;
1994
+ return {
1995
+ message: "Something went wrong. Please try again.",
1996
+ action: "retry"
1997
+ };
2123
1998
  }
2124
1999
  //#endregion
2125
2000
  //#region ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.ts
@@ -2148,26 +2023,6 @@ function useEventRegistrationSubmit(options) {
2148
2023
  * and the balance left, which the block's copy cannot. Every other method keeps
2149
2024
  * whatever success message the site configured.
2150
2025
  */
2151
- function toRegistrationResult(result) {
2152
- if (result.alreadyRegistered) {
2153
- const waitlisted = result.status === "waitlist";
2154
- return {
2155
- status: result.status,
2156
- variant: "info",
2157
- title: waitlisted ? "Already waitlisted" : "Already registered",
2158
- message: result.message ?? (waitlisted ? "You are already on the waitlist for this event." : "You are already registered for this event.")
2159
- };
2160
- }
2161
- if (result.status === "waitlist") return {
2162
- status: "waitlist",
2163
- title: "Added to waitlist",
2164
- message: result.message ?? (typeof result.waitlistPosition === "number" ? `You've been added to the waitlist (position ${result.waitlistPosition}).` : "You've been added to the waitlist.")
2165
- };
2166
- return {
2167
- status: result.status,
2168
- message: result.paymentMethod === "pass" ? result.message : void 0
2169
- };
2170
- }
2171
2026
  function useEventRegistrationSubmitWithDeps(options, deps) {
2172
2027
  /**
2173
2028
  * The command identity for the submission currently in progress.
@@ -2181,7 +2036,7 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
2181
2036
  * Identical retries retain the command after a lost response. Changed form
2182
2037
  * details get a new command so the server applies the new booking intent.
2183
2038
  */
2184
- const commandRef = useRef(null);
2039
+ const { commandRef } = options;
2185
2040
  const inFlightRef = useRef(null);
2186
2041
  const submit = async (method, passId, membershipId, selection, submitOptions) => {
2187
2042
  const target = options.registrationTarget;
@@ -2194,19 +2049,15 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
2194
2049
  const registrationCommandId = commandRef.current?.id ?? deps.createClientCommandId();
2195
2050
  try {
2196
2051
  let prepared = prepareSubmitContext(options, registrationCommandId, target, method, passId, membershipId, selection);
2197
- if (prepared.subject.kind === "wholeSeries" && method === "stripe") {
2198
- commandRef.current = null;
2199
- return await submitWholeSeriesCheckout(options, deps, {
2052
+ let request = buildRegistrationRequest(options, prepared, method, passId, membershipId);
2053
+ const intentKey = registrationRequestIntentKey(request);
2054
+ if (commandRef.current !== null && commandRef.current.intentKey !== intentKey) {
2055
+ prepared = {
2200
2056
  ...prepared,
2201
- subject: prepared.subject
2202
- }, submitOptions);
2057
+ registrationCommandId: deps.createClientCommandId()
2058
+ };
2059
+ request = buildRegistrationRequest(options, prepared, method, passId, membershipId);
2203
2060
  }
2204
- const request = buildOccurrenceRegistrationRequest(options, prepared, method, passId, membershipId);
2205
- const intentKey = registrationRequestIntentKey(request);
2206
- if (commandRef.current !== null && commandRef.current.intentKey !== intentKey) prepared = {
2207
- ...prepared,
2208
- registrationCommandId: deps.createClientCommandId()
2209
- };
2210
2061
  commandRef.current = {
2211
2062
  id: prepared.registrationCommandId,
2212
2063
  intentKey
@@ -2215,8 +2066,7 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
2215
2066
  ...request,
2216
2067
  registrationCommandId: prepared.registrationCommandId
2217
2068
  });
2218
- commandRef.current = null;
2219
- return await completeRegistrationSubmission(options, deps, prepared, result, method, submitOptions);
2069
+ return await completeRegistrationSubmission(options, deps, prepared, result, submitOptions);
2220
2070
  } catch (error) {
2221
2071
  if (error instanceof BlockApiError && error.tag === "registrationPreparationChanged") commandRef.current = null;
2222
2072
  const { message, action } = registrationErrorPresentation(error);
@@ -2240,9 +2090,40 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
2240
2090
  return pending;
2241
2091
  };
2242
2092
  }
2243
- async function completeRegistrationSubmission(options, deps, prepared, result, method, submitOptions) {
2244
- if (needsOccurrenceCheckout(result, method)) return await submitOccurrenceCheckout(options, deps, prepared, result, method, submitOptions);
2245
- options.setRegistrationResult(toRegistrationResult(result));
2093
+ async function completeRegistrationSubmission(options, deps, prepared, result, submitOptions) {
2094
+ switch (result.kind) {
2095
+ case "paymentStatusRequired":
2096
+ options.setPaymentStatusRecovery({
2097
+ kind: "ready",
2098
+ subject: result.subject
2099
+ });
2100
+ options.setStep("payment-status");
2101
+ return {
2102
+ ok: true,
2103
+ outcome: { kind: "payment_status" }
2104
+ };
2105
+ case "checkoutRequired":
2106
+ options.setPendingRegistrationReference(result.reference);
2107
+ savePendingCheckout(options, deps, prepared, result.submissionId, result.reference);
2108
+ options.setStep("redirecting");
2109
+ return redirectToCheckout(result.checkoutUrl, submitOptions);
2110
+ case "waitlisted":
2111
+ options.setRegistrationResult({
2112
+ status: "waitlist",
2113
+ title: "Added to waitlist",
2114
+ message: result.waitlist.message
2115
+ });
2116
+ break;
2117
+ case "bookingComplete":
2118
+ case "paymentDeferred":
2119
+ options.setPendingRegistrationReference(result.reference);
2120
+ options.setRegistrationResult({
2121
+ status: "confirmed",
2122
+ message: result.summary.paymentMethod === "pass" ? result.summary.message : void 0
2123
+ });
2124
+ break;
2125
+ }
2126
+ options.commandRef.current = null;
2246
2127
  options.setStep("success");
2247
2128
  return {
2248
2129
  ok: true,
@@ -2273,15 +2154,7 @@ function prepareSubmitContext(options, registrationCommandId, target, method, pa
2273
2154
  email: guest.email?.trim().toLowerCase() || void 0,
2274
2155
  phone: guest.phone?.trim() || void 0
2275
2156
  })) : void 0,
2276
- subject: target.subject.kind === "occurrence" ? {
2277
- kind: "occurrence",
2278
- occurrenceId: target.subject.occurrenceId,
2279
- attendanceMode: options.selectedAttendanceMode ?? void 0
2280
- } : {
2281
- kind: "wholeSeries",
2282
- eventSeriesId: target.subject.eventSeriesId,
2283
- attendanceMode: options.selectedAttendanceMode ?? void 0
2284
- }
2157
+ subject: toRegistrationRequestSubject(target.subject, options.selectedAttendanceMode)
2285
2158
  };
2286
2159
  }
2287
2160
  function resolveSelectionSnapshot(method, passId, membershipId, bundleProductId, selection) {
@@ -2305,85 +2178,6 @@ function resolveSelectionSnapshot(method, passId, membershipId, bundleProductId,
2305
2178
  if (method === "free") return { method };
2306
2179
  throw new Error("Choose an available payment option.");
2307
2180
  }
2308
- async function submitWholeSeriesCheckout(options, deps, prepared, submitOptions) {
2309
- options.setStep("redirecting");
2310
- if (prepared.selectionSnapshot.method !== "stripe") throw new Error("Choose an available payment option.");
2311
- const checkoutAttemptId = deps.createClientCommandId();
2312
- const urls = buildCheckoutReturnUrls("registration", EVENT_REGISTRATION_ANCHOR_ID, { [EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM]: checkoutAttemptId });
2313
- const result = await options.api.createEventCheckout({
2314
- subject: prepared.subject,
2315
- ticketSelections: toTicketSelectionRequests(prepared.ticketSelections),
2316
- selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
2317
- email: options.formData.email.trim().toLowerCase(),
2318
- name: options.formData.name.trim(),
2319
- phone: options.formData.phone.trim() || void 0,
2320
- formData: options.formData,
2321
- consentAccepted: options.formData.consentAccepted,
2322
- altchaSolution: options.altchaSolution,
2323
- guests: prepared.guestsPayload,
2324
- claimToken: options.claimToken ?? void 0,
2325
- waitlistEntryId: options.waitlistEntryId ?? void 0,
2326
- discountCode: options.discountCode.trim() || void 0,
2327
- collectionOptionKind: prepared.selectionSnapshot.collectionOptionKind,
2328
- checkoutAttemptId,
2329
- ...urls
2330
- });
2331
- if (result.scope !== "wholeSeries") throw new Error("Unexpected registration scope for event checkout.");
2332
- if ("waitlistEntryId" in result) {
2333
- options.setRegistrationResult({
2334
- status: "waitlist",
2335
- variant: result.alreadyRegistered ? "info" : void 0,
2336
- title: result.alreadyRegistered ? "Already waitlisted" : "Added to waitlist",
2337
- message: result.alreadyRegistered ? "You are already on the waitlist for this event." : "You've been added to the waitlist."
2338
- });
2339
- options.setStep("success");
2340
- return {
2341
- ok: true,
2342
- outcome: { kind: "completed" }
2343
- };
2344
- }
2345
- if (result.bypassed) return completeBypassedCheckout(options);
2346
- if (!result.checkoutUrl) throw new Error("Checkout URL missing");
2347
- savePendingCheckout(options, deps, prepared, result.checkoutAttemptId, {
2348
- scope: "wholeSeries",
2349
- registrationId: result.registrationId
2350
- });
2351
- return redirectToCheckout(result.checkoutUrl, submitOptions);
2352
- }
2353
- function needsOccurrenceCheckout(result, method) {
2354
- return result.scope === "occurrence" && result.status === "confirmed" && result.pendingPayment && [
2355
- "stripe",
2356
- "pass_bundle",
2357
- "membership_bundle"
2358
- ].includes(method);
2359
- }
2360
- async function submitOccurrenceCheckout(options, deps, prepared, registration, method, submitOptions) {
2361
- const reference = {
2362
- scope: "occurrence",
2363
- attendeeId: registration.attendeeId
2364
- };
2365
- options.setPendingRegistrationReference(reference);
2366
- options.setStep("redirecting");
2367
- const checkoutAttemptId = deps.createClientCommandId();
2368
- const urls = buildCheckoutReturnUrls("registration", EVENT_REGISTRATION_ANCHOR_ID, { [EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM]: checkoutAttemptId });
2369
- const result = await options.api.createEventCheckout({
2370
- subject: {
2371
- kind: "occurrence",
2372
- attendeeId: registration.attendeeId
2373
- },
2374
- registrationCommandId: prepared.registrationCommandId,
2375
- checkoutType: method === "pass_bundle" ? "pass_bundle" : method === "membership_bundle" ? "membership_bundle" : "event",
2376
- productId: prepared.bundleProductId,
2377
- discountCode: options.discountCode.trim() || void 0,
2378
- collectionOptionKind: prepared.selectionSnapshot.method === "stripe" ? prepared.selectionSnapshot.collectionOptionKind : void 0,
2379
- ...urls
2380
- });
2381
- if (result.scope !== "occurrence") throw new Error("Unexpected registration scope for event checkout.");
2382
- if (result.bypassed) return completeBypassedCheckout(options);
2383
- if (!result.checkoutUrl) throw new Error("Checkout URL missing");
2384
- if (prepared.subject.kind === "occurrence") savePendingCheckout(options, deps, prepared, checkoutAttemptId, reference);
2385
- return redirectToCheckout(result.checkoutUrl, submitOptions);
2386
- }
2387
2181
  function savePendingCheckout(options, deps, prepared, checkoutAttemptId, reference) {
2388
2182
  const siteId = options.api.config.siteId;
2389
2183
  if (!siteId) return;
@@ -2396,14 +2190,6 @@ function savePendingCheckout(options, deps, prepared, checkoutAttemptId, referen
2396
2190
  paymentSelection: prepared.selectionSnapshot
2397
2191
  });
2398
2192
  }
2399
- function completeBypassedCheckout(options) {
2400
- options.setRegistrationResult({ status: "confirmed" });
2401
- options.setStep("success");
2402
- return {
2403
- ok: true,
2404
- outcome: { kind: "completed" }
2405
- };
2406
- }
2407
2193
  function redirectToCheckout(checkoutUrl, submitOptions) {
2408
2194
  if (submitOptions?.redirect !== "manual") window.location.href = checkoutUrl;
2409
2195
  return {
@@ -2415,12 +2201,23 @@ function redirectToCheckout(checkoutUrl, submitOptions) {
2415
2201
  };
2416
2202
  }
2417
2203
  function registrationRequestIntentKey(request) {
2418
- const { registrationCommandId: _command, altchaSolution: _proof, ...intent } = request;
2419
- return JSON.stringify(intent);
2204
+ const { registrationCommandId: _command, altchaSolution: _proof, checkout, ...intent } = request;
2205
+ return JSON.stringify({
2206
+ ...intent,
2207
+ checkout: checkout ? {
2208
+ discountCode: checkout.discountCode,
2209
+ collectionOptionKind: checkout.collectionOptionKind
2210
+ } : void 0
2211
+ });
2420
2212
  }
2421
- function buildOccurrenceRegistrationRequest(options, prepared, method, passId, membershipId) {
2213
+ function buildRegistrationRequest(options, prepared, method, passId, membershipId) {
2422
2214
  return {
2423
2215
  registrationCommandId: prepared.registrationCommandId,
2216
+ checkout: {
2217
+ ...buildCheckoutReturnUrls("registration", EVENT_REGISTRATION_ANCHOR_ID, { [EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM]: prepared.registrationCommandId }),
2218
+ discountCode: options.discountCode.trim() || void 0,
2219
+ collectionOptionKind: prepared.selectionSnapshot.method === "stripe" ? prepared.selectionSnapshot.collectionOptionKind : void 0
2220
+ },
2424
2221
  subject: prepared.subject,
2425
2222
  ticketSelections: toTicketSelectionRequests(prepared.ticketSelections),
2426
2223
  selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
@@ -2638,20 +2435,124 @@ function useWaitlistClaimRestoration(input) {
2638
2435
  }
2639
2436
  //#endregion
2640
2437
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationCompletion.ts
2438
+ function isSelectionSoldOut(availability) {
2439
+ return availability?.kind === "unavailable" && availability.reason === "full";
2440
+ }
2641
2441
  function resolveRegistrationAvailabilityBlocker(input) {
2642
2442
  if (input.hasWaitlistClaim) return null;
2643
2443
  if (input.eligibilityError) return input.eligibilityError;
2644
2444
  if (!input.eligibility) return "Registration is not available for this event.";
2445
+ if (input.eligibility.registration.kind === "pending_payment") return null;
2446
+ if (isSelectionSoldOut(input.eligibility.availability)) return null;
2645
2447
  if (input.eligibility.availability.kind === "unavailable" && input.eligibility.availability.reason === "paymentUnavailable") return "Payment options are not available for this event.";
2646
- if (input.eligibility.availability.kind === "unavailable" || !input.eligibility.eligible) return "Registration is not available for this event.";
2448
+ if (input.eligibility.availability.kind === "unavailable" || input.eligibility.registration.kind === "waitlisted") return "Registration is not available for this event.";
2647
2449
  return null;
2648
2450
  }
2649
2451
  function resolvePricingSelectionBlocker(input) {
2650
2452
  if (input.selectionRequired && !input.hasValidSelection) return "Choose at least one available ticket to continue.";
2651
2453
  return input.slidingScaleSelectionMessage;
2652
2454
  }
2653
- function isSoldOutWithoutWaitlist(input) {
2654
- return Boolean(!input.hasWaitlistClaim && input.eligibility?.isFull && !input.eligibility.canJoinWaitlist);
2455
+ //#endregion
2456
+ //#region ../blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.ts
2457
+ function isCheckableRecovery(state) {
2458
+ return state.kind === "ready" || state.kind === "processing" || state.kind === "temporarilyUnavailable";
2459
+ }
2460
+ function assistanceRequired(recovery) {
2461
+ return {
2462
+ kind: "assistanceRequired",
2463
+ subject: recovery.subject
2464
+ };
2465
+ }
2466
+ function applyStatusOutcome(outcome, recovery, setters, setRecovery) {
2467
+ if (outcome.kind === "resolved") {
2468
+ setters.setPendingRegistrationReference(outcome.reference);
2469
+ setters.setRegistrationResult({ status: "confirmed" });
2470
+ setRecovery(null);
2471
+ setters.setStep("success");
2472
+ return;
2473
+ }
2474
+ switch (outcome.kind) {
2475
+ case "paymentProcessing":
2476
+ setRecovery({
2477
+ ...recovery,
2478
+ kind: "processing"
2479
+ });
2480
+ return;
2481
+ case "paymentAvailable":
2482
+ setRecovery({
2483
+ ...recovery,
2484
+ kind: "paymentAvailable",
2485
+ checkoutUrl: outcome.checkoutUrl
2486
+ });
2487
+ return;
2488
+ case "paymentCheckUnavailable":
2489
+ setRecovery({
2490
+ ...recovery,
2491
+ kind: "temporarilyUnavailable"
2492
+ });
2493
+ return;
2494
+ case "paymentRetryAvailable":
2495
+ setRecovery({
2496
+ ...recovery,
2497
+ kind: "retryAvailable"
2498
+ });
2499
+ return;
2500
+ case "abandoned":
2501
+ case "notFound":
2502
+ case "reconciliationRequired":
2503
+ case "paymentAssistanceRequired":
2504
+ setRecovery(assistanceRequired(recovery));
2505
+ return;
2506
+ }
2507
+ }
2508
+ function usePaymentStatusRecovery(options) {
2509
+ const [recovery, setRecovery] = useState(null);
2510
+ const checkRef = useRef(null);
2511
+ return {
2512
+ recovery,
2513
+ showRecovery: useCallback((next) => setRecovery(next), []),
2514
+ check: useCallback(() => {
2515
+ if (!recovery || !isCheckableRecovery(recovery) || checkRef.current) return;
2516
+ const pending = (async () => {
2517
+ setRecovery({
2518
+ ...recovery,
2519
+ kind: "checking"
2520
+ });
2521
+ try {
2522
+ applyStatusOutcome(await options.api.resolveEventCheckoutReturn({
2523
+ kind: "checkStatus",
2524
+ subject: recovery.subject
2525
+ }), recovery, options, setRecovery);
2526
+ } catch {
2527
+ setRecovery({
2528
+ ...recovery,
2529
+ kind: "temporarilyUnavailable"
2530
+ });
2531
+ }
2532
+ })();
2533
+ checkRef.current = pending;
2534
+ pending.finally(() => {
2535
+ if (checkRef.current === pending) checkRef.current = null;
2536
+ });
2537
+ }, [options, recovery]),
2538
+ continuePayment: useCallback(() => {
2539
+ if (recovery?.kind === "paymentAvailable") window.location.assign(recovery.checkoutUrl);
2540
+ }, [recovery]),
2541
+ returnToPayment: useCallback(() => {
2542
+ if (recovery?.kind !== "retryAvailable") return;
2543
+ options.beginNewSubmission();
2544
+ setRecovery(null);
2545
+ options.setOptionsRefreshKey((value) => value + 1);
2546
+ options.setStep("select-payment");
2547
+ }, [options, recovery]),
2548
+ returnToBooking: useCallback(() => {
2549
+ if (recovery?.kind !== "assistanceRequired") return;
2550
+ options.beginNewSubmission();
2551
+ setRecovery(null);
2552
+ options.setOptionsRefreshKey((value) => value + 1);
2553
+ options.setStep("select-tickets");
2554
+ }, [options, recovery])
2555
+ };
2655
2556
  }
2656
2557
  //#endregion
2657
2558
  //#region ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.ts
@@ -2709,6 +2610,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2709
2610
  const [errorAction, setErrorAction] = useState("retry");
2710
2611
  const [optionsRefreshKey, setOptionsRefreshKey] = useState(0);
2711
2612
  const [altchaSolution, setAltchaSolution] = useState(null);
2613
+ const [checkingNextStep, setCheckingNextStep] = useState(false);
2712
2614
  const { authState: portalAuthState, profile: portalProfile } = deps.usePortalAuth();
2713
2615
  const waitlistClaim = deps.useWaitlistClaim();
2714
2616
  const [showMagicLinkModal, setShowMagicLinkModal] = useState(false);
@@ -2735,28 +2637,29 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2735
2637
  }), [options.occurrenceContext, selectedEvent]);
2736
2638
  const registrationOccurrenceId = getRegistrationOccurrenceId(registrationTarget);
2737
2639
  const currentEvent = selectedEvent ?? preSelectedEvent;
2640
+ const commandRef = useRef(null);
2738
2641
  const setStep = useCallback((nextStep) => {
2739
2642
  dispatchFlow({
2740
2643
  type: "set-step",
2741
2644
  step: nextStep
2742
2645
  });
2743
2646
  }, []);
2744
- const { checkoutRecovery, startCheckoutRecovery } = useCheckoutRecovery(currentEvent, setStep);
2745
- const currentEventUsesTicketTypes = usesTieredPricing(currentEvent);
2746
- const currentCanJoinWaitlist = canJoinWaitlistOutsideRecovery(currentEvent, selectedAttendanceMode, checkoutRecovery);
2747
- const usesTicketTypeSelections = currentEventUsesTicketTypes && !currentCanJoinWaitlist;
2647
+ const { recovery: paymentStatusRecovery, showRecovery: setPaymentStatusRecovery, check: handleCheckPaymentStatus, continuePayment: handleContinueExistingPayment, returnToPayment: handleReturnToPayment, returnToBooking: handleReturnToBooking } = usePaymentStatusRecovery({
2648
+ api,
2649
+ beginNewSubmission: () => {
2650
+ commandRef.current = null;
2651
+ },
2652
+ setPendingRegistrationReference,
2653
+ setRegistrationResult,
2654
+ setOptionsRefreshKey,
2655
+ setStep
2656
+ });
2657
+ const usesTicketTypeSelections = usesTieredPricing(currentEvent);
2748
2658
  const eventTicketTypesForSelection = useMemo(() => getVisibleTicketTypes({
2749
2659
  event: currentEvent,
2750
2660
  eligibility: null,
2751
2661
  selectedAttendanceMode
2752
- }).map((option) => checkoutRecovery ? {
2753
- ...option,
2754
- availability: checkoutRecoveryOptionAvailability(option.availability)
2755
- } : option), [
2756
- currentEvent,
2757
- selectedAttendanceMode,
2758
- checkoutRecovery
2759
- ]);
2662
+ }), [currentEvent, selectedAttendanceMode]);
2760
2663
  const normalizedSelectedTicketLines = useMemo(() => normalizeTicketSelectionLines(selectedTicketLines), [selectedTicketLines]);
2761
2664
  const ticketTypeSelectionTotal = useMemo(() => getTicketSelectionTotal(normalizedSelectedTicketLines), [normalizedSelectedTicketLines]);
2762
2665
  const ticketsCount = usesTicketTypeSelections ? ticketTypeSelectionTotal : flowState.ticketsCount;
@@ -2794,7 +2697,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2794
2697
  isAuthenticated: portalAuthState === "authenticated",
2795
2698
  occurrenceId: registrationOccurrenceId
2796
2699
  });
2797
- const eligibility = useCheckoutRecoveryEligibility(eligibilityResult.eligibility, step, checkoutRecovery, startCheckoutRecovery);
2700
+ const eligibility = eligibilityResult.eligibility;
2798
2701
  const eligibilityLoading = eligibilityResult.isLoading;
2799
2702
  const eligibilityError = eligibilityResult.error;
2800
2703
  const portalCreditState = eligibilityResult.creditState;
@@ -2906,14 +2809,10 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2906
2809
  event: currentEvent,
2907
2810
  eligibility,
2908
2811
  selectedAttendanceMode
2909
- }).map((option) => checkoutRecovery ? {
2910
- ...option,
2911
- availability: checkoutRecoveryOptionAvailability(option.availability)
2912
- } : option), [
2812
+ }), [
2913
2813
  currentEvent,
2914
2814
  selectedAttendanceMode,
2915
- eligibility,
2916
- checkoutRecovery
2815
+ eligibility
2917
2816
  ]);
2918
2817
  const singleSelectedPricingOptionId = getSingleSelectedPricingOptionId(normalizedSelectedTicketLines);
2919
2818
  const selectedTicketType = useMemo(() => findSelectedTicketType({
@@ -2937,12 +2836,8 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2937
2836
  const event = currentEvent;
2938
2837
  if (!usesTieredPricing(event)) return false;
2939
2838
  if ((event.deliveryMode ?? "in_person") === "hybrid" && !selectedAttendanceMode) return false;
2940
- return checkoutRecovery || !canJoinEventWaitlist(event, { attendanceMode: selectedAttendanceMode });
2941
- }, [
2942
- currentEvent,
2943
- selectedAttendanceMode,
2944
- checkoutRecovery
2945
- ]);
2839
+ return true;
2840
+ }, [currentEvent, selectedAttendanceMode]);
2946
2841
  const hasValidTicketSelection = hasValidPricingOptionSelection({
2947
2842
  selectionRequired: ticketTypeSelectionRequired,
2948
2843
  lines: normalizedSelectedTicketLines,
@@ -2953,8 +2848,8 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2953
2848
  if (selectedTicketLines.length > 0) setSelectedTicketLines([]);
2954
2849
  return;
2955
2850
  }
2956
- const availableTicketTypes = eventTicketTypesForSelection.filter((ticketType) => ticketType.availability.kind === "available");
2957
- const implicitTicketType = getImplicitSingleAvailableTicketType(eventTicketTypesForSelection);
2851
+ const availableTicketTypes = eventTicketTypesForSelection.filter((ticketType) => canRequestTicketOption(ticketType.availability));
2852
+ const implicitTicketType = getImplicitSingleTicketType(eventTicketTypesForSelection);
2958
2853
  if (implicitTicketType && normalizedSelectedTicketLines.length === 0) {
2959
2854
  setSelectedTicketLines([{
2960
2855
  pricingOptionId: implicitTicketType.pricingOptionId,
@@ -3021,6 +2916,26 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3021
2916
  selectedEventSlidingScale,
3022
2917
  selectedSlidingAmount
3023
2918
  ]);
2919
+ const checkDetailsNextStep = async () => {
2920
+ if (portalAuthState === "authenticated" || waitlistClaim.claim || eligibility?.availability.kind === "available" || !registrationTarget) return { kind: canRecoverToWaitlist(eligibility) ? "waitlist" : "continue" };
2921
+ setCheckingNextStep(true);
2922
+ try {
2923
+ return await api.checkRegistrationNextStep({
2924
+ subject: toRegistrationRequestSubject(registrationTarget.subject, selectedAttendanceMode),
2925
+ selections: ticketSelections,
2926
+ selectedSlidingAmount: selectedSlidingAmount ?? void 0,
2927
+ email: formData.email,
2928
+ altchaSolution
2929
+ });
2930
+ } catch {
2931
+ return {
2932
+ kind: "unavailable",
2933
+ message: "We could not check availability. Please try again."
2934
+ };
2935
+ } finally {
2936
+ setCheckingNextStep(false);
2937
+ }
2938
+ };
3024
2939
  const handleDetailsComplete = async () => {
3025
2940
  const blockDetailsCompletion = (message) => {
3026
2941
  setErrorAction("places");
@@ -3052,11 +2967,10 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3052
2967
  message: "Check the highlighted fields and try again."
3053
2968
  };
3054
2969
  }
3055
- if (isSoldOutWithoutWaitlist({
3056
- hasWaitlistClaim: waitlistClaim.claim !== null,
3057
- eligibility
3058
- })) return blockDetailsCompletion("Sorry, there aren’t enough places left for your selection. Please check the available places and choose again.");
3059
- if (!eligibility?.canJoinWaitlist && requiresPaymentStep({
2970
+ const next = await checkDetailsNextStep();
2971
+ if (next.kind === "unavailable") return blockDetailsCompletion(next.message);
2972
+ const canJoinWaitlist = next.kind === "waitlist";
2973
+ if (!canJoinWaitlist && requiresPaymentStep({
3060
2974
  eventRequiresPayment,
3061
2975
  paymentSelection
3062
2976
  })) {
@@ -3069,7 +2983,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3069
2983
  const submitPlan = resolveEventRegistrationSubmitPlan({
3070
2984
  paymentSelection,
3071
2985
  eventRequiresPayment,
3072
- canJoinWaitlist: eligibility?.canJoinWaitlist
2986
+ canJoinWaitlist
3073
2987
  });
3074
2988
  if (!submitPlan.ok) return blockDetailsCompletion(submitPlan.message);
3075
2989
  const submitResult = await handleSubmit(submitPlan.args.method, submitPlan.args.passId, submitPlan.args.membershipId, submitPlan.args.selection);
@@ -3087,7 +3001,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3087
3001
  const submitPlan = resolveEventRegistrationSubmitPlan({
3088
3002
  paymentSelection,
3089
3003
  eventRequiresPayment,
3090
- canJoinWaitlist: eligibility?.canJoinWaitlist
3004
+ canJoinWaitlist: false
3091
3005
  });
3092
3006
  if (!submitPlan.ok) {
3093
3007
  setErrorMessage(submitPlan.message);
@@ -3119,6 +3033,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3119
3033
  const selectionMethod = paymentSelection.method;
3120
3034
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
3121
3035
  const handleSubmit = deps.useEventRegistrationSubmit({
3036
+ commandRef,
3122
3037
  setErrorAction,
3123
3038
  api,
3124
3039
  registrationTarget,
@@ -3135,7 +3050,8 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3135
3050
  setPendingRegistrationReference,
3136
3051
  setStep,
3137
3052
  setRegistrationResult: (result) => setRegistrationResult(result),
3138
- setErrorMessage
3053
+ setErrorMessage,
3054
+ setPaymentStatusRecovery
3139
3055
  });
3140
3056
  const handleEventSelect = (event) => {
3141
3057
  setSelectedEvent(event);
@@ -3194,8 +3110,6 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3194
3110
  scopedEvents,
3195
3111
  preSelectedEvent,
3196
3112
  hasPreSelectedContext,
3197
- checkoutRecovery,
3198
- startCheckoutRecovery,
3199
3113
  step,
3200
3114
  setStep,
3201
3115
  selectedEvent,
@@ -3249,7 +3163,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3249
3163
  waitlistClaim,
3250
3164
  eligibility,
3251
3165
  registrationOptionsState: eligibilityResult.state,
3252
- eligibilityLoading,
3166
+ eligibilityLoading: eligibilityLoading || checkingNextStep,
3253
3167
  eligibilityError,
3254
3168
  existingRegistration: existingRegistrationResult.registration,
3255
3169
  existingRegistrationLoading: existingRegistrationResult.isLoading || portalAuthState === "authenticated" && eligibilityLoading,
@@ -3269,6 +3183,11 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3269
3183
  discountContext,
3270
3184
  shouldShowDiscountToggle,
3271
3185
  verification,
3186
+ paymentStatusRecovery,
3187
+ handleCheckPaymentStatus,
3188
+ handleContinueExistingPayment,
3189
+ handleReturnToPayment,
3190
+ handleReturnToBooking,
3272
3191
  handleDetailsComplete,
3273
3192
  handlePaymentSelect,
3274
3193
  handleUseCardInsteadOfPass,
@@ -3440,7 +3359,6 @@ const labels = {
3440
3359
  start: "Start again",
3441
3360
  details: "Check my details",
3442
3361
  "payment-options": "View payment options",
3443
- "payment-status": "Check payment status",
3444
3362
  portal: "View my bookings",
3445
3363
  event: "View event"
3446
3364
  };
@@ -3459,7 +3377,7 @@ const ErrorStep = ({ message, onRetry, action = "retry", canJoinWaitlist = false
3459
3377
  className: "er-text--muted er-message",
3460
3378
  children: offerWaitlist ? "Sorry, this event is now full. You can join the waiting list, and we’ll email you if a place becomes available." : message
3461
3379
  }),
3462
- actions: action === "none" ? void 0 : action === "portal" || action === "payment-status" ? /* @__PURE__ */ jsx("a", {
3380
+ actions: action === "none" ? void 0 : action === "portal" ? /* @__PURE__ */ jsx("a", {
3463
3381
  href: "/portal",
3464
3382
  className: "er-retry-btn",
3465
3383
  children: labels[action]
@@ -3713,8 +3631,11 @@ function EventRegistrationSummaryBar(props) {
3713
3631
  */
3714
3632
  function resolveDetailsButtonText(input) {
3715
3633
  const { wizard } = input;
3716
- if (wizard.eligibility?.canJoinWaitlist) return "Join waitlist";
3717
- if (!input.hasWaitlistClaim && wizard.eligibility?.isFull) return "Sold out";
3634
+ const availability = wizard.eligibility?.availability;
3635
+ const full = availability?.kind === "waitlist" || isSelectionSoldOut(availability);
3636
+ if (wizard.portalAuthState !== "authenticated" && full) return "Continue";
3637
+ if (canRecoverToWaitlist(wizard.eligibility)) return "Join waitlist";
3638
+ if (!input.hasWaitlistClaim && full && wizard.eligibility?.registration.kind !== "pending_payment") return "Sold out";
3718
3639
  if (wizard.paymentSelection.method === "pass") return "Book using class pass";
3719
3640
  if (wizard.paymentSelection.method === "membership") return input.fallbackText;
3720
3641
  return wizard.eventRequiresPayment ? "Continue to payment" : input.fallbackText;
@@ -3729,44 +3650,28 @@ function isExistingRegistrationBlocking$1(wizard) {
3729
3650
  hasWaitlistClaim: wizard.waitlistClaim.claim !== null
3730
3651
  });
3731
3652
  }
3732
- /**
3733
- * Full for the visitor's selected attendance mode (or the whole event when no
3734
- * mode is chosen) with no waitlist to fall back on. Reads the same capacity
3735
- * source as the steps above the action bar so the button never disagrees
3736
- * with them on a hybrid event.
3737
- */
3738
- function isFullWithNoWaitlist(event, attendanceMode) {
3739
- if (!event) return false;
3740
- const source = selectEventCapacitySource(event, attendanceMode);
3741
- return getEventAvailability(source).isFull && !source.waitlistEnabled;
3742
- }
3743
3653
  function resolveTicketSelectionContinueLabel(input) {
3744
3654
  if (input.isCheckingExistingRegistration) return "Checking registration...";
3745
3655
  if (input.registrationOptionsPending) return "Checking availability...";
3746
3656
  if (input.registrationOptionsFailed) return "Availability unavailable";
3747
3657
  if (input.hasExistingRegistration) return resolveExistingRegistrationLabel(input.wizard);
3748
- if (input.isSoldOutWithoutWaitlist) return "Sold out";
3749
3658
  if (input.ticketTypeSelectionBlocked) return "Choose a ticket";
3750
3659
  return "Continue";
3751
3660
  }
3752
3661
  function SelectTicketsActions(props) {
3753
3662
  const { wizard } = props;
3754
- const hasWaitlistClaim = wizard.waitlistClaim.claim !== null;
3755
- const currentEvent = wizard.selectedEvent ?? wizard.preSelectedEvent;
3756
3663
  const registrationOptionsPending = wizard.registrationOptionsState.tag === "idle" || wizard.registrationOptionsState.tag === "loading";
3757
3664
  const registrationOptionsFailed = wizard.registrationOptionsState.tag === "failed";
3758
- const isSoldOutWithoutWaitlist = !wizard.checkoutRecovery && !hasWaitlistClaim && isFullWithNoWaitlist(currentEvent, wizard.selectedAttendanceMode);
3759
3665
  const isCheckingExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistrationLoading;
3760
3666
  const hasExistingRegistration = isExistingRegistrationBlocking$1(wizard);
3761
3667
  const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidTicketSelection;
3762
- const isContinueDisabled = registrationOptionsPending || registrationOptionsFailed || isCheckingExistingRegistration || hasExistingRegistration || isSoldOutWithoutWaitlist || ticketTypeSelectionBlocked;
3668
+ const isContinueDisabled = registrationOptionsPending || registrationOptionsFailed || isCheckingExistingRegistration || hasExistingRegistration || ticketTypeSelectionBlocked;
3763
3669
  const continueLabel = resolveTicketSelectionContinueLabel({
3764
3670
  wizard,
3765
3671
  registrationOptionsPending,
3766
3672
  registrationOptionsFailed,
3767
3673
  isCheckingExistingRegistration,
3768
3674
  hasExistingRegistration,
3769
- isSoldOutWithoutWaitlist,
3770
3675
  ticketTypeSelectionBlocked
3771
3676
  });
3772
3677
  return /* @__PURE__ */ jsxs("div", {
@@ -3797,8 +3702,12 @@ function EnterDetailsActions(props) {
3797
3702
  const { wizard } = props;
3798
3703
  const hasWaitlistClaim = wizard.waitlistClaim.claim !== null;
3799
3704
  const isCheckingEligibility = wizard.eligibilityLoading;
3800
- const eligibilityUnavailable = !hasWaitlistClaim && (Boolean(wizard.eligibilityError) || !wizard.eligibility || wizard.eligibility.availability.kind === "unavailable" || !wizard.eligibility.eligible);
3801
- const isSoldOut = !hasWaitlistClaim && wizard.eligibility?.isFull === true && wizard.eligibility.canJoinWaitlist !== true;
3705
+ const eligibilityUnavailable = Boolean(resolveRegistrationAvailabilityBlocker({
3706
+ hasWaitlistClaim,
3707
+ eligibility: wizard.eligibility,
3708
+ eligibilityError: wizard.eligibilityError
3709
+ }));
3710
+ const isSoldOut = !hasWaitlistClaim && wizard.portalAuthState === "authenticated" && wizard.eligibility?.registration.kind !== "pending_payment" && isSelectionSoldOut(wizard.eligibility?.availability);
3802
3711
  const isContinueDisabled = isCheckingEligibility || eligibilityUnavailable || isSoldOut;
3803
3712
  const continueLabel = isCheckingEligibility ? "Checking availability..." : wizard.eligibilityError ? "Availability unavailable" : eligibilityUnavailable ? "Registration unavailable" : resolveDetailsButtonText({
3804
3713
  wizard,
@@ -3849,6 +3758,7 @@ function EventRegistrationWizardActions(props) {
3849
3758
  case "redirecting":
3850
3759
  case "verifying-payment":
3851
3760
  case "verifying-timeout":
3761
+ case "payment-status":
3852
3762
  case "payment-failed": return null;
3853
3763
  case "select-event": return null;
3854
3764
  case "select-tickets": return /* @__PURE__ */ jsx(SelectTicketsActions, { ...props });
@@ -3933,7 +3843,7 @@ function buildSelectedSiteCreditPreview(input) {
3933
3843
  function resolvePaymentStepStatus(input) {
3934
3844
  if (input.loading) return "loading";
3935
3845
  if (input.error || !input.eligibility) return "error";
3936
- if (input.eligibility.registration.kind !== "none" || !input.eligibility.eligible && !input.eligibility.isFree) return "not-eligible";
3846
+ if (input.eligibility.registration.kind === "waitlisted" || input.eligibility.paymentOptions.length === 0) return "not-eligible";
3937
3847
  return "ready";
3938
3848
  }
3939
3849
  function shouldPromptForLogin(status, isAuthenticated, eligibility) {
@@ -3968,7 +3878,7 @@ function UnavailableEntitlementMessage(props) {
3968
3878
  ] });
3969
3879
  }
3970
3880
  function IneligiblePaymentPanel(props) {
3971
- if (props.eligibility.isFull && !props.eligibility.canJoinWaitlist) return /* @__PURE__ */ jsxs("div", {
3881
+ if (isSelectionSoldOut(props.eligibility.availability)) return /* @__PURE__ */ jsxs("div", {
3972
3882
  className: "rb-reg-stack er-stack",
3973
3883
  children: [/* @__PURE__ */ jsx("h3", {
3974
3884
  className: "er-title",
@@ -4037,26 +3947,23 @@ function PaymentBlockingPanel(props) {
4037
3947
  })
4038
3948
  });
4039
3949
  if (!props.eligibility) return /* @__PURE__ */ jsx(MissingEligibilityPanel, {});
4040
- if (props.eligibility.registration.kind !== "none") {
4041
- const isBooked = props.eligibility.registration.kind === "booked";
4042
- return /* @__PURE__ */ jsxs("div", {
4043
- className: "rb-reg-stack er-stack",
4044
- children: [/* @__PURE__ */ jsx("h3", {
4045
- className: "er-title",
4046
- children: isBooked ? "Already booked" : "Already on the waitlist"
4047
- }), /* @__PURE__ */ jsxs("div", {
4048
- className: "er-center",
4049
- children: [/* @__PURE__ */ jsx("p", {
4050
- className: "er-text er-text--muted",
4051
- children: isBooked ? "You already have a booking for this event. Please check your bookings to see its status. If you need help, contact the organiser." : "You're already on the waitlist for this event."
4052
- }), /* @__PURE__ */ jsx("a", {
4053
- href: "/portal",
4054
- className: "er-retry-btn",
4055
- children: "View my bookings"
4056
- })]
3950
+ if (props.eligibility.registration.kind === "waitlisted") return /* @__PURE__ */ jsxs("div", {
3951
+ className: "rb-reg-stack er-stack",
3952
+ children: [/* @__PURE__ */ jsx("h3", {
3953
+ className: "er-title",
3954
+ children: "Already on the waitlist"
3955
+ }), /* @__PURE__ */ jsxs("div", {
3956
+ className: "er-center",
3957
+ children: [/* @__PURE__ */ jsx("p", {
3958
+ className: "er-text er-text--muted",
3959
+ children: "You're already on the waitlist for this event."
3960
+ }), /* @__PURE__ */ jsx("a", {
3961
+ href: "/portal",
3962
+ className: "er-retry-btn",
3963
+ children: "View my bookings"
4057
3964
  })]
4058
- });
4059
- }
3965
+ })]
3966
+ });
4060
3967
  if (props.eligibility.isFree) return /* @__PURE__ */ jsxs("div", {
4061
3968
  className: "rb-reg-stack er-stack",
4062
3969
  children: [/* @__PURE__ */ jsx("h3", {
@@ -4077,7 +3984,7 @@ function PaymentBlockingPanel(props) {
4077
3984
  })]
4078
3985
  })]
4079
3986
  });
4080
- return props.eligibility.eligible ? null : /* @__PURE__ */ jsx(IneligiblePaymentPanel, {
3987
+ return props.eligibility.paymentOptions.length > 0 ? null : /* @__PURE__ */ jsx(IneligiblePaymentPanel, {
4081
3988
  eligibility: props.eligibility,
4082
3989
  email: props.email,
4083
3990
  isAuthenticated: props.isAuthenticated,
@@ -4209,7 +4116,7 @@ function SlidingScaleAmountField(props) {
4209
4116
  }
4210
4117
  function TicketTypeQuantityRows(props) {
4211
4118
  const totalSelectedQuantity = getTicketSelectionTotal(props.ticketSelections);
4212
- const effectiveMaxTickets = props.availableSpots == null ? props.maxTickets : Math.min(props.maxTickets, props.availableSpots);
4119
+ const effectiveMaxTickets = props.maxTickets;
4213
4120
  return /* @__PURE__ */ jsxs("div", {
4214
4121
  className: "er-ticket-lines",
4215
4122
  children: [
@@ -4219,16 +4126,14 @@ function TicketTypeQuantityRows(props) {
4219
4126
  "aria-label": props.bookingUnit === "place" ? "Places" : "Tickets",
4220
4127
  children: props.ticketTypes.map((ticketType) => {
4221
4128
  const quantity = getTicketLineQuantity(props.ticketSelections, ticketType.pricingOptionId);
4222
- const disabled = ticketType.availability.kind === "unavailable";
4129
+ const disabled = !canRequestTicketOption(ticketType.availability);
4223
4130
  const availabilityLabel = getTicketTypeAvailabilityLabel(ticketType);
4224
4131
  const ticketTypeRemainingLabel = formatTicketTypeRemaining(ticketType.availability.remainingPlaces);
4225
4132
  const priceLabel = ticketType.priceAmount > 0 ? formatPrice(ticketType.priceAmount, props.currency) : "Free";
4226
4133
  const maxAllowed = disabled ? 0 : getTicketTypeLineMax({
4227
4134
  currentQuantity: quantity,
4228
4135
  totalSelectedQuantity,
4229
- maxTickets: props.maxTickets,
4230
- availableSpots: props.availableSpots,
4231
- ticketTypeRemainingCapacity: ticketType.availability.remainingPlaces
4136
+ maxTickets: props.maxTickets
4232
4137
  });
4233
4138
  return /* @__PURE__ */ jsxs("div", {
4234
4139
  className: `er-ticket-line${disabled ? " er-ticket-line--disabled" : ""}`,
@@ -4333,33 +4238,13 @@ function AttendanceModeTiles(props) {
4333
4238
  })
4334
4239
  });
4335
4240
  }
4336
- function getMaxAllowedTicketCount(input) {
4337
- if (input.canJoinWaitlist) return input.maxTickets;
4338
- if (!input.usesTicketTypes) return Math.min(input.maxTickets, input.availableSpots ?? input.maxTickets);
4339
- if (!input.hasSelectedTicketType) return 1;
4340
- return Math.max(1, Math.min(input.maxTickets, input.selectedTicketTypeRemainingCapacity ?? input.maxTickets, input.availableSpots ?? input.maxTickets));
4341
- }
4342
4241
  function resolveTicketSelectionPricing(event) {
4343
4242
  return {
4344
4243
  mode: event ? publicEventPricingStrategy(event.pricing) : void 0,
4345
4244
  currency: event ? publicEventPricingCurrency(event.pricing) : null
4346
4245
  };
4347
4246
  }
4348
- function CheckoutRecoveryEntry(props) {
4349
- return props.onContinue ? /* @__PURE__ */ jsx("button", {
4350
- type: "button",
4351
- className: "er-back-btn",
4352
- onClick: props.onContinue,
4353
- children: "Continue an existing booking"
4354
- }) : null;
4355
- }
4356
- function getTicketSelectionCapacity(event, attendanceMode, checkoutRecovery) {
4357
- if (checkoutRecovery) return {
4358
- availableSpots: null,
4359
- isSoldOut: false,
4360
- canJoinWaitlist: false,
4361
- eventAvailabilityLabel: null
4362
- };
4247
+ function getTicketSelectionCapacity(event, attendanceMode) {
4363
4248
  const source = event ? selectEventCapacitySource(event, attendanceMode) : null;
4364
4249
  const availability = source ? getEventAvailability(source) : null;
4365
4250
  const canJoinWaitlist = source ? canJoinEventWaitlist(source) : false;
@@ -4378,29 +4263,21 @@ function getTicketSelectionCapacity(event, attendanceMode, checkoutRecovery) {
4378
4263
  *
4379
4264
  * Can use either a full PublicEvent or just occurrence context (when on event page).
4380
4265
  */
4381
- const TicketSelectionStep = ({ event, occurrenceContext: _occurrenceContext, selectedAttendanceMode = null, onAttendanceModeChange, ticketsCount, maxTickets, visibleTicketTypes: visibleTicketTypesOverride, ticketSelections = [], onTicketSelectionsChange, slidingScale = null, selectedSlidingAmountInput = "", slidingScaleSelectionMessage = null, onSlidingAmountChange, onTicketsChange, bookingUnit = { kind: "ticket" }, checkoutRecovery = false, onCheckoutRecovery }) => {
4266
+ const TicketSelectionStep = ({ event, occurrenceContext: _occurrenceContext, selectedAttendanceMode = null, onAttendanceModeChange, ticketsCount, maxTickets, visibleTicketTypes: visibleTicketTypesOverride, ticketSelections = [], onTicketSelectionsChange, slidingScale = null, selectedSlidingAmountInput = "", slidingScaleSelectionMessage = null, onSlidingAmountChange, onTicketsChange, bookingUnit = { kind: "ticket" } }) => {
4382
4267
  const unit = bookingUnit.kind;
4383
4268
  const deliveryMode = event?.deliveryMode ?? "in_person";
4384
4269
  const attendanceModes = event?.attendanceModes ?? [];
4385
- const { availableSpots, isSoldOut, canJoinWaitlist, eventAvailabilityLabel } = getTicketSelectionCapacity(event, selectedAttendanceMode, checkoutRecovery);
4270
+ const { availableSpots, isSoldOut, canJoinWaitlist, eventAvailabilityLabel } = getTicketSelectionCapacity(event, selectedAttendanceMode);
4386
4271
  const { mode: eventPricingMode, currency: eventPricingCurrency } = resolveTicketSelectionPricing(event);
4387
- const usesTicketTypes = eventPricingMode === "tiered" && !canJoinWaitlist;
4272
+ const usesTicketTypes = eventPricingMode === "tiered";
4388
4273
  const visibleTicketTypes = usesTicketTypes ? visibleTicketTypesOverride ?? getVisibleTicketTypes({
4389
4274
  event,
4390
4275
  eligibility: null,
4391
4276
  selectedAttendanceMode
4392
4277
  }) : [];
4393
- const implicitTicketType = usesTicketTypes ? getImplicitSingleAvailableTicketType(visibleTicketTypes) : null;
4394
4278
  const noVisibleTicketTypes = usesTicketTypes && visibleTicketTypes.length === 0;
4395
- const hasAvailableTicketTypes = usesTicketTypes && visibleTicketTypes.some((ticketType) => ticketType.availability.kind === "available");
4396
- const maxAllowed = getMaxAllowedTicketCount({
4397
- canJoinWaitlist,
4398
- usesTicketTypes,
4399
- maxTickets,
4400
- availableSpots,
4401
- selectedTicketTypeRemainingCapacity: implicitTicketType?.availability.remainingPlaces ?? null,
4402
- hasSelectedTicketType: implicitTicketType !== null
4403
- });
4279
+ const hasAvailableTicketTypes = usesTicketTypes && visibleTicketTypes.some((ticketType) => canRequestTicketOption(ticketType.availability));
4280
+ const maxAllowed = maxTickets;
4404
4281
  if (deliveryMode === "hybrid" && !selectedAttendanceMode) return /* @__PURE__ */ jsxs("div", {
4405
4282
  className: "rb-reg-stack er-stack",
4406
4283
  children: [
@@ -4419,20 +4296,6 @@ const TicketSelectionStep = ({ event, occurrenceContext: _occurrenceContext, sel
4419
4296
  })
4420
4297
  ]
4421
4298
  });
4422
- if (isSoldOut && !canJoinWaitlist) return /* @__PURE__ */ jsxs("div", {
4423
- className: "rb-reg-stack er-stack",
4424
- children: [
4425
- /* @__PURE__ */ jsx("h3", {
4426
- className: "er-title",
4427
- children: "Sold out"
4428
- }),
4429
- /* @__PURE__ */ jsx("p", {
4430
- className: "er-text er-text--muted",
4431
- children: "Sorry you missed out! This event is fully booked."
4432
- }),
4433
- /* @__PURE__ */ jsx(CheckoutRecoveryEntry, { onContinue: onCheckoutRecovery })
4434
- ]
4435
- });
4436
4299
  if (!eventPricingCurrency) return /* @__PURE__ */ jsx("div", {
4437
4300
  className: "rb-reg-stack er-stack",
4438
4301
  children: /* @__PURE__ */ jsx("p", {
@@ -4499,14 +4362,14 @@ const TicketSelectionStep = ({ event, occurrenceContext: _occurrenceContext, sel
4499
4362
  })]
4500
4363
  })
4501
4364
  ] }),
4502
- canJoinWaitlist && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", {
4365
+ isSoldOut && !canJoinWaitlist && /* @__PURE__ */ jsx("p", {
4503
4366
  className: "er-text er-text--muted",
4504
- children: "This event is currently full. Continue to join the waitlist if a place opens up."
4505
- }), /* @__PURE__ */ jsx(CheckoutRecoveryEntry, { onContinue: onCheckoutRecovery })] }),
4506
- checkoutRecovery && /* @__PURE__ */ jsx("p", {
4367
+ children: "This event is fully booked."
4368
+ }),
4369
+ canJoinWaitlist && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("p", {
4507
4370
  className: "er-text er-text--muted",
4508
- children: "Enter the details from your earlier booking. We’ll check your checkout before continuing to payment."
4509
- })
4371
+ children: "This event is currently full. Continue to join the waitlist if a place opens up."
4372
+ }) })
4510
4373
  ]
4511
4374
  });
4512
4375
  };
@@ -5101,7 +4964,7 @@ const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, i
5101
4964
  const occurrenceId = occurrenceContext?.id ?? (event?.registrationSubject.kind === "occurrence" ? event.registrationSubject.occurrenceId : "");
5102
4965
  const selectedMethod = selectedSelection.method;
5103
4966
  const totalPrice = directPaymentTotalCents;
5104
- const canShowDirectPaymentOptions = Boolean(eligibility?.eligible && eligibility.canPayDirectly && eligibility.price);
4967
+ const canShowDirectPaymentOptions = Boolean(eligibility?.requiresPayment && eligibility.price);
5105
4968
  const directPaymentOptions = eligibility && canShowDirectPaymentOptions ? buildEventRegistrationPaymentOptionsViewModel({
5106
4969
  eventTitle: event?.title ?? "Event",
5107
4970
  approvedPaymentOptions: eligibility.paymentOptions,
@@ -5164,7 +5027,7 @@ const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, i
5164
5027
  onRequestLogin
5165
5028
  });
5166
5029
  if (eligibilityLoading || eligibilityError || !eligibility) return blockingPanel;
5167
- if (eligibility.isFree || !eligibility.eligible) return blockingPanel;
5030
+ if (eligibility.isFree || paymentStepStatus !== "ready") return blockingPanel;
5168
5031
  const isMultiTicket = areEntitlementsDisabledForTicketCount(ticketsCount);
5169
5032
  return /* @__PURE__ */ jsxs("div", {
5170
5033
  className: "rb-reg-stack er-stack",
@@ -5398,6 +5261,7 @@ function EventRegistrationWizardContent(props) {
5398
5261
  case "redirecting":
5399
5262
  case "verifying-payment":
5400
5263
  case "verifying-timeout":
5264
+ case "payment-status":
5401
5265
  case "payment-failed": return null;
5402
5266
  case "select-event": return /* @__PURE__ */ jsx(EventSelectionStep, {
5403
5267
  events: wizard.scopedEvents,
@@ -5413,8 +5277,6 @@ function EventRegistrationWizardContent(props) {
5413
5277
  onBookForSomeoneElse: wizard.handleBookForSomeoneElse
5414
5278
  }), /* @__PURE__ */ jsx(TicketSelectionStep, {
5415
5279
  event: wizard.selectedEvent,
5416
- checkoutRecovery: wizard.checkoutRecovery,
5417
- onCheckoutRecovery: canRecoverEventCheckout(wizard.selectedEvent) ? wizard.startCheckoutRecovery : void 0,
5418
5280
  occurrenceContext: wizard.occurrenceContext,
5419
5281
  selectedAttendanceMode: wizard.selectedAttendanceMode,
5420
5282
  onAttendanceModeChange: wizard.setSelectedAttendanceMode,
@@ -5469,6 +5331,81 @@ const PaymentFailedStep = ({ onRetry }) => {
5469
5331
  });
5470
5332
  };
5471
5333
  //#endregion
5334
+ //#region ../blocks/src/system/runtime/nodes/event-registration/PaymentStatusStep.tsx
5335
+ function presentation(state) {
5336
+ switch (state.kind) {
5337
+ case "ready": return {
5338
+ title: "We haven’t confirmed your payment yet",
5339
+ message: "We’ve saved your booking details, but we haven’t been able to confirm that your payment was successful. Please don’t make another payment. Select Check payment status to get the latest update.",
5340
+ action: { label: "Check payment status" }
5341
+ };
5342
+ case "checking": return {
5343
+ title: "We haven’t confirmed your payment yet",
5344
+ message: "We’ve saved your booking details, but we haven’t been able to confirm that your payment was successful. Please don’t make another payment.",
5345
+ action: {
5346
+ label: "Checking payment…",
5347
+ disabled: true
5348
+ }
5349
+ };
5350
+ case "processing": return {
5351
+ title: "Your payment is still being processed",
5352
+ message: "Please don’t make another payment. You can check its status again in a moment.",
5353
+ action: { label: "Check payment status" }
5354
+ };
5355
+ case "paymentAvailable": return {
5356
+ title: "Your payment hasn’t been completed",
5357
+ message: "Your booking details are still saved. Continue to payment to finish your booking.",
5358
+ action: { label: "Continue to payment" }
5359
+ };
5360
+ case "retryAvailable": return {
5361
+ title: "Continue your booking",
5362
+ message: "You can return to payment. We’ll check your booking, any existing payment and availability before starting another checkout.",
5363
+ action: { label: "Return to payment" }
5364
+ };
5365
+ case "temporarilyUnavailable": return {
5366
+ title: "We couldn’t check your payment",
5367
+ message: "Please don’t make another payment yet. Try checking again in a moment.",
5368
+ action: { label: "Check payment status" }
5369
+ };
5370
+ case "assistanceRequired": return {
5371
+ title: "We still can’t confirm your payment",
5372
+ message: "Please don’t make another payment. Contact the organiser so they can check your booking.",
5373
+ action: { label: "Return to booking" }
5374
+ };
5375
+ }
5376
+ }
5377
+ function PaymentStatusStep(props) {
5378
+ const view = presentation(props.state);
5379
+ const onClick = props.state.kind === "paymentAvailable" ? props.onContinuePayment : props.state.kind === "retryAvailable" ? props.onReturnToPayment : props.state.kind === "assistanceRequired" ? props.onReturnToBooking : props.onCheck;
5380
+ return /* @__PURE__ */ jsx(TerminalPanel, {
5381
+ tone: "warning",
5382
+ className: "er-center",
5383
+ titleClassName: "er-title--lg er-message",
5384
+ icon: /* @__PURE__ */ jsx(StateIcon, {
5385
+ variant: "warning",
5386
+ children: /* @__PURE__ */ jsx(ClockIcon, {})
5387
+ }),
5388
+ title: view.title,
5389
+ body: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", {
5390
+ className: "er-text--muted er-message",
5391
+ children: view.message
5392
+ }), /* @__PURE__ */ jsx("span", {
5393
+ className: "rb-sr-only",
5394
+ "aria-live": "polite",
5395
+ "aria-atomic": "true",
5396
+ children: props.state.kind === "checking" ? "Checking payment…" : view.title
5397
+ })] }),
5398
+ actionsClassName: "er-button-group",
5399
+ actions: view.action ? /* @__PURE__ */ jsx("button", {
5400
+ type: "button",
5401
+ onClick,
5402
+ disabled: view.action.disabled,
5403
+ className: "er-btn er-btn--primary",
5404
+ children: /* @__PURE__ */ jsx("span", { children: view.action.label })
5405
+ }) : void 0
5406
+ });
5407
+ }
5408
+ //#endregion
5472
5409
  //#region ../blocks/src/system/runtime/nodes/event-registration/StepIndicator.tsx
5473
5410
  /**
5474
5411
  * Step indicator component for the event registration wizard.
@@ -5586,7 +5523,7 @@ const VerifyingTimeoutStep = ({ onCheckAgain, supportEmail, title = "We’re che
5586
5523
  title,
5587
5524
  body: /* @__PURE__ */ jsx("p", {
5588
5525
  className: "er-text--muted er-message",
5589
- children: message
5526
+ children: onCheckAgain ? message : "We still can’t confirm your payment. Please don’t make another payment. Contact the organiser so they can check your booking."
5590
5527
  }),
5591
5528
  actionsClassName: "er-button-group",
5592
5529
  actions: onCheckAgain ? /* @__PURE__ */ jsx("button", {
@@ -5594,11 +5531,7 @@ const VerifyingTimeoutStep = ({ onCheckAgain, supportEmail, title = "We’re che
5594
5531
  onClick: onCheckAgain,
5595
5532
  className: "er-btn er-btn--primary",
5596
5533
  children: "Check payment status"
5597
- }) : /* @__PURE__ */ jsx("a", {
5598
- href: "/portal",
5599
- className: "er-btn er-btn--primary",
5600
- children: "Check payment status"
5601
- }),
5534
+ }) : void 0,
5602
5535
  secondaryBody: supportEmail ? /* @__PURE__ */ jsxs("p", {
5603
5536
  className: "er-text--sm er-text--muted er-message",
5604
5537
  children: [
@@ -5699,6 +5632,13 @@ function EventRegistrationTerminalState(props) {
5699
5632
  message: wizard.verification.status === "timeout" ? void 0 : wizard.errorMessage,
5700
5633
  supportEmail: props.supportEmail
5701
5634
  });
5635
+ case "payment-status": return wizard.paymentStatusRecovery ? /* @__PURE__ */ jsx(PaymentStatusStep, {
5636
+ state: wizard.paymentStatusRecovery,
5637
+ onCheck: wizard.handleCheckPaymentStatus,
5638
+ onContinuePayment: wizard.handleContinueExistingPayment,
5639
+ onReturnToPayment: wizard.handleReturnToPayment,
5640
+ onReturnToBooking: wizard.handleReturnToBooking
5641
+ }) : null;
5702
5642
  case "success": return wizard.registrationResult ? /* @__PURE__ */ jsx(SuccessStep, {
5703
5643
  title: wizard.registrationResult.title,
5704
5644
  variant: wizard.registrationResult.variant,
@@ -5707,7 +5647,7 @@ function EventRegistrationTerminalState(props) {
5707
5647
  case "error": return /* @__PURE__ */ jsx(ErrorStep, {
5708
5648
  message: wizard.errorMessage,
5709
5649
  action: wizard.errorAction,
5710
- canJoinWaitlist: wizard.eligibility?.isFull && wizard.eligibility.canJoinWaitlist,
5650
+ canJoinWaitlist: wizard.eligibility?.availability.kind === "waitlist" && wizard.eligibility.registration.kind !== "pending_payment",
5711
5651
  onRetry: wizard.handleRetry
5712
5652
  });
5713
5653
  case "select-event":
@@ -5901,6 +5841,7 @@ function getWizardStepLabel(input) {
5901
5841
  case "redirecting":
5902
5842
  case "verifying-payment":
5903
5843
  case "verifying-timeout":
5844
+ case "payment-status":
5904
5845
  case "payment-failed": return "";
5905
5846
  }
5906
5847
  }