@riverbankcms/sdk 0.148.0 → 0.150.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 (250) hide show
  1. package/README.md +1 -1
  2. package/dist/EventEmbeddedCheckoutStep-D00Kc8Zj.mjs +285 -0
  3. package/dist/{PreviewEditorSidebar-DvEPG3Z2.mjs → PreviewEditorSidebar-7ZhmmnBB.mjs} +103 -66
  4. package/dist/{PreviewEditorUI-D6eb6fkx.mjs → PreviewEditorUI-BIJucTRd.mjs} +3 -6
  5. package/dist/{PreviewQueryProvider-5znTyJuz.mjs → PreviewQueryProvider-C0mndwHB.mjs} +3 -3
  6. package/dist/{SdkPreviewModeRuntime-DJ6utQ_R.mjs → SdkPreviewModeRuntime-tj45gmCX.mjs} +96 -67
  7. package/dist/{SiteChromeCustomizeContext-CFJYh6vo.mjs → SiteChromeCustomizeContext-BFOt0ss-.mjs} +4804 -4161
  8. package/dist/_dts/ai/src/contracts/proposals.d.ts +660 -0
  9. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +510 -0
  10. package/dist/_dts/api/src/billing.d.ts +2 -2
  11. package/dist/_dts/api/src/bookingConfig.d.ts +33 -48
  12. package/dist/_dts/api/src/bookingManagementEndpoints.d.ts +2 -6
  13. package/dist/_dts/api/src/bookingOperations.d.ts +5 -7
  14. package/dist/_dts/api/src/bookingRecords.d.ts +15 -11
  15. package/dist/_dts/api/src/bookingRefundOutcomes.d.ts +5 -5
  16. package/dist/_dts/api/src/clientErrorReporting.d.ts +11 -0
  17. package/dist/_dts/api/src/commercePortalEndpoints.d.ts +0 -2
  18. package/dist/_dts/api/src/common/cancellation.d.ts +23 -0
  19. package/dist/_dts/api/src/contentRuntime.d.ts +5 -1
  20. package/dist/_dts/api/src/customerMembershipOutcomes.d.ts +1 -1
  21. package/dist/_dts/api/src/endpoints.d.ts +2 -10
  22. package/dist/_dts/api/src/eventManagementContracts.d.ts +4 -7
  23. package/dist/_dts/api/src/footerCompositionContracts.d.ts +9 -0
  24. package/dist/_dts/api/src/footerSettings.d.ts +3 -3
  25. package/dist/_dts/api/src/index.d.ts +4 -4
  26. package/dist/_dts/api/src/navigation/bookingPortalLink.d.ts +13 -0
  27. package/dist/_dts/api/src/navigation.d.ts +1 -0
  28. package/dist/_dts/api/src/publicAppointmentBookingContracts.d.ts +2 -2
  29. package/dist/_dts/api/src/publicExperience/commerceEndpoints.d.ts +2 -29
  30. package/dist/_dts/api/src/publicExperience/eventEndpoints.d.ts +9 -173
  31. package/dist/_dts/api/src/request.d.ts +1 -1
  32. package/dist/_dts/api/src/sdk-event-upsert.d.ts +1 -1
  33. package/dist/_dts/api/src/sdkPullEventContracts.d.ts +1 -1
  34. package/dist/_dts/api/src/sdkThemeWire.d.ts +12 -0
  35. package/dist/_dts/api/src/siteManagementEndpoints.d.ts +3 -3
  36. package/dist/_dts/api/src/siteOperations.d.ts +6 -2
  37. package/dist/_dts/api/src/siteSettings.d.ts +2 -0
  38. package/dist/_dts/blocks/src/client/site-chrome.d.ts +1 -1
  39. package/dist/_dts/blocks/src/client.d.ts +2 -1
  40. package/dist/_dts/blocks/src/contracts/index.d.ts +1 -1
  41. package/dist/_dts/blocks/src/contracts/non-block-islands.d.ts +2 -2
  42. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +38 -10
  43. package/dist/_dts/blocks/src/portal/types.d.ts +9 -6
  44. package/dist/_dts/blocks/src/rendering/server.d.ts +1 -1
  45. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +24 -24
  46. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +1 -1
  47. package/dist/_dts/blocks/src/system/blocks/shared/bookingPaymentTerms.d.ts +84 -160
  48. package/dist/_dts/blocks/src/system/blocks/shop.d.ts +2 -2
  49. package/dist/_dts/blocks/src/system/blocks/shop.shared.d.ts +31 -4
  50. package/dist/_dts/blocks/src/system/blocks/site-footer-runtime.d.ts +30 -3
  51. package/dist/_dts/blocks/src/system/designCapabilities.d.ts +10 -1
  52. package/dist/_dts/blocks/src/system/runtime/api/index.d.ts +1 -1
  53. package/dist/_dts/blocks/src/system/runtime/api/types/appointments.d.ts +2 -2
  54. package/dist/_dts/blocks/src/system/runtime/api/types/core.d.ts +3 -1
  55. package/dist/_dts/blocks/src/system/runtime/api/types/events.d.ts +38 -18
  56. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +48 -6
  57. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +24 -3
  58. package/dist/_dts/blocks/src/system/runtime/hooks/useBookingNowIso.d.ts +8 -0
  59. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessEventRegistrationFlow.d.ts +1 -1
  60. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.server.d.ts +6 -0
  61. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +2 -1
  62. package/dist/_dts/blocks/src/system/runtime/nodes/commerce/shared/display.d.ts +9 -2
  63. package/dist/_dts/blocks/src/system/runtime/nodes/event-details/presentation.d.ts +14 -2
  64. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventEmbeddedCheckoutStep.d.ts +13 -0
  65. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationPayment.d.ts +15 -0
  66. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationWizardActions.d.ts +0 -1
  67. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/FullyBookedStep.d.ts +6 -0
  68. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/PaymentBlockingPanel.d.ts +3 -0
  69. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/PaymentSelectionStep.d.ts +4 -0
  70. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +15 -5
  71. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.d.ts +4 -2
  72. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/eventPaymentOptions.d.ts +3 -3
  73. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/paymentLocation.d.ts +3 -0
  74. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/paymentResponse.d.ts +72 -0
  75. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationCompletion.d.ts +66 -5
  76. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.d.ts +1 -4
  77. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationStateMachine.d.ts +13 -5
  78. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/sessionStorage.d.ts +11 -52
  79. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +19 -2
  80. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +7 -42
  81. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +7 -8
  82. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +94 -24
  83. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useGuestRegistrationRecovery.d.ts +30 -0
  84. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.d.ts +24 -12
  85. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useRegistrationAdmission.d.ts +18 -0
  86. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/wizardSteps.d.ts +6 -1
  87. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration.interactive.d.ts +1 -1
  88. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +24 -24
  89. package/dist/_dts/blocks/src/system/runtime/nodes/shop.interactive.d.ts +41 -6
  90. package/dist/_dts/blocks/src/system/runtime/shared/bookingPaymentOptions.d.ts +7 -2
  91. package/dist/_dts/blocks/src/system/runtime/shared/stripe/browserStripe.d.ts +8 -0
  92. package/dist/_dts/blocks/src/system/transforms/registry/eventCalendar.d.ts +8 -0
  93. package/dist/_dts/commerce/src/purchase/bookingPaymentSelection.d.ts +54 -0
  94. package/dist/_dts/commerce/src/purchase/checkoutCommitPlan.d.ts +58 -13
  95. package/dist/_dts/commerce/src/purchase/checkoutCopy.d.ts +10 -0
  96. package/dist/_dts/commerce/src/purchase/checkoutErrorCodes.d.ts +1 -1
  97. package/dist/_dts/commerce/src/purchase/checkoutPaymentPlanPlanning.d.ts +16 -21
  98. package/dist/_dts/commerce/src/purchase/checkoutPaymentTerms.d.ts +23 -40
  99. package/dist/_dts/commerce/src/purchase/checkoutPresentation.d.ts +49 -0
  100. package/dist/_dts/commerce/src/purchase/checkoutSessionMetadata.d.ts +6 -6
  101. package/dist/_dts/commerce/src/purchase/checkoutStripeFinalizationPlanning.d.ts +5 -1
  102. package/dist/_dts/commerce/src/purchase/checkoutStripeSessionPlanning.d.ts +5 -4
  103. package/dist/_dts/commerce/src/purchase/event-registration-errors.d.ts +9 -0
  104. package/dist/_dts/commerce/src/purchase/index.d.ts +3 -3
  105. package/dist/_dts/commerce/src/purchase/membershipOrderItemMetadata.d.ts +4 -0
  106. package/dist/_dts/commerce/src/purchase/oneTimeCheckoutPlanning.d.ts +9 -4
  107. package/dist/_dts/commerce/src/purchase/orderInstalmentClosure.d.ts +13 -1
  108. package/dist/_dts/commerce/src/purchase/orderInstalmentLifecycle.d.ts +5 -0
  109. package/dist/_dts/commerce/src/purchase/orderItemMetadata.d.ts +4 -1
  110. package/dist/_dts/commerce/src/purchase/orderTypes.d.ts +7 -1
  111. package/dist/_dts/commerce/src/purchase/refundRailSplit.d.ts +1 -1
  112. package/dist/_dts/commerce/src/purchase/stateTransitions.d.ts +2 -2
  113. package/dist/_dts/commerce/src/purchase/stripeCheckoutReturnUrl.d.ts +9 -2
  114. package/dist/_dts/commerce/src/purchase/stripeMinimumCharge.d.ts +6 -0
  115. package/dist/_dts/commerce/src/purchase/types.d.ts +7 -7
  116. package/dist/_dts/content-editor/src/FooterComposerOverview.d.ts +3 -2
  117. package/dist/_dts/content-editor/src/controller/editLeaseHandover.d.ts +30 -0
  118. package/dist/_dts/content-editor/src/footerPreviewRuntime.d.ts +6 -1
  119. package/dist/_dts/content-editor/src/lib/browserStorage.d.ts +5 -0
  120. package/dist/_dts/content-editor/src/useFooterComposerState.d.ts +7 -0
  121. package/dist/_dts/core/src/booking-flexible-balance.d.ts +2 -2
  122. package/dist/_dts/core/src/booking-instalment-deposit.d.ts +20 -0
  123. package/dist/_dts/core/src/booking-live-payment-terms.d.ts +72 -0
  124. package/dist/_dts/core/src/booking-payment-options.d.ts +24 -0
  125. package/dist/_dts/core/src/booking-payment-schemas.d.ts +522 -0
  126. package/dist/_dts/core/src/branded.d.ts +3 -0
  127. package/dist/_dts/core/src/edit-lease.d.ts +5 -0
  128. package/dist/_dts/core/src/index.d.ts +4 -0
  129. package/dist/_dts/core/src/patch/index.d.ts +30 -0
  130. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +326 -36
  131. package/dist/_dts/media-editor/src/MediaPickerDialog.d.ts +3 -1
  132. package/dist/_dts/media-editor/src/MediaPickerMiniDialog.d.ts +10 -0
  133. package/dist/_dts/media-editor/src/index.d.ts +2 -0
  134. package/dist/_dts/preview-next/src/client/preview/PreviewEntryControllerProvider.d.ts +0 -1
  135. package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +1 -1
  136. package/dist/_dts/preview-next/src/client/previewAuthStore.d.ts +7 -0
  137. package/dist/_dts/preview-next/src/constants.d.ts +4 -0
  138. package/dist/_dts/preview-next/src/keepAlive.d.ts +1 -0
  139. package/dist/_dts/sdk/src/bookings/index.d.ts +2 -1
  140. package/dist/_dts/sdk/src/bookings/types.d.ts +1 -1
  141. package/dist/_dts/sdk/src/rendering/islands/types.d.ts +2 -12
  142. package/dist/_dts/sdk/src/version.d.ts +1 -1
  143. package/dist/_dts/theme-core/src/blocks/index.d.ts +1 -0
  144. package/dist/_dts/theme-core/src/blocks/opaquePanels.d.ts +147 -0
  145. package/dist/_dts/theme-core/src/boxBackgroundSchema.d.ts +22 -0
  146. package/dist/_dts/theme-core/src/buttons/buttonSurfaceContext.d.ts +22 -0
  147. package/dist/_dts/theme-core/src/cards/roles.d.ts +12 -0
  148. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  149. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +198 -0
  150. package/dist/_dts/theme-core/src/palette/utils/index.d.ts +2 -0
  151. package/dist/_dts/theme-core/src/palette/utils/scrimContrast.d.ts +22 -0
  152. package/dist/_dts/theme-core/src/runtime/bridgeColourTokens.d.ts +10 -0
  153. package/dist/_dts/theme-core/src/schema.d.ts +330 -0
  154. package/dist/_dts/theme-core/src/site-styles/appearancePresetCatalog.d.ts +0 -51
  155. package/dist/_dts/theme-core/src/site-styles/blockAppearancePresets.d.ts +0 -51
  156. package/dist/_dts/theme-core/src/site-styles/lookResolution.d.ts +11 -0
  157. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorCapabilities.d.ts +7 -1
  158. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorModel.d.ts +7 -1
  159. package/dist/_dts/theme-core/src/site-styles/pageDesignIntent.d.ts +0 -5
  160. package/dist/_dts/theme-core/src/site-styles/previewRuntimeCapabilities.d.ts +12 -6
  161. package/dist/_dts/theme-core/src/site-styles/runtimeCompiler.d.ts +7 -4
  162. package/dist/_dts/theme-core/src/site-styles/sectionSurfaceBackgrounds.d.ts +65 -0
  163. package/dist/_dts/theme-core/src/site-styles/types.d.ts +17 -0
  164. package/dist/_dts/theme-core/src/surfaces/opaqueSurface.d.ts +43 -0
  165. package/dist/_dts/ui/src/color-picker.d.ts +1 -1
  166. package/dist/_dts/ui/src/palette/useHexDraft.d.ts +7 -0
  167. package/dist/client/{BookingMailingListNote-CzRt2dF_.mjs → BookingMailingListNote-CYHOufsI.mjs} +33 -124
  168. package/dist/client/{CheckboxChoice-B33MqoUo.mjs → CheckboxChoice-M-3HycLw.mjs} +1 -1
  169. package/dist/client/{DemoBookingPortalAccess-mxqwap3f.mjs → DemoBookingPortalAccess-Cvr9O_ca.mjs} +2 -2
  170. package/dist/client/{EventCombined.client-CDb11igO.mjs → EventCombined.client-m5UZIobP.mjs} +7 -7
  171. package/dist/client/EventEmbeddedCheckoutStep-DL6zFAkA.mjs +289 -0
  172. package/dist/client/{EventListing.client-EgYhzKPa.mjs → EventListing.client-DeMfCM04.mjs} +2 -2
  173. package/dist/client/{LeafletStaticMap-gtmtaA2b.mjs → LeafletStaticMap-CvZ3gOdD.mjs} +1 -1
  174. package/dist/client/{checkoutUrls-CXhko2Rv.mjs → RichText-BtfNvQV6.mjs} +2 -29
  175. package/dist/client/{TerminalPanel-DvNUP5ot.mjs → TerminalPanel-CUCNEwlH.mjs} +2 -18
  176. package/dist/client/{ThemedButton-C7CeUJjW.mjs → ThemedButton-C7WhXuRl.mjs} +2 -2
  177. package/dist/client/{accordion-kjvt49rA.mjs → accordion-cQm5mjqs.mjs} +1 -1
  178. package/dist/client/{bookingForm-pbRZordh.mjs → bookingForm-BVPJE5RQ.mjs} +15 -11
  179. package/dist/client/bookings.mjs +2860 -1222
  180. package/dist/client/{carousel-f4Zbmicv.mjs → carousel-3vgnY7LC.mjs} +2 -2
  181. package/dist/client/{carousel.client-A2lG-9od.mjs → carousel.client-CISsdZvg.mjs} +1 -1
  182. package/dist/client/{cart-DO20An50.mjs → cart-BGK4oOjm.mjs} +3 -3
  183. package/dist/client/{checkout-Du5amDR_.mjs → checkout-GsBLUNJD.mjs} +3 -3
  184. package/dist/client/checkoutUrls-ChxwaNtO.mjs +28 -0
  185. package/dist/client/client.mjs +3324 -2053
  186. package/dist/client/{colorStyles-j5U9v5JA.mjs → colorStyles-D-32xHZh.mjs} +2 -30
  187. package/dist/client/{context-eW97E945.mjs → context-D_8s12Fp.mjs} +1 -1
  188. package/dist/client/{dates-jPCirRea.mjs → dates-DeJQbQ9T.mjs} +2 -2
  189. package/dist/client/{eventCalendar-C2M-iE4j.mjs → eventCalendar-cmucD4NR.mjs} +10 -10
  190. package/dist/client/{eventCombined-W6ns3LHg.mjs → eventCombined-ku3E9FTl.mjs} +4 -4
  191. package/dist/client/{eventDetails-CR-GT0f0.mjs → eventDetails-CyKZ61sF.mjs} +5 -5
  192. package/dist/client/{eventListing-BK8AfYZz.mjs → eventListing-B7yxGIhX.mjs} +4 -4
  193. package/dist/client/{eventRegistration-BJSd4KUu.mjs → eventRegistration-BoXfSFgZ.mjs} +1455 -1184
  194. package/dist/client/{form-BvpfbQaT.mjs → form-CNQOWhJo.mjs} +5 -5
  195. package/dist/client/{headerSection-I5sUQ8x-.mjs → headerSection-DahaBJGQ.mjs} +3 -3
  196. package/dist/client/hooks.mjs +283 -193
  197. package/dist/client/{locationMap-BLvwf9_f.mjs → locationMap-BoDjqYlW.mjs} +4 -4
  198. package/dist/client/{media-Cl38iOMI.mjs → media-BFY0y2rb.mjs} +2 -1
  199. package/dist/client/{newsletterForm-DrQ2weE8.mjs → newsletterForm-DtzXDklw.mjs} +4 -4
  200. package/dist/client/{pricing-BES87mrR.mjs → pricing-DXEh2Lhh.mjs} +1 -1
  201. package/dist/client/{productDetail-Url9jcc5.mjs → productDetail-D7cPa-00.mjs} +3 -3
  202. package/dist/client/{productList-DE7ngEZJ.mjs → productList-D1XMap66.mjs} +3 -3
  203. package/dist/client/{propsDecoder-D1OtGXDV.mjs → propsDecoder-DiIeBopB.mjs} +913 -322
  204. package/dist/client/rendering/client.mjs +3913 -2260
  205. package/dist/client/rendering/islands.mjs +19 -19
  206. package/dist/client/rendering.mjs +3302 -2013
  207. package/dist/client/result-DRW7JWXz.mjs +30 -0
  208. package/dist/client/{routeIslandApiConfig-BG9Y-SHg.mjs → routeIslandApiConfig-wzR4lKAY.mjs} +1 -1
  209. package/dist/client/scrollToBlock-C86BYkyV.mjs +17 -0
  210. package/dist/client/{shop-CJkuySJ3.mjs → shop-CYT5P-S5.mjs} +13 -14
  211. package/dist/client/{shop-commerce-store-Bxm75Pg6.mjs → shop-commerce-store-wKhsJQxI.mjs} +1 -1
  212. package/dist/client/{shop-commerce-4rKj5n9h.mjs → shop-commerce-ypBu_dx4.mjs} +9 -9
  213. package/dist/client/{shopShared-DpoQDWfz.mjs → shopShared-DFGhYrf2.mjs} +52 -6
  214. package/dist/client/{useEventFilters-D8HliSqR.mjs → useEventFilters-blSbN5o7.mjs} +8 -8
  215. package/dist/preview-next/before-render.mjs +15 -22
  216. package/dist/preview-next/client/runtime.mjs +4 -4
  217. package/dist/preview-next/constants.mjs +5 -1
  218. package/dist/preview-next/index.mjs +5 -1
  219. package/dist/preview-next/keepAlive.mjs +149 -82
  220. package/dist/preview-next/middleware.mjs +15 -22
  221. package/dist/{previewAuthStore-BXXF3hhW.mjs → previewAuthStore-BBmV7UsW.mjs} +49 -29
  222. package/dist/{sdk-runtime-C2a05WjL.mjs → sdk-runtime-2p8uQk1V.mjs} +2 -2
  223. package/dist/{separator-CuNhBAhS.mjs → separator-Cvvzk0Oo.mjs} +1 -1
  224. package/dist/server/components.mjs +4041 -3704
  225. package/dist/server/config-validation.mjs +345 -255
  226. package/dist/server/config.mjs +345 -255
  227. package/dist/server/data.mjs +283 -193
  228. package/dist/server/index.mjs +20 -27
  229. package/dist/server/next.mjs +788 -469
  230. package/dist/server/page-converter.mjs +245 -121
  231. package/dist/server/prebuild.mjs +1 -1
  232. package/dist/server/rendering/server.mjs +4018 -3690
  233. package/dist/server/rendering.mjs +4044 -3707
  234. package/dist/server/routing.mjs +314 -190
  235. package/dist/server/server.mjs +290 -218
  236. package/dist/server/theme-bridge.mjs +1122 -310
  237. package/dist/server/theme.mjs +126 -116
  238. package/dist/spinner-CDo6AkDz.mjs +658 -0
  239. package/dist/styles/index.css +75 -212
  240. package/package.json +1 -1
  241. package/dist/_dts/api/src/bookingPaymentTermSchemas.d.ts +0 -123
  242. package/dist/_dts/billing/src/checkout/platform.d.ts +0 -77
  243. package/dist/_dts/billing/src/config/env.d.ts +0 -29
  244. package/dist/_dts/billing/src/stripe/client.d.ts +0 -29
  245. package/dist/_dts/blocks/src/system/runtime/hooks/usePaymentVerification.d.ts +0 -63
  246. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventCheckoutHandoffRestore.d.ts +0 -9
  247. package/dist/_dts/commerce/src/purchase/checkoutPaymentTermPlanning.d.ts +0 -10
  248. package/dist/_dts/commerce/src/purchase/checkoutValidatedItemPlanning.d.ts +0 -55
  249. package/dist/_dts/commerce/src/purchase/orderInstalmentPayoff.d.ts +0 -24
  250. package/dist/client/urlParams-D71VXFGx.mjs +0 -47
@@ -1,39 +1,25 @@
1
1
  "use client";
2
- import { a as buildBookingPaymentSelectionViewModel, i as usePolling, n as BookingConsentAgreement, o as resolveBookingPaymentDueNowCents, r as StepIndicator$1, s as formatPrice, t as BookingMailingListNote } from "./BookingMailingListNote-CzRt2dF_.mjs";
3
- import { C as resolveDiscountContext, Cn as formatCurrencyAmount, D as WIZARD_STEPS, E as toRegistrationRequestSubject, M as resolveEventRegistrationScopedEvents, O as getStepNumber, Ot as getRegistrationSuccessCopy, S as findPreSelectedEvent, Sn as publicEventPricingType, T as resolveRegistrationTarget, Wt as assertNever, _ as buildEventRegistrationDisplayFromScopedEvents, b as toWholeSeriesRegistrationEvents, bn as publicEventPricingOptions, en as resolveBookingSignupOffer, fn as toBookingTimeZone, ft as groupPublicEventsByRun, in as formatBookingDateTime, k as getWizardStepsForContext, mn as toScheduledBookingDateTime, nn as formatBookingDate, on as formatBookingTime, t as defineIslandPropsDecoder, v as getRegistrationStartAt, vn as publicEventPricingCurrency, w as resolveEventRequiresPayment, wn as parseCurrencyCode, x as findEventByOccurrenceId, xn as publicEventPricingStrategy, y as toRegistrationEvents } from "./propsDecoder-D1OtGXDV.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-BES87mrR.mjs";
5
- import { n as useBlockApi, r as BlockApiError, t as BlockApiProvider } from "./context-eW97E945.mjs";
2
+ import { a as buildBookingPaymentSelectionViewModel, c as useBookingNowIso, i as usePolling, n as BookingConsentAgreement, o as resolveBookingPaymentDueNowCents, r as StepIndicator$1, s as formatPrice, t as BookingMailingListNote } from "./BookingMailingListNote-CYHOufsI.mjs";
3
+ import { $t as parseEventRegistrationErrorReason, An as publicEventPricingOptions, C as resolveDiscountContext, D as getRegistrationStepPresentation, E as toRegistrationRequestSubject, M as resolveEventRegistrationScopedEvents, Mn as publicEventPricingType, Nn as formatCurrencyAmount, O as getStepNumber, On as publicEventPricingCurrency, Ot as getRegistrationSuccessCopy, Qt as eventRegistrationErrors, S as findPreSelectedEvent, Sn as toScheduledBookingDateTime, T as resolveRegistrationTarget, Wt as assertNever, _ as buildEventRegistrationDisplayFromScopedEvents, b as toWholeSeriesRegistrationEvents, bn as toBookingTimeZone, cn as resolveBookingSignupOffer, en as getStripeMinimumChargeRequirement, fn as formatBookingDateTime, ft as groupPublicEventsByRun, jn as publicEventPricingStrategy, k as getWizardStepsForContext, mn as formatBookingTime, nn as planFlexibleBalanceCreditFirst, t as defineIslandPropsDecoder, tn as planCheckoutPaymentFlow, un as formatBookingDate, v as getRegistrationStartAt, w as resolveEventRequiresPayment, x as findEventByOccurrenceId, y as toRegistrationEvents } from "./propsDecoder-DiIeBopB.mjs";
4
+ import { i as SpinnerNode, n as ThemedButtonLink, t as ThemedButton } from "./ThemedButton-C7WhXuRl.mjs";
5
+ 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-DXEh2Lhh.mjs";
6
+ import { n as useBlockApi, r as BlockApiError, t as BlockApiProvider } from "./context-D_8s12Fp.mjs";
6
7
  import { n as isSpamProtectionEnabled, r as AltchaWidget, t as getChallengeUrl } from "./src-B833rYRr.mjs";
7
- import { a as requestDemoPortalMagicLink, f as storeDemoBookingPass, i as buildPortalCallbackUrl, o as DiscountCodeDisclosure, r as CUSTOMER_PORTAL_PATH, t as BookingConfirmation } from "./DemoBookingPortalAccess-mxqwap3f.mjs";
8
- import { a as useDiscountCodeUi, i as CheckIcon, n as StateIcon, r as scrollToBlock, t as TerminalPanel } from "./TerminalPanel-DvNUP5ot.mjs";
9
- import { i as SpinnerNode, n as ThemedButtonLink, t as ThemedButton } from "./ThemedButton-C7CeUJjW.mjs";
8
+ import { a as requestDemoPortalMagicLink, f as storeDemoBookingPass, i as buildPortalCallbackUrl, o as DiscountCodeDisclosure, r as CUSTOMER_PORTAL_PATH, t as BookingConfirmation } from "./DemoBookingPortalAccess-Cvr9O_ca.mjs";
9
+ import { i as useDiscountCodeUi, n as StateIcon, r as CheckIcon, t as TerminalPanel } from "./TerminalPanel-CUCNEwlH.mjs";
10
10
  import { t as FeedbackMessage } from "./FeedbackMessage-CtLvvS_d.mjs";
11
11
  import { i as getFieldValidationControlProps, r as FieldValidationMessage } from "./mailingListSignupCopy-DZSHZrm6.mjs";
12
- import { t as buildCheckoutReturnUrls } from "./checkoutUrls-CXhko2Rv.mjs";
13
- import { t as CheckboxChoice } from "./CheckboxChoice-B33MqoUo.mjs";
12
+ import { t as CheckboxChoice } from "./CheckboxChoice-M-3HycLw.mjs";
14
13
  import { t as createBodyThemePortalRoot } from "./themePortalRoot-AGs4cE7t.mjs";
15
- import { n as consumeSearchParams } from "./urlParams-D71VXFGx.mjs";
16
14
  import { t as CrossIcon } from "./CrossIcon-Dn1KRKW5.mjs";
17
15
  import { t as mountReactIsland } from "./reactMount-SY8AVRof.mjs";
18
- import { t as buildRouteIslandApiConfig } from "./routeIslandApiConfig-BG9Y-SHg.mjs";
16
+ import { t as buildRouteIslandApiConfig } from "./routeIslandApiConfig-wzR4lKAY.mjs";
19
17
  import { t as createLucideIcon } from "./createLucideIcon-PaLVMov4.mjs";
20
18
  import { t as ModalShell } from "./ModalShell-CqkBvBIC.mjs";
21
- import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
19
+ import React, { Suspense, lazy, useCallback, useEffect, useId, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
22
20
  import { z } from "zod";
23
21
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
24
22
  import { createPortal } from "react-dom";
25
- //#region ../core/src/money-input.ts
26
- function unsafeAsNonNegativeCents(value) {
27
- return value;
28
- }
29
- /**
30
- * Normalizes a derived amount to non-negative whole cents. This intentionally
31
- * clamps negatives to zero; use parseNonNegativeCents for validation.
32
- */
33
- function toNonNegativeCents(value) {
34
- return unsafeAsNonNegativeCents(Math.max(0, Math.trunc(value)));
35
- }
36
- //#endregion
37
23
  //#region ../core/src/uuid.ts
38
24
  function generateUuidV4(cryptoSource = globalThis.crypto) {
39
25
  if (typeof cryptoSource.randomUUID === "function") return cryptoSource.randomUUID();
@@ -188,6 +174,161 @@ const Wallet = createLucideIcon("wallet", [["path", {
188
174
  key: "xoc0q4"
189
175
  }]]);
190
176
  //#endregion
177
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationCompletion.ts
178
+ /** One admission decision for navigation, labels and completion. Email recovery
179
+ * permits navigation only; submission remains the owner of payment recovery. */
180
+ function resolveRegistrationAdmission(input) {
181
+ if (input.hasWaitlistClaim) return { kind: "resume" };
182
+ if (input.eligibilityLoading) return { kind: "checking" };
183
+ if (input.eligibilityError) return {
184
+ kind: "closed",
185
+ reason: "optionsUnavailable",
186
+ message: input.eligibilityError
187
+ };
188
+ if (!input.eligibility) return { kind: "checking" };
189
+ const { registration, availability } = input.eligibility;
190
+ if (registration.kind === "pending_payment") return { kind: "resume" };
191
+ if (registration.kind === "waitlisted") return {
192
+ kind: "closed",
193
+ reason: "alreadyWaitlisted",
194
+ message: "You're already on the waitlist for this event."
195
+ };
196
+ return admissionFromAvailability(availability, input.isAuthenticated);
197
+ }
198
+ function admissionFromAvailability(availability, isAuthenticated) {
199
+ switch (availability.kind) {
200
+ case "available": return { kind: "open" };
201
+ case "waitlist": return {
202
+ kind: "waitlist",
203
+ recovery: isAuthenticated ? "none" : "byEmail"
204
+ };
205
+ case "unavailable": switch (availability.reason) {
206
+ case "full": return {
207
+ kind: "fullyBooked",
208
+ recovery: isAuthenticated ? "none" : "byEmail"
209
+ };
210
+ case "paymentUnavailable": return {
211
+ kind: "closed",
212
+ reason: "unavailable",
213
+ message: "Payment options are not available for this event."
214
+ };
215
+ case "selectionRequired": return { kind: "open" };
216
+ case "bookingClosed": return {
217
+ kind: "closed",
218
+ reason: "unavailable",
219
+ message: "Registration is not available for this event."
220
+ };
221
+ }
222
+ }
223
+ }
224
+ function stepAfterTickets(admission) {
225
+ switch (admission.kind) {
226
+ case "open":
227
+ case "resume":
228
+ case "waitlist": return "enter-details";
229
+ case "fullyBooked": return "fully-booked";
230
+ case "checking":
231
+ case "closed": return "select-tickets";
232
+ }
233
+ }
234
+ function resolveRegistrationAvailabilityBlocker(admission) {
235
+ switch (admission.kind) {
236
+ case "open":
237
+ case "resume":
238
+ case "waitlist": return null;
239
+ case "fullyBooked": return admission.recovery === "none" ? {
240
+ kind: "unavailable",
241
+ message: "This event is fully booked."
242
+ } : null;
243
+ case "checking": return {
244
+ kind: "pending",
245
+ message: "Checking availability..."
246
+ };
247
+ case "closed": return {
248
+ kind: "unavailable",
249
+ message: admission.message
250
+ };
251
+ }
252
+ }
253
+ function resolvePricingSelectionBlocker(input) {
254
+ if (input.selectionRequired && !input.hasValidSelection) return "Choose at least one available ticket to continue.";
255
+ return input.slidingScaleSelectionMessage;
256
+ }
257
+ /** Submit owns fresh admission. Open quotes do not depend on an email lookup. */
258
+ function resolveGuestNextStepCheck(admission, request) {
259
+ switch (admission.kind) {
260
+ case "open":
261
+ case "resume": return {
262
+ kind: "skip",
263
+ outcome: { kind: "continue" }
264
+ };
265
+ case "checking": return {
266
+ kind: "blocked",
267
+ message: "Checking availability..."
268
+ };
269
+ case "closed": return {
270
+ kind: "blocked",
271
+ message: admission.message
272
+ };
273
+ case "fullyBooked":
274
+ if (admission.recovery === "none") return {
275
+ kind: "blocked",
276
+ message: "This event is fully booked."
277
+ };
278
+ return recoveryCheckRequest(request);
279
+ case "waitlist": return admission.recovery === "none" ? {
280
+ kind: "skip",
281
+ outcome: { kind: "waitlist" }
282
+ } : recoveryCheckRequest(request);
283
+ }
284
+ }
285
+ function recoveryCheckRequest(request) {
286
+ return request ? {
287
+ kind: "check",
288
+ request
289
+ } : {
290
+ kind: "blocked",
291
+ message: "Choose an event before checking your booking."
292
+ };
293
+ }
294
+ function resolveRegistrationTicketContinuation(input) {
295
+ if (input.isCheckingExistingRegistration) return {
296
+ kind: "blocked",
297
+ label: "Checking registration...",
298
+ pending: true,
299
+ message: "Your booking options are being checked."
300
+ };
301
+ if (input.ticketSelectionRequired && !input.hasValidTicketSelection) return {
302
+ kind: "blocked",
303
+ label: "Choose a ticket",
304
+ pending: false,
305
+ message: "Choose at least one available ticket to continue."
306
+ };
307
+ switch (input.admission.kind) {
308
+ case "checking": return {
309
+ kind: "blocked",
310
+ label: "Checking availability...",
311
+ pending: true,
312
+ message: "Your booking options are being checked."
313
+ };
314
+ case "closed": return {
315
+ kind: "blocked",
316
+ label: input.admission.reason === "alreadyWaitlisted" ? "Already on waitlist" : input.admission.reason === "optionsUnavailable" ? "Availability unavailable" : "Registration unavailable",
317
+ pending: false,
318
+ message: input.admission.message
319
+ };
320
+ case "open":
321
+ case "resume":
322
+ case "waitlist":
323
+ case "fullyBooked": return {
324
+ kind: "advance",
325
+ admission: input.admission,
326
+ label: "Continue",
327
+ pending: false
328
+ };
329
+ }
330
+ }
331
+ //#endregion
191
332
  //#region ../blocks/src/system/runtime/nodes/event-registration/eventPaymentOptions.ts
192
333
  function getApprovedDirectPaymentCollectionOptionKinds(paymentOptions) {
193
334
  return paymentOptions.flatMap((option) => {
@@ -212,6 +353,7 @@ function buildEventRegistrationPaymentOptionsViewModel(input) {
212
353
  priceAmount: input.totalPriceCents,
213
354
  priceCurrency: input.currency,
214
355
  bookingStartAt: input.startsAt,
356
+ nowIso: input.nowIso,
215
357
  locale: input.locale
216
358
  });
217
359
  return {
@@ -219,7 +361,7 @@ function buildEventRegistrationPaymentOptionsViewModel(input) {
219
361
  sectionTitle: selection.sectionTitle,
220
362
  options: selection.options.flatMap((option, index) => {
221
363
  const sourceOption = approvedOptions[index];
222
- return sourceOption ? [toEventDirectPaymentOption(option, sourceOption, selection.mode, input.totalPriceCents, input.startsAt)] : [];
364
+ return sourceOption ? [toEventDirectPaymentOption(option, sourceOption, selection.mode, input.totalPriceCents)] : [];
223
365
  })
224
366
  };
225
367
  }
@@ -227,11 +369,10 @@ function selectApprovedEventDirectPaymentOptions(approvedPaymentOptions, payment
227
369
  const approvedKinds = new Set(getApprovedDirectPaymentCollectionOptionKinds(approvedPaymentOptions));
228
370
  return paymentTerms.allowedOptions.filter((option) => approvedKinds.has(option.kind));
229
371
  }
230
- function toEventDirectPaymentOption(option, sourceOption, mode, totalCents, startsAt) {
372
+ function toEventDirectPaymentOption(option, sourceOption, mode, totalCents) {
231
373
  const configuredPaymentDueNowCents = resolveBookingPaymentDueNowCents({
232
374
  option: sourceOption,
233
- totalCents,
234
- bookingStartAt: startsAt
375
+ totalCents
235
376
  });
236
377
  switch (option.kind) {
237
378
  case "upfront": return {
@@ -287,9 +428,6 @@ function resolveEventRegistrationDirectPaymentSelection(input) {
287
428
  }
288
429
  //#endregion
289
430
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationStateMachine.ts
290
- function hasBlockingExistingRegistration(input) {
291
- return !input.hasWaitlistClaim && input.isAuthenticated && input.registration.kind === "waitlisted";
292
- }
293
431
  function createInitialRegistrationFlowState(hasPreSelectedContext) {
294
432
  return {
295
433
  step: hasPreSelectedContext ? "select-tickets" : "select-event",
@@ -365,8 +503,7 @@ function isSelectionValid(input) {
365
503
  function normalizeRegistrationFlowState(state) {
366
504
  const hasPass = Boolean(state.eligibility?.eligiblePasses.length);
367
505
  const hasMembership = Boolean(state.eligibility?.eligibleMemberships.length);
368
- let nextPassOptOut = state.passOptOut;
369
- if (nextPassOptOut && (state.step === "select-tickets" || state.ticketsCount !== 1 || !hasPass || hasMembership)) nextPassOptOut = false;
506
+ const nextPassOptOut = state.passOptOut && !(state.step === "select-tickets" || state.ticketsCount !== 1 || !hasPass || hasMembership);
370
507
  const autoSelection = computeAutoSelection({
371
508
  isAuthenticated: state.isAuthenticated,
372
509
  eligibility: state.eligibility,
@@ -391,8 +528,27 @@ function normalizeRegistrationFlowState(state) {
391
528
  };
392
529
  }
393
530
  function registrationFlowReducer(state, action) {
531
+ if ([
532
+ "payment",
533
+ "submitting",
534
+ "verifying-payment",
535
+ "verifying-timeout",
536
+ "payment-status"
537
+ ].includes(state.step) && action.type !== "set-step") return state;
394
538
  switch (action.type) {
395
- case "set-step": return normalizeRegistrationFlowState({
539
+ case "admission-updated":
540
+ case "tickets-completed":
541
+ case "recovery-checked": return reduceRegistrationAdmission(state, action);
542
+ case "set-step": return [
543
+ "payment",
544
+ "submitting",
545
+ "verifying-payment",
546
+ "verifying-timeout",
547
+ "payment-status"
548
+ ].includes(action.step) ? {
549
+ ...state,
550
+ step: action.step
551
+ } : normalizeRegistrationFlowState({
396
552
  ...state,
397
553
  step: action.step
398
554
  });
@@ -408,18 +564,8 @@ function registrationFlowReducer(state, action) {
408
564
  ...state,
409
565
  eligibility: action.eligibility
410
566
  });
411
- case "set-manual-payment-selection": return normalizeRegistrationFlowState({
412
- ...state,
413
- paymentSelection: normalizePaymentSelection(action.selection),
414
- selectionSource: "manual",
415
- passOptOut: action.selection.method === "stripe" ? true : state.passOptOut
416
- });
417
- case "use-card-instead-of-pass": return normalizeRegistrationFlowState({
418
- ...state,
419
- passOptOut: true,
420
- paymentSelection: computeDirectPaymentSelection(state.eligibility),
421
- selectionSource: "manual"
422
- });
567
+ case "set-manual-payment-selection":
568
+ case "use-card-instead-of-pass": return reduceRegistrationPaymentChoice(state, action);
423
569
  case "event-changed": return normalizeRegistrationFlowState({
424
570
  ...state,
425
571
  step: action.hasPreSelectedContext ? "select-tickets" : "select-event",
@@ -429,7 +575,6 @@ function registrationFlowReducer(state, action) {
429
575
  eligibility: null
430
576
  });
431
577
  case "reset": return createInitialRegistrationFlowState(action.hasPreSelectedContext);
432
- default: return state;
433
578
  }
434
579
  }
435
580
  function isEntitlementSelection(selection) {
@@ -439,6 +584,38 @@ function requiresPaymentStep(params) {
439
584
  if (!params.eventRequiresPayment) return false;
440
585
  return !isEntitlementSelection(params.paymentSelection);
441
586
  }
587
+ function reduceRegistrationPaymentChoice(state, action) {
588
+ switch (action.type) {
589
+ case "set-manual-payment-selection": return normalizeRegistrationFlowState({
590
+ ...state,
591
+ paymentSelection: normalizePaymentSelection(action.selection),
592
+ selectionSource: "manual",
593
+ passOptOut: action.selection.method === "stripe" ? true : state.passOptOut
594
+ });
595
+ case "use-card-instead-of-pass": return normalizeRegistrationFlowState({
596
+ ...state,
597
+ passOptOut: true,
598
+ paymentSelection: computeDirectPaymentSelection(state.eligibility),
599
+ selectionSource: "manual"
600
+ });
601
+ }
602
+ }
603
+ function reduceRegistrationAdmission(state, action) {
604
+ switch (action.type) {
605
+ case "admission-updated": return state.step === "fully-booked" && action.admission.kind !== "checking" ? normalizeRegistrationFlowState({
606
+ ...state,
607
+ step: stepAfterTickets(action.admission)
608
+ }) : state;
609
+ case "tickets-completed": return state.step === "select-tickets" ? normalizeRegistrationFlowState({
610
+ ...state,
611
+ step: stepAfterTickets(action.admission)
612
+ }) : state;
613
+ case "recovery-checked": return state.step === "fully-booked" && action.outcome.kind !== "unavailable" ? normalizeRegistrationFlowState({
614
+ ...state,
615
+ step: "enter-details"
616
+ }) : state;
617
+ }
618
+ }
442
619
  //#endregion
443
620
  //#region ../blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.ts
444
621
  function isEventRegistrationAvailable(event) {
@@ -780,16 +957,66 @@ const EventSelectionStep = ({ events, selectedEvent, onSelect, showVenue, showCa
780
957
  });
781
958
  };
782
959
  //#endregion
960
+ //#region ../blocks/src/system/runtime/nodes/event-registration/useRegistrationAdmission.ts
961
+ /** Owns admission and its navigation commands; renderers receive decisions. */
962
+ function useRegistrationAdmission(input) {
963
+ const { eligibility, eligibilityError, eligibilityLoading, hasWaitlistClaim, isAuthenticated, dispatchFlow } = input;
964
+ const admission = useMemo(() => resolveRegistrationAdmission({
965
+ eligibility,
966
+ eligibilityError,
967
+ eligibilityLoading,
968
+ hasWaitlistClaim,
969
+ isAuthenticated
970
+ }), [
971
+ eligibility,
972
+ eligibilityError,
973
+ eligibilityLoading,
974
+ hasWaitlistClaim,
975
+ isAuthenticated
976
+ ]);
977
+ useEffect(() => {
978
+ dispatchFlow({
979
+ type: "admission-updated",
980
+ admission
981
+ });
982
+ }, [admission, dispatchFlow]);
983
+ const ticketContinuation = resolveRegistrationTicketContinuation({
984
+ admission,
985
+ ticketSelectionRequired: input.ticketSelectionRequired,
986
+ hasValidTicketSelection: input.hasValidTicketSelection,
987
+ isCheckingExistingRegistration: isAuthenticated && input.existingRegistrationLoading
988
+ });
989
+ const handleTicketsComplete = () => {
990
+ if (ticketContinuation.kind === "blocked") return {
991
+ ok: false,
992
+ reason: "blocked",
993
+ message: ticketContinuation.message
994
+ };
995
+ dispatchFlow({
996
+ type: "tickets-completed",
997
+ admission: ticketContinuation.admission
998
+ });
999
+ return { ok: true };
1000
+ };
1001
+ const onRecoveryChecked = (outcome) => dispatchFlow({
1002
+ type: "recovery-checked",
1003
+ outcome
1004
+ });
1005
+ return {
1006
+ admission,
1007
+ ticketContinuation,
1008
+ handleTicketsComplete,
1009
+ onRecoveryChecked
1010
+ };
1011
+ }
1012
+ //#endregion
783
1013
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.ts
784
1014
  function resolveRegistrationRecoveryAction(step, action) {
785
1015
  if (step === "payment-failed") return "payment-options";
786
1016
  if (step === "payment-status") return "none";
787
- if (step === "verifying-payment" || step === "verifying-timeout" || step === "redirecting") return "none";
1017
+ if (step === "verifying-payment" || step === "verifying-timeout" || step === "payment") return "none";
788
1018
  return action;
789
1019
  }
790
- function canRecoverToWaitlist(eligibility) {
791
- return eligibility?.availability.kind === "waitlist" && eligibility.registration.kind !== "pending_payment";
792
- }
793
1020
  function shouldRefreshAfterRegistrationError(step, action, enabled) {
794
1021
  return enabled || step === "error" && action === "places";
795
1022
  }
@@ -803,7 +1030,6 @@ function resetRegistrationAfterExpiry(input) {
803
1030
  const mode = input.preSelectedEvent?.deliveryMode;
804
1031
  input.setSelectedAttendanceMode(mode === "online" || mode === "in_person" ? mode : null);
805
1032
  input.setErrorMessage("");
806
- input.setPendingRegistrationReference(null);
807
1033
  }
808
1034
  /** Retry the retained submission in place; explicit payment uncertainty stays status-only. */
809
1035
  function recoverRegistrationError(action, handlers) {
@@ -1156,291 +1382,6 @@ function logoutPortal(anchorId) {
1156
1382
  form.submit();
1157
1383
  }
1158
1384
  //#endregion
1159
- //#region ../commerce/src/purchase/checkoutPaymentFlow.ts
1160
- function planCheckoutPaymentFlow(options) {
1161
- const amountDueCents = toNonNegativeCents(options.amountDueCents);
1162
- const discountCents = toNonNegativeCents(options.discountCents);
1163
- const availableCreditCents = toNonNegativeCents(options.availableCreditCents);
1164
- const minimumStripeChargeCents = toNonNegativeCents(options.minimumStripeChargeCents ?? 0);
1165
- if (amountDueCents <= 0) return {
1166
- kind: "bypass",
1167
- bypassReason: discountCents > 0 ? "discount_free" : "free",
1168
- reserveCreditCents: 0
1169
- };
1170
- if (!options.creditCurrencyMatches || availableCreditCents <= 0) return {
1171
- kind: "stripe",
1172
- reserveCreditCents: toNonNegativeCents(0)
1173
- };
1174
- if (availableCreditCents >= amountDueCents) return {
1175
- kind: "store_credit_only",
1176
- reserveCreditCents: amountDueCents
1177
- };
1178
- return {
1179
- kind: "stripe",
1180
- reserveCreditCents: planMixedPaymentCreditReservation({
1181
- amountDueCents,
1182
- availableCreditCents,
1183
- minimumStripeChargeCents
1184
- })
1185
- };
1186
- }
1187
- function planMixedPaymentCreditReservation(input) {
1188
- const chargeAfterFullCredit = input.amountDueCents - input.availableCreditCents;
1189
- if (input.minimumStripeChargeCents <= 0 || chargeAfterFullCredit <= 0 || chargeAfterFullCredit >= input.minimumStripeChargeCents) return input.availableCreditCents;
1190
- if (input.amountDueCents < input.minimumStripeChargeCents) return toNonNegativeCents(0);
1191
- return toNonNegativeCents(input.amountDueCents - input.minimumStripeChargeCents);
1192
- }
1193
- /**
1194
- * Apply site credit to a flexible-balance booking before card payment.
1195
- *
1196
- * Credit first reduces the configured payment due now. Once that payment is
1197
- * covered, any remaining credit reduces the later balance. If a partial credit
1198
- * application would leave a positive card charge below Stripe's minimum, less
1199
- * credit is used so the configured payment can still be collected.
1200
- */
1201
- function planFlexibleBalanceCreditFirst(input) {
1202
- const totalCents = toNonNegativeCents(input.totalAfterDiscountCents);
1203
- const configuredDueNowCents = toNonNegativeCents(Math.min(input.configuredPaymentDueNowCents, totalCents));
1204
- const availableCreditCents = toNonNegativeCents(input.creditCurrencyMatches ? input.availableCreditCents : 0);
1205
- const minimumStripeChargeCents = toNonNegativeCents(input.minimumStripeChargeCents ?? 0);
1206
- if (availableCreditCents >= totalCents && totalCents > 0) return {
1207
- kind: "credit_only",
1208
- reserveCreditCents: totalCents,
1209
- cardDueNowCents: toNonNegativeCents(0),
1210
- laterBalanceCents: toNonNegativeCents(0)
1211
- };
1212
- let reserveCreditCents = toNonNegativeCents(Math.min(availableCreditCents, totalCents));
1213
- if (reserveCreditCents >= configuredDueNowCents) return {
1214
- kind: "flexible_balance",
1215
- reserveCreditCents,
1216
- cardDueNowCents: toNonNegativeCents(0),
1217
- laterBalanceCents: toNonNegativeCents(totalCents - reserveCreditCents)
1218
- };
1219
- const plannedCardDueNowCents = toNonNegativeCents(configuredDueNowCents - reserveCreditCents);
1220
- if (plannedCardDueNowCents > 0 && plannedCardDueNowCents < minimumStripeChargeCents) {
1221
- if (configuredDueNowCents < minimumStripeChargeCents) return {
1222
- kind: "card_below_minimum",
1223
- reserveCreditCents: toNonNegativeCents(0),
1224
- cardDueNowCents: configuredDueNowCents,
1225
- laterBalanceCents: toNonNegativeCents(totalCents - configuredDueNowCents)
1226
- };
1227
- reserveCreditCents = toNonNegativeCents(configuredDueNowCents - minimumStripeChargeCents);
1228
- }
1229
- const cardDueNowCents = toNonNegativeCents(configuredDueNowCents - reserveCreditCents);
1230
- return {
1231
- kind: "flexible_balance",
1232
- reserveCreditCents,
1233
- cardDueNowCents,
1234
- laterBalanceCents: toNonNegativeCents(totalCents - reserveCreditCents - cardDueNowCents)
1235
- };
1236
- }
1237
- //#endregion
1238
- //#region ../commerce/src/purchase/settlement.ts
1239
- function normalizeCurrencyCode(raw) {
1240
- const normalized = raw.trim().toUpperCase();
1241
- if (!/^[A-Z]{3}$/.test(normalized)) throw new Error(`Invalid currency code: ${raw}`);
1242
- return normalized;
1243
- }
1244
- //#endregion
1245
- //#region ../commerce/src/purchase/checkoutSessionMetadata.ts
1246
- const datedOfferingSharedEngineMetadataShape = {
1247
- datedOfferingEngineContractVersion: z.literal("shared_v1"),
1248
- datedOfferingEngineRouteKind: z.literal("shared_whole_series_registration"),
1249
- datedOfferingCapacityHoldId: z.string().uuid(),
1250
- datedOfferingCapacityHoldAuditRef: z.string().uuid(),
1251
- customerId: z.string().uuid(),
1252
- eventSeriesId: z.string().uuid()
1253
- };
1254
- z.object(datedOfferingSharedEngineMetadataShape);
1255
- const orderCheckoutSessionMetadataBaseShape = {
1256
- type: z.literal("order"),
1257
- site_id: z.string().uuid(),
1258
- order_id: z.string().uuid().optional(),
1259
- customer_email: z.string().email(),
1260
- customer_name: z.string().optional(),
1261
- product_id: z.string().uuid().optional(),
1262
- attendee_id: z.string().uuid().optional(),
1263
- occurrence_id: z.string().uuid().optional(),
1264
- tickets_count: z.string().optional(),
1265
- notes: z.string().optional(),
1266
- discount_redemption_id: z.string().uuid().optional(),
1267
- discount_code_id: z.string().uuid().optional(),
1268
- discount_code: z.string().optional(),
1269
- discount_public_label: z.string().optional(),
1270
- credit_reservation_id: z.string().uuid().optional(),
1271
- credit_reserved_cents: z.string().optional(),
1272
- discount_code_amount_cents: z.string().optional(),
1273
- order_payment_reason: z.enum([
1274
- "instalment_payoff",
1275
- "flexible_balance_initial",
1276
- "flexible_balance_paydown"
1277
- ]).optional(),
1278
- order_payment_plan_id: z.string().uuid().optional(),
1279
- order_flexible_balance_contract_id: z.string().uuid().optional()
1280
- };
1281
- const absentDatedOfferingSharedEngineMetadataShape = {
1282
- datedOfferingEngineContractVersion: z.never().optional(),
1283
- datedOfferingEngineRouteKind: z.never().optional(),
1284
- datedOfferingCapacityHoldId: z.never().optional(),
1285
- datedOfferingCapacityHoldAuditRef: z.never().optional(),
1286
- customerId: z.never().optional(),
1287
- eventSeriesId: z.never().optional()
1288
- };
1289
- const LegacyOrderCheckoutSessionMetadataSchema = z.object({
1290
- ...orderCheckoutSessionMetadataBaseShape,
1291
- ...absentDatedOfferingSharedEngineMetadataShape
1292
- });
1293
- const SharedDatedOfferingOrderCheckoutSessionMetadataSchema = z.object({
1294
- ...orderCheckoutSessionMetadataBaseShape,
1295
- ...datedOfferingSharedEngineMetadataShape
1296
- });
1297
- z.union([SharedDatedOfferingOrderCheckoutSessionMetadataSchema, LegacyOrderCheckoutSessionMetadataSchema]);
1298
- //#endregion
1299
- //#region ../commerce/src/purchase/stripeCommissionMetadata.ts
1300
- /** Reasons are payment evidence, not an independent pricing policy. */
1301
- const STRIPE_COMMISSION_FREE_REASONS = [
1302
- "managed_billing",
1303
- "no_commission_plan",
1304
- "billing_evidence_invalid",
1305
- "fee_rounds_to_zero",
1306
- "payment_not_stripe"
1307
- ];
1308
- const historicalContext = {
1309
- currency: z.string().refine((value) => parseCurrencyCode(value) !== null).optional(),
1310
- sourcePlanId: z.string().trim().min(1).optional()
1311
- };
1312
- const percentSchema = z.number().finite().min(0).max(100);
1313
- z.discriminatedUnion("kind", [
1314
- z.object({
1315
- kind: z.literal("commission_free"),
1316
- reason: z.enum(STRIPE_COMMISSION_FREE_REASONS),
1317
- ...historicalContext
1318
- }),
1319
- z.object({
1320
- kind: z.literal("application_fee_amount"),
1321
- amountCents: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
1322
- percent: percentSchema,
1323
- ...historicalContext
1324
- }),
1325
- z.object({
1326
- kind: z.literal("application_fee_percent"),
1327
- percent: percentSchema,
1328
- ...historicalContext
1329
- })
1330
- ]);
1331
- toNonNegativeCents(0);
1332
- //#endregion
1333
- //#region ../commerce/src/purchase/stripeMinimumCharge.ts
1334
- const STRIPE_MINIMUM_CHARGE_CENTS_BY_CURRENCY = {
1335
- GBP: 30,
1336
- USD: 50,
1337
- EUR: 50,
1338
- AED: 200,
1339
- AUD: 50,
1340
- BGN: 100,
1341
- BRL: 50,
1342
- CAD: 50,
1343
- CHF: 50,
1344
- CZK: 1500,
1345
- DKK: 250,
1346
- HKD: 400,
1347
- HUF: 17500,
1348
- JPY: 50,
1349
- MXN: 1e3,
1350
- MYR: 200,
1351
- NOK: 300,
1352
- NZD: 50,
1353
- PLN: 200,
1354
- RON: 200,
1355
- SEK: 300,
1356
- SGD: 50,
1357
- THB: 1e3
1358
- };
1359
- function isStripeMinimumChargeCurrency(currency) {
1360
- return currency in STRIPE_MINIMUM_CHARGE_CENTS_BY_CURRENCY;
1361
- }
1362
- function getStripeMinimumChargeRequirement(rawCurrency) {
1363
- let currency;
1364
- try {
1365
- currency = normalizeCurrencyCode(rawCurrency);
1366
- } catch {
1367
- return null;
1368
- }
1369
- const currencyKey = currency;
1370
- if (!isStripeMinimumChargeCurrency(currencyKey)) return null;
1371
- return {
1372
- currency,
1373
- minimumAmountCents: STRIPE_MINIMUM_CHARGE_CENTS_BY_CURRENCY[currencyKey]
1374
- };
1375
- }
1376
- //#endregion
1377
- //#region ../commerce/src/purchase/event-registration-errors.ts
1378
- const eventRegistrationErrors = {
1379
- registrationClosed: {
1380
- message: "Booking is currently closed for this event. Please contact the organiser if you’d like to find out more.",
1381
- action: "none"
1382
- },
1383
- paymentInvestigationRequired: {
1384
- message: "We still can’t confirm your payment. Please don’t make another payment. Contact the organiser so they can check your booking.",
1385
- action: "none"
1386
- },
1387
- paymentProviderUnavailable: {
1388
- message: "We couldn’t reach the payment provider. Please try again in a minute.",
1389
- action: "retry"
1390
- },
1391
- paymentPageUnavailable: {
1392
- message: "We couldn’t open the payment page. Please refresh this page and try again. If this keeps happening, contact the organiser for help.",
1393
- action: "retry"
1394
- },
1395
- paymentUnavailable: {
1396
- message: "Online payment is not available for this event right now. Please contact the organiser for help with booking.",
1397
- action: "none"
1398
- },
1399
- capacityChanged: {
1400
- message: "Sorry, there aren’t enough places left for your selection. Please check the available places and choose again.",
1401
- action: "places"
1402
- },
1403
- selectionChanged: {
1404
- message: "The booking options for this event have changed. Please check the latest options and choose your places again.",
1405
- action: "places"
1406
- },
1407
- registrationPreparationChanged: {
1408
- message: "The booking options for this event have changed. Please check the latest options and choose your places again.",
1409
- action: "places"
1410
- },
1411
- registrationAlreadyExists: {
1412
- message: "You already have a booking for this event. Please check your bookings to see its status. If you need help, contact the organiser.",
1413
- action: "portal"
1414
- },
1415
- holdExpired: {
1416
- message: "Your reservation has timed out. Please check availability and choose your places again.",
1417
- action: "start"
1418
- },
1419
- waitlistUnavailable: {
1420
- message: "Sorry, the waiting list place offered in this invitation is no longer available. Please check the event for available places or contact the organiser for help.",
1421
- action: "event"
1422
- },
1423
- paymentContextMismatch: {
1424
- message: "The payment options for this event have changed. Please check the latest options before continuing.",
1425
- action: "payment-options"
1426
- },
1427
- verificationFailed: {
1428
- message: "We couldn’t confirm this request came from a person. Please refresh the page and try again.",
1429
- action: "none"
1430
- },
1431
- idempotencyConflict: {
1432
- message: "You’ve already started booking this event. Please finish in the other tab if it’s still open, or check your booking status before starting again.",
1433
- action: "portal"
1434
- },
1435
- invalidCommitInput: {
1436
- message: "We couldn’t accept your booking details. Please check the information you entered and try again. If this keeps happening, contact the organiser for help.",
1437
- action: "details"
1438
- }
1439
- };
1440
- function parseEventRegistrationErrorReason(value) {
1441
- return typeof value === "string" && Object.prototype.hasOwnProperty.call(eventRegistrationErrors, value) ? value : null;
1442
- }
1443
- //#endregion
1444
1385
  //#region ../blocks/src/system/runtime/shared/useWaitlistClaim.state.ts
1445
1386
  const DEFAULT_WAITLIST_CLAIM_ERROR = "Online booking is unavailable right now. Please contact the organiser for help with booking.";
1446
1387
  function createInitialWaitlistClaimState(hasToken) {
@@ -1588,441 +1529,174 @@ function useResolvedWaitlistClaim(deps, credential) {
1588
1529
  };
1589
1530
  }
1590
1531
  //#endregion
1591
- //#region ../blocks/src/system/runtime/hooks/usePaymentVerification.ts
1592
- /**
1593
- * Hook for polling payment status after Stripe redirects.
1594
- *
1595
- * Built on top of the generic usePolling hook, this provides
1596
- * a specialized interface for verifying event registration payments.
1597
- *
1598
- * @example
1599
- * ```tsx
1600
- * const verification = usePaymentVerification(
1601
- * step === 'verifying-payment' && attendeeId
1602
- * ? { transactionId: attendeeId }
1603
- * : null
1604
- * );
1605
- *
1606
- * useEffect(() => {
1607
- * if (verification.status === 'completed') {
1608
- * setStep('success');
1609
- * } else if (verification.status === 'failed') {
1610
- * setStep('payment-failed');
1611
- * } else if (verification.status === 'timeout') {
1612
- * setStep('verifying-timeout');
1613
- * }
1614
- * }, [verification.status]);
1615
- * ```
1616
- */
1617
- const paymentVerificationDeps = { useBlockApi };
1618
- function resolvePaymentPollingTarget(config) {
1619
- const reference = config.reference;
1620
- return reference.scope === "occurrence" ? {
1621
- key: `occurrence:${reference.attendeeId}`,
1622
- reference
1623
- } : {
1624
- key: `wholeSeries:${reference.registrationId}`,
1625
- reference
1626
- };
1627
- }
1628
- function createPaymentPollingConfig(api, settings) {
1629
- if (settings === null) return null;
1630
- const target = resolvePaymentPollingTarget(settings);
1631
- return {
1632
- restartKey: target.key,
1633
- pollFn: () => api.getEventRegistrationStatus(target.reference),
1634
- isComplete: (result) => result.paymentStatus === "completed" || result.paymentStatus === "not_required",
1635
- isFailed: (result) => result.paymentStatus === "failed" || result.paymentStatus === "refunded",
1636
- maxAttempts: settings.maxAttempts,
1637
- interval: settings.pollInterval
1638
- };
1639
- }
1640
- function getPaymentVerificationError(polling, paymentStatus) {
1641
- if (polling.status === "timeout") return "Could not verify payment. If you were charged, please contact support.";
1642
- if (polling.status === "failed" && paymentStatus !== null) return paymentStatus === "refunded" ? "Payment was refunded. Please contact support." : "Payment was declined. Please try again.";
1643
- return polling.error?.message ?? null;
1644
- }
1645
- function mapPaymentVerificationResult(polling) {
1646
- const paymentStatus = polling.result?.paymentStatus ?? null;
1647
- return {
1648
- status: polling.status,
1649
- paymentStatus,
1650
- attendeeStatus: polling.result?.attendeeStatus ?? null,
1651
- waitlistPosition: polling.result?.waitlistPosition ?? null,
1652
- error: getPaymentVerificationError(polling, paymentStatus),
1653
- retry: polling.retry
1654
- };
1655
- }
1656
- /**
1657
- * Hook for polling payment status after Stripe redirects.
1658
- *
1659
- * Pass config to start polling, or null to stop/idle.
1660
- * The hook handles cleanup on unmount and starts a new cycle when the status
1661
- * reference changes.
1662
- *
1663
- * @param config - Configuration for polling, or null to stop
1664
- * @returns Verification result with status, data, and retry function
1665
- */
1666
- function usePaymentVerification(config) {
1667
- return usePaymentVerificationWithDeps(config, paymentVerificationDeps);
1668
- }
1669
- function usePaymentVerificationWithDeps(config, deps) {
1670
- return mapPaymentVerificationResult(usePolling(createPaymentPollingConfig(deps.useBlockApi(), config)));
1671
- }
1672
- //#endregion
1673
- //#region ../blocks/src/system/runtime/nodes/event-registration/constants.ts
1674
- const EVENT_REGISTRATION_ANCHOR_ID = "event-registration";
1675
- const EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM = "registration_attempt";
1676
- //#endregion
1677
1532
  //#region ../blocks/src/system/runtime/nodes/event-registration/sessionStorage.ts
1678
1533
  const STORAGE_PREFIX = "riverbank:event-registration:";
1679
1534
  const STALE_THRESHOLD_MS = 1440 * 60 * 1e3;
1680
- /**
1681
- * Build the sessionStorage key for a specific registration.
1682
- */
1683
- function getStorageKey(siteId, checkoutAttemptId) {
1684
- return `${STORAGE_PREFIX}${siteId}:${checkoutAttemptId}`;
1685
- }
1686
- /**
1687
- * Check if sessionStorage is available.
1688
- * Returns false in private browsing mode or when storage is disabled.
1689
- */
1690
- function isStorageAvailable() {
1691
- try {
1692
- const testKey = "__riverbank_test__";
1693
- sessionStorage.setItem(testKey, "test");
1694
- sessionStorage.removeItem(testKey);
1695
- return true;
1696
- } catch {
1697
- return false;
1698
- }
1535
+ const paymentSelection = z.discriminatedUnion("method", [
1536
+ z.object({ method: z.literal("free") }),
1537
+ z.object({
1538
+ method: z.literal("stripe"),
1539
+ collectionOptionKind: z.enum([
1540
+ "upfront",
1541
+ "deferred_manual",
1542
+ "instalment_plan",
1543
+ "flexible_balance"
1544
+ ])
1545
+ }),
1546
+ z.object({
1547
+ method: z.literal("pass"),
1548
+ passId: z.string(),
1549
+ productName: z.string().optional()
1550
+ }),
1551
+ z.object({
1552
+ method: z.literal("membership"),
1553
+ membershipId: z.string(),
1554
+ productName: z.string().optional()
1555
+ }),
1556
+ z.object({
1557
+ method: z.literal("pass_bundle"),
1558
+ bundlePassId: z.string(),
1559
+ productName: z.string().optional(),
1560
+ productDescription: z.string().optional(),
1561
+ productPrice: z.number().optional(),
1562
+ productCurrency: z.string().optional()
1563
+ }),
1564
+ z.object({
1565
+ method: z.literal("membership_bundle"),
1566
+ bundleMembershipId: z.string(),
1567
+ productName: z.string().optional(),
1568
+ productDescription: z.string().optional(),
1569
+ productPrice: z.number().optional(),
1570
+ productCurrency: z.string().optional(),
1571
+ productInterval: z.string().optional()
1572
+ })
1573
+ ]);
1574
+ const schema = z.object({
1575
+ version: z.literal(6),
1576
+ reference: z.discriminatedUnion("scope", [z.object({
1577
+ scope: z.literal("occurrence"),
1578
+ attendeeId: z.string()
1579
+ }), z.object({
1580
+ scope: z.literal("wholeSeries"),
1581
+ registrationId: z.string()
1582
+ })]),
1583
+ formData: z.object({
1584
+ name: z.string(),
1585
+ email: z.string(),
1586
+ phone: z.string(),
1587
+ notes: z.string(),
1588
+ consentAccepted: z.boolean().optional(),
1589
+ mailingListOptIn: z.boolean().optional()
1590
+ }),
1591
+ ticketSelections: z.array(z.object({
1592
+ pricingOptionId: z.string().nullable(),
1593
+ quantity: z.number().int().positive()
1594
+ })).min(1),
1595
+ selectedEventId: z.string().nullable(),
1596
+ selectedAttendanceMode: z.enum(["in_person", "online"]).nullable().optional(),
1597
+ paymentSelection,
1598
+ guests: z.array(z.object({
1599
+ name: z.string(),
1600
+ email: z.string().nullable(),
1601
+ phone: z.string().nullable()
1602
+ })),
1603
+ selectedSlidingAmount: z.number().nullable(),
1604
+ discountCode: z.string(),
1605
+ mailingListSignupNote: z.enum(["confirmation_required", "not_confirmed"]).optional(),
1606
+ timestamp: z.number().finite()
1607
+ });
1608
+ function key(siteId, submissionId) {
1609
+ return `${STORAGE_PREFIX}${siteId}:${submissionId}`;
1699
1610
  }
1700
- /**
1701
- * Save pending registration data before Stripe redirect.
1702
- *
1703
- * @param siteId - Site ID
1704
- * @param checkoutAttemptId - Correlation ID included in the checkout return URL
1705
- * @param data - Registration data to save (excluding version and timestamp)
1706
- */
1707
- function savePendingRegistration(siteId, checkoutAttemptId, data) {
1708
- if (!isStorageAvailable()) {
1709
- console.warn("[sessionStorage] Storage not available, skipping save");
1710
- return;
1711
- }
1611
+ function savePendingRegistration(siteId, submissionId, data) {
1712
1612
  try {
1713
- const key = getStorageKey(siteId, checkoutAttemptId);
1714
- const ticketSelections = normalizeTicketSelectionLines(data.ticketSelections);
1715
- if (ticketSelections.length === 0) {
1716
- console.warn("[sessionStorage] No ticket selections to save, skipping registration state");
1717
- return;
1718
- }
1719
- const entry = {
1613
+ const entry = schema.safeParse({
1720
1614
  ...data,
1721
- ticketSelections,
1722
- version: 5,
1615
+ ticketSelections: normalizeTicketSelectionLines(data.ticketSelections),
1616
+ version: 6,
1723
1617
  timestamp: Date.now()
1724
- };
1725
- sessionStorage.setItem(key, JSON.stringify(entry));
1726
- } catch (error) {
1727
- console.warn("[sessionStorage] Failed to save registration:", error);
1728
- }
1729
- }
1730
- /**
1731
- * Get pending registration data after returning from Stripe.
1732
- *
1733
- * Returns null if:
1734
- * - No entry exists for this checkout attempt
1735
- * - Entry is stale (> 24 hours old)
1736
- * - Entry is corrupt or wrong version
1737
- * - Storage is unavailable
1738
- *
1739
- * @param siteId - Site ID
1740
- * @param checkoutAttemptId - Correlation ID returned from checkout.
1741
- * @returns Pending registration data or null
1742
- */
1743
- function getPendingRegistration(siteId, checkoutAttemptId) {
1744
- if (!isStorageAvailable()) return null;
1745
- try {
1746
- const key = getStorageKey(siteId, checkoutAttemptId);
1747
- const stored = sessionStorage.getItem(key);
1748
- if (stored) return parseAndValidateEntry(stored, key);
1749
- return null;
1750
- } catch (error) {
1751
- console.warn("[sessionStorage] Failed to read registration:", error);
1752
- return null;
1753
- }
1618
+ });
1619
+ if (entry.success) sessionStorage.setItem(key(siteId, submissionId), JSON.stringify(entry.data));
1620
+ } catch {}
1754
1621
  }
1755
- /**
1756
- * Internal helper to parse and validate a storage entry.
1757
- */
1758
- function parseAndValidateEntry(stored, key) {
1622
+ function getPendingRegistration(siteId, submissionId) {
1759
1623
  try {
1760
- const raw = JSON.parse(stored);
1761
- if (!raw || typeof raw !== "object") {
1762
- sessionStorage.removeItem(key);
1763
- return null;
1764
- }
1765
- const version = raw.version;
1766
- if (version !== 4 && version !== 5) {
1767
- console.warn("[sessionStorage] Outdated version, ignoring entry");
1768
- sessionStorage.removeItem(key);
1624
+ const stored = sessionStorage.getItem(key(siteId, submissionId));
1625
+ const parsed = schema.safeParse(stored ? JSON.parse(stored) : null);
1626
+ if (!parsed.success || Date.now() - parsed.data.timestamp > STALE_THRESHOLD_MS) {
1627
+ clearPendingRegistration(siteId, submissionId);
1769
1628
  return null;
1770
1629
  }
1771
- const entry = normalizeStoredRegistration(raw);
1772
- const timestamp = typeof entry.timestamp === "number" ? entry.timestamp : NaN;
1773
- if (!Number.isFinite(timestamp)) {
1774
- sessionStorage.removeItem(key);
1775
- return null;
1776
- }
1777
- if (Date.now() - timestamp > STALE_THRESHOLD_MS) {
1778
- console.info("[sessionStorage] Entry is stale, removing");
1779
- sessionStorage.removeItem(key);
1780
- return null;
1781
- }
1782
- const ticketSelections = normalizeTicketSelectionLines(entry.ticketSelections);
1783
- if (ticketSelections.length === 0 || getTicketSelectionTotal(ticketSelections) <= 0) {
1784
- sessionStorage.removeItem(key);
1785
- return null;
1786
- }
1787
- return {
1788
- ...entry,
1789
- ticketSelections
1790
- };
1630
+ return parsed.data;
1791
1631
  } catch {
1792
- console.warn("[sessionStorage] Failed to parse entry, removing");
1793
- sessionStorage.removeItem(key);
1794
1632
  return null;
1795
1633
  }
1796
1634
  }
1797
- function normalizeStoredRegistration(entry) {
1798
- if (entry.version === 5) return entry;
1799
- const { attendeeId, ...stored } = entry;
1800
- return {
1801
- ...stored,
1802
- version: 5,
1803
- reference: {
1804
- scope: "occurrence",
1805
- attendeeId
1806
- }
1807
- };
1808
- }
1809
- /**
1810
- * Clear pending registration data after successful verification.
1811
- *
1812
- * @param siteId - Site ID
1813
- * @param checkoutAttemptId - Checkout return correlation ID
1814
- */
1815
- function clearPendingRegistration(siteId, checkoutAttemptId) {
1816
- if (!isStorageAvailable()) return;
1635
+ function clearPendingRegistration(siteId, submissionId) {
1817
1636
  try {
1818
- const key = getStorageKey(siteId, checkoutAttemptId);
1819
- sessionStorage.removeItem(key);
1637
+ sessionStorage.removeItem(key(siteId, submissionId));
1820
1638
  } catch {}
1821
1639
  }
1822
- /**
1823
- * Clean up all stale registration entries from sessionStorage.
1824
- * Should be called on component mount to prevent accumulation.
1825
- */
1826
1640
  function cleanupStaleRegistrations() {
1827
- if (!isStorageAvailable()) return;
1828
1641
  try {
1829
- const keysToRemove = [];
1830
- for (let i = 0; i < sessionStorage.length; i++) {
1831
- const key = sessionStorage.key(i);
1832
- if (!key?.startsWith(STORAGE_PREFIX)) continue;
1642
+ Array.from({ length: sessionStorage.length }, (_, index) => sessionStorage.key(index)).filter((item) => item !== null && item.startsWith(STORAGE_PREFIX)).forEach((item) => {
1833
1643
  try {
1834
- const stored = sessionStorage.getItem(key);
1835
- if (!stored) continue;
1836
- const entry = JSON.parse(stored);
1837
- const timestamp = typeof entry.timestamp === "number" ? entry.timestamp : NaN;
1838
- const age = Date.now() - timestamp;
1839
- if (!Number.isFinite(timestamp) || age > STALE_THRESHOLD_MS) keysToRemove.push(key);
1644
+ const parsed = schema.safeParse(JSON.parse(sessionStorage.getItem(item) ?? "null"));
1645
+ if (!parsed.success || Date.now() - parsed.data.timestamp > STALE_THRESHOLD_MS) sessionStorage.removeItem(item);
1840
1646
  } catch {
1841
- keysToRemove.push(key);
1647
+ sessionStorage.removeItem(item);
1842
1648
  }
1843
- }
1844
- for (const key of keysToRemove) sessionStorage.removeItem(key);
1845
- if (keysToRemove.length > 0) console.info(`[sessionStorage] Cleaned up ${keysToRemove.length} stale entries`);
1846
- } catch (error) {
1847
- console.warn("[sessionStorage] Cleanup failed:", error);
1848
- }
1649
+ });
1650
+ } catch {}
1849
1651
  }
1850
1652
  //#endregion
1653
+ //#region ../blocks/src/system/runtime/nodes/event-registration/constants.ts
1654
+ const EVENT_REGISTRATION_ANCHOR_ID = "event-registration";
1655
+ const EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM = "registration_attempt";
1656
+ //#endregion
1851
1657
  //#region ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.ts
1852
- const eventRegistrationStripeReturnDeps = {
1853
- usePaymentVerification,
1854
- readReturnParams: () => {
1855
- const params = new URLSearchParams(window.location.search);
1856
- return {
1857
- registration: params.get("registration"),
1858
- registration_attempt: params.get(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM),
1859
- session_id: params.get("session_id")
1860
- };
1861
- },
1862
- clearReturnParams: () => {
1863
- consumeSearchParams([
1864
- "registration",
1865
- EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM,
1866
- "session_id"
1867
- ]);
1868
- },
1869
- scrollToBlock,
1870
- cleanupStaleRegistrations,
1871
- clearPendingRegistration,
1872
- getPendingRegistration
1873
- };
1874
- function restorePendingPresentation(options, pending) {
1875
- options.setFormData(pending.formData);
1876
- options.setTicketSelections(pending.ticketSelections);
1877
- options.setTicketsCount(getTicketSelectionTotal(pending.ticketSelections));
1878
- if (pending.selectedEventId) {
1879
- const event = options.events.find((item) => item.selectionId === pending.selectedEventId) ?? null;
1880
- if (event) options.setSelectedEvent(event);
1881
- }
1882
- options.setSelectedAttendanceMode(pending.selectedAttendanceMode ?? null);
1883
- options.setPaymentSelection(pending.paymentSelection);
1884
- }
1885
- function showReturnReconciliation(options, message) {
1886
- options.setErrorMessage(message);
1887
- options.setStep("verifying-timeout");
1888
- }
1889
- function checkoutReturnCommand(params) {
1890
- const checkoutAttemptId = params.registration_attempt;
1891
- if (!checkoutAttemptId) return null;
1892
- if (params.registration === "cancelled") return {
1893
- kind: "abandon",
1894
- checkoutAttemptId
1895
- };
1896
- if (params.registration === "success" && params.session_id) return {
1897
- kind: "resolveSuccess",
1898
- checkoutAttemptId,
1899
- sessionId: params.session_id
1900
- };
1901
- return null;
1902
- }
1903
- function handleCheckoutReturnOutcome(options, deps, context, outcome) {
1904
- const { pending, params } = context;
1905
- if (outcome.kind === "resolved") {
1906
- deps.clearReturnParams();
1907
- options.setPendingRegistrationReference(outcome.reference);
1908
- options.setStep("verifying-payment");
1909
- return "settled";
1910
- }
1911
- if (outcome.kind === "abandoned") {
1912
- deps.clearReturnParams();
1913
- options.setErrorMessage("Your payment was cancelled. You can choose a payment option and try again.");
1914
- options.setStep("select-payment");
1915
- return "settled";
1916
- }
1917
- if (outcome.kind === "notFound" && params.registration === "cancelled" && (options.registrationSubject?.kind === "occurrence" || pending?.reference.scope === "occurrence")) {
1918
- deps.clearReturnParams();
1919
- options.setErrorMessage("Your payment was cancelled. You can choose a payment option and try again.");
1920
- options.setStep("select-payment");
1921
- return "settled";
1922
- }
1923
- 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.");
1924
- return outcome.kind === "notFound" ? "settled" : "retryable";
1925
- }
1926
- async function handleCheckoutReturn(options, deps, returnContextRef) {
1927
- deps.cleanupStaleRegistrations();
1928
- const params = deps.readReturnParams();
1929
- if (params.registration !== "cancelled" && params.registration !== "success") return "settled";
1930
- deps.scrollToBlock(EVENT_REGISTRATION_ANCHOR_ID);
1931
- const siteId = options.api.config.siteId;
1932
- const command = checkoutReturnCommand(params);
1933
- if (!siteId) return "settled";
1934
- if (!command) {
1935
- 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.");
1936
- return "settled";
1937
- }
1938
- const { checkoutAttemptId } = command;
1939
- const pending = deps.getPendingRegistration(siteId, checkoutAttemptId);
1940
- returnContextRef.current = {
1941
- checkoutAttemptId,
1942
- mailingListSignupNote: pending?.mailingListSignupNote
1943
- };
1944
- if (pending) restorePendingPresentation(options, pending);
1945
- options.setStep("verifying-payment");
1946
- try {
1947
- const outcome = await options.api.resolveEventCheckoutReturn(command);
1948
- return handleCheckoutReturnOutcome(options, deps, {
1949
- pending,
1950
- params
1951
- }, outcome);
1952
- } catch {
1953
- 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.");
1954
- }
1955
- return "retryable";
1956
- }
1957
1658
  function useEventRegistrationStripeReturn(options) {
1958
- return useEventRegistrationStripeReturnWithDeps(options, eventRegistrationStripeReturnDeps);
1959
- }
1960
- function completeVerifiedRegistration(options, deps, returnContext) {
1961
- const siteId = options.api.config.siteId;
1962
- const checkoutAttemptId = returnContext?.checkoutAttemptId;
1963
- if (siteId && checkoutAttemptId) deps.clearPendingRegistration(siteId, checkoutAttemptId);
1964
- options.setRegistrationResult({
1965
- status: "confirmed",
1966
- mailingListSignupNote: returnContext?.mailingListSignupNote
1967
- });
1968
- options.setStep("success");
1969
- }
1970
- function useEventRegistrationStripeReturnWithDeps(options, deps) {
1971
- const { api, pendingRegistrationReference, setErrorMessage, setRegistrationResult, setStep, step } = options;
1972
- const returnContextRef = useRef(null);
1973
- const lookupInFlightRef = useRef(false);
1974
- const [lookupState, setLookupState] = useState("settled");
1975
- const resolveReturn = useCallback(async () => {
1976
- if (lookupInFlightRef.current) return;
1977
- lookupInFlightRef.current = true;
1978
- setLookupState("loading");
1979
- try {
1980
- setLookupState(await handleCheckoutReturn(options, deps, returnContextRef));
1981
- } finally {
1982
- lookupInFlightRef.current = false;
1983
- }
1984
- }, [options, deps]);
1985
- const verificationConfig = useMemo(() => step === "verifying-payment" && pendingRegistrationReference ? { reference: pendingRegistrationReference } : null, [step, pendingRegistrationReference]);
1986
- const verification = deps.usePaymentVerification(verificationConfig);
1987
- const hasVerificationTimeout = step === "verifying-timeout" && pendingRegistrationReference !== null;
1988
- const retry = useCallback(() => {
1989
- if (hasVerificationTimeout) {
1990
- setStep("verifying-payment");
1991
- return;
1992
- }
1993
- verification.retry();
1994
- }, [
1995
- hasVerificationTimeout,
1996
- setStep,
1997
- verification.retry
1998
- ]);
1999
- useEffect(() => {
2000
- resolveReturn();
2001
- }, []);
1659
+ const restored = useRef(false);
2002
1660
  useEffect(() => {
2003
- if (verification.status === "completed") completeVerifiedRegistration(options, deps, returnContextRef.current);
2004
- else if (verification.status === "failed") {
2005
- setErrorMessage("Please try again or choose another payment option. If this keeps happening, contact the organiser for help.");
2006
- setStep("payment-failed");
2007
- } else if (verification.status === "timeout") setStep("verifying-timeout");
2008
- }, [
2009
- verification.status,
2010
- verification.attendeeStatus,
2011
- verification.waitlistPosition,
2012
- verification.error,
2013
- api.config.siteId,
2014
- setErrorMessage,
2015
- setRegistrationResult,
2016
- setStep
2017
- ]);
2018
- return {
2019
- ...verification,
2020
- status: hasVerificationTimeout ? "timeout" : verification.status,
2021
- retry,
2022
- retryReturn: lookupState === "retryable" ? () => {
2023
- resolveReturn();
2024
- } : void 0
2025
- };
1661
+ if (restored.current) return;
1662
+ restored.current = true;
1663
+ cleanupStaleRegistrations();
1664
+ const params = new URLSearchParams(window.location.search);
1665
+ const checkoutAttemptId = params.get(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM);
1666
+ const sessionId = params.get("session_id");
1667
+ if (params.get("registration") !== "payment" || !checkoutAttemptId || !sessionId) return;
1668
+ const pending = options.siteId ? getPendingRegistration(options.siteId, checkoutAttemptId) : null;
1669
+ if (pending) options.restore(pending);
1670
+ options.verify({
1671
+ kind: "resolveSession",
1672
+ checkoutAttemptId,
1673
+ sessionId
1674
+ });
1675
+ }, [options]);
1676
+ }
1677
+ //#endregion
1678
+ //#region ../blocks/src/system/runtime/nodes/event-registration/paymentLocation.ts
1679
+ function eventPaymentReturnUrl(submissionId) {
1680
+ const url = new URL(window.location.href);
1681
+ url.searchParams.set("registration", "payment");
1682
+ url.searchParams.set(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM, submissionId);
1683
+ url.searchParams.delete("session_id");
1684
+ url.hash = EVENT_REGISTRATION_ANCHOR_ID;
1685
+ return url.toString();
1686
+ }
1687
+ function rememberEventPayment(submissionId, sessionId) {
1688
+ const url = new URL(eventPaymentReturnUrl(submissionId));
1689
+ url.searchParams.set("session_id", sessionId);
1690
+ window.history.replaceState(window.history.state, "", url);
1691
+ }
1692
+ function clearEventPayment(siteId) {
1693
+ const url = new URL(window.location.href);
1694
+ const submissionId = url.searchParams.get(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM);
1695
+ if (siteId && submissionId) clearPendingRegistration(siteId, submissionId);
1696
+ url.searchParams.delete("registration");
1697
+ url.searchParams.delete(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM);
1698
+ url.searchParams.delete("session_id");
1699
+ window.history.replaceState(window.history.state, "", url);
2026
1700
  }
2027
1701
  //#endregion
2028
1702
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationErrorPresentation.ts
@@ -2069,7 +1743,7 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
2069
1743
  */
2070
1744
  const { commandRef } = options;
2071
1745
  const inFlightRef = useRef(null);
2072
- const submit = async (method, passId, membershipId, selection, submitOptions) => {
1746
+ const submit = async (method, passId, membershipId, selection) => {
2073
1747
  const target = options.registrationTarget;
2074
1748
  if (!target) return {
2075
1749
  ok: false,
@@ -2079,25 +1753,21 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
2079
1753
  options.setStep("submitting");
2080
1754
  const registrationCommandId = commandRef.current?.id ?? deps.createClientCommandId();
2081
1755
  try {
2082
- let prepared = prepareSubmitContext(options, registrationCommandId, target, method, passId, membershipId, selection);
2083
- let request = buildRegistrationRequest(options, prepared, method, passId, membershipId);
2084
- const intentKey = registrationRequestIntentKey(request);
2085
- if (commandRef.current !== null && commandRef.current.intentKey !== intentKey) {
2086
- prepared = {
2087
- ...prepared,
2088
- registrationCommandId: deps.createClientCommandId()
2089
- };
2090
- request = buildRegistrationRequest(options, prepared, method, passId, membershipId);
2091
- }
1756
+ const initialPrepared = prepareSubmitContext(options, registrationCommandId, target, method, passId, membershipId, selection);
1757
+ const intentKey = registrationRequestIntentKey(buildRegistrationRequest(options, initialPrepared, method, passId, membershipId));
1758
+ const prepared = commandRef.current !== null && commandRef.current.intentKey !== intentKey ? {
1759
+ ...initialPrepared,
1760
+ registrationCommandId: deps.createClientCommandId()
1761
+ } : initialPrepared;
1762
+ const request = buildRegistrationRequest(options, prepared, method, passId, membershipId);
2092
1763
  commandRef.current = {
2093
1764
  id: prepared.registrationCommandId,
2094
1765
  intentKey
2095
1766
  };
2096
- const result = await options.api.registerForEvent({
1767
+ return await completeRegistrationSubmission(options, deps, prepared, await options.api.registerForEvent({
2097
1768
  ...request,
2098
1769
  registrationCommandId: prepared.registrationCommandId
2099
- });
2100
- return await completeRegistrationSubmission(options, deps, prepared, result, submitOptions);
1770
+ }));
2101
1771
  } catch (error) {
2102
1772
  if (error instanceof BlockApiError && error.tag === "registrationPreparationChanged") commandRef.current = null;
2103
1773
  const { message, action } = registrationErrorPresentation(error);
@@ -2121,7 +1791,7 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
2121
1791
  return pending;
2122
1792
  };
2123
1793
  }
2124
- async function completeRegistrationSubmission(options, deps, prepared, result, submitOptions) {
1794
+ async function completeRegistrationSubmission(options, deps, prepared, result) {
2125
1795
  if (result.demoBookingPass) storeDemoBookingPass(options.formData.email, result.demoBookingPass);
2126
1796
  switch (result.kind) {
2127
1797
  case "paymentStatusRequired":
@@ -2136,10 +1806,24 @@ async function completeRegistrationSubmission(options, deps, prepared, result, s
2136
1806
  outcome: { kind: "payment_status" }
2137
1807
  };
2138
1808
  case "checkoutRequired":
2139
- options.setPendingRegistrationReference(result.reference);
2140
1809
  savePendingCheckout(options, deps, prepared, result.submissionId, result.reference, result.mailingListSignupNote);
2141
- options.setStep("redirecting");
2142
- return redirectToCheckout(result.checkoutUrl, submitOptions);
1810
+ const subject = result.reference.scope === "occurrence" ? {
1811
+ scope: "occurrence",
1812
+ attendeeId: result.reference.attendeeId,
1813
+ submissionId: result.submissionId
1814
+ } : {
1815
+ scope: "wholeSeries",
1816
+ submissionId: result.submissionId
1817
+ };
1818
+ options.openPayment(result.checkout, subject, result.mailingListSignupNote);
1819
+ return {
1820
+ ok: true,
1821
+ outcome: {
1822
+ kind: "checkout_ready",
1823
+ checkout: result.checkout,
1824
+ subject
1825
+ }
1826
+ };
2143
1827
  case "waitlisted":
2144
1828
  options.setRegistrationResult({
2145
1829
  status: "waitlist",
@@ -2151,7 +1835,6 @@ async function completeRegistrationSubmission(options, deps, prepared, result, s
2151
1835
  break;
2152
1836
  case "bookingComplete":
2153
1837
  case "paymentDeferred":
2154
- options.setPendingRegistrationReference(result.reference);
2155
1838
  options.setRegistrationResult({
2156
1839
  status: "confirmed",
2157
1840
  message: result.summary.paymentMethod === "pass" ? result.summary.message : void 0,
@@ -2224,19 +1907,12 @@ function savePendingCheckout(options, deps, prepared, checkoutAttemptId, referen
2224
1907
  selectedEventId: options.registrationTarget?.selectedEventId ?? null,
2225
1908
  selectedAttendanceMode: options.selectedAttendanceMode,
2226
1909
  paymentSelection: prepared.selectionSnapshot,
1910
+ guests: options.guests,
1911
+ selectedSlidingAmount: options.selectedSlidingAmount,
1912
+ discountCode: options.discountCode,
2227
1913
  mailingListSignupNote
2228
1914
  });
2229
1915
  }
2230
- function redirectToCheckout(checkoutUrl, submitOptions) {
2231
- if (submitOptions?.redirect !== "manual") window.location.href = checkoutUrl;
2232
- return {
2233
- ok: true,
2234
- outcome: {
2235
- kind: "checkout_redirect",
2236
- checkoutUrl
2237
- }
2238
- };
2239
- }
2240
1916
  function registrationRequestIntentKey(request) {
2241
1917
  const { registrationCommandId: _command, altchaSolution: _proof, mailingListOptIn: _marketingConsent, checkout, ...intent } = request;
2242
1918
  return JSON.stringify({
@@ -2251,7 +1927,7 @@ function buildRegistrationRequest(options, prepared, method, passId, membershipI
2251
1927
  return {
2252
1928
  registrationCommandId: prepared.registrationCommandId,
2253
1929
  checkout: {
2254
- ...buildCheckoutReturnUrls("registration", EVENT_REGISTRATION_ANCHOR_ID, { [EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM]: prepared.registrationCommandId }),
1930
+ returnUrl: eventPaymentReturnUrl(prepared.registrationCommandId),
2255
1931
  discountCode: options.discountCode.trim() || void 0,
2256
1932
  collectionOptionKind: prepared.selectionSnapshot.method === "stripe" ? prepared.selectionSnapshot.collectionOptionKind : void 0
2257
1933
  },
@@ -2373,7 +2049,7 @@ function getRegistrationOccurrenceId(target) {
2373
2049
  return target?.subject.kind === "occurrence" ? target.subject.occurrenceId : "";
2374
2050
  }
2375
2051
  function shouldLoadRegistrationOptions(authState, step) {
2376
- return authState === "authenticated" || step === "select-tickets" || step === "enter-details" || step === "select-payment";
2052
+ return authState === "authenticated" || step === "select-tickets" || step === "fully-booked" || step === "enter-details" || step === "select-payment";
2377
2053
  }
2378
2054
  function getEligibilityFlowFlags(input) {
2379
2055
  const hasPass = (input.eligibility?.eligiblePasses.length ?? 0) > 0;
@@ -2472,132 +2148,544 @@ function useWaitlistClaimRestoration(input) {
2472
2148
  ]);
2473
2149
  }
2474
2150
  //#endregion
2475
- //#region ../blocks/src/system/runtime/nodes/event-registration/registrationCompletion.ts
2476
- function isSelectionSoldOut(availability) {
2477
- return availability?.kind === "unavailable" && availability.reason === "full";
2478
- }
2479
- function resolveRegistrationAvailabilityBlocker(input) {
2480
- if (input.hasWaitlistClaim) return null;
2481
- if (input.eligibilityError) return input.eligibilityError;
2482
- if (!input.eligibility) return "Registration is not available for this event.";
2483
- if (input.eligibility.registration.kind === "pending_payment") return null;
2484
- if (isSelectionSoldOut(input.eligibility.availability)) return null;
2485
- if (input.eligibility.availability.kind === "unavailable" && input.eligibility.availability.reason === "paymentUnavailable") return "Payment options are not available for this event.";
2486
- if (input.eligibility.availability.kind === "unavailable" || input.eligibility.registration.kind === "waitlisted") return "Registration is not available for this event.";
2487
- return null;
2151
+ //#region ../blocks/src/system/runtime/nodes/event-registration/useGuestRegistrationRecovery.ts
2152
+ /** Guest email is navigation permission only. Discard checks when the visitor
2153
+ * changes identity or selection before the response arrives. */
2154
+ function useGuestRegistrationRecovery(input) {
2155
+ const [checkingNextStep, setCheckingNextStep] = useState(false);
2156
+ const [recoveryMessage, setRecoveryMessage] = useState(null);
2157
+ const request = guestNextStepRequest(input);
2158
+ const requestKey = JSON.stringify([
2159
+ request,
2160
+ input.step,
2161
+ input.portalAuthState,
2162
+ input.admission
2163
+ ]);
2164
+ const currentRequest = useRef(requestKey);
2165
+ currentRequest.current = requestKey;
2166
+ const checkDetailsNextStep = () => checkGuestNextStep(input, request, setCheckingNextStep);
2167
+ const handleRecoveryEmail = () => recoverByEmail({
2168
+ ...input,
2169
+ checkingNextStep,
2170
+ check: checkDetailsNextStep,
2171
+ setRecoveryMessage,
2172
+ isCurrent: () => currentRequest.current === requestKey
2173
+ });
2174
+ return {
2175
+ checkingNextStep,
2176
+ recoveryMessage,
2177
+ checkDetailsNextStep,
2178
+ handleRecoveryEmail
2179
+ };
2488
2180
  }
2489
- function resolvePricingSelectionBlocker(input) {
2490
- if (input.selectionRequired && !input.hasValidSelection) return "Choose at least one available ticket to continue.";
2491
- return input.slidingScaleSelectionMessage;
2181
+ async function checkGuestNextStep(input, request, setCheckingNextStep) {
2182
+ const plan = resolveGuestNextStepCheck(input.admission, request);
2183
+ switch (plan.kind) {
2184
+ case "skip": return plan.outcome;
2185
+ case "blocked": return {
2186
+ kind: "unavailable",
2187
+ message: plan.message
2188
+ };
2189
+ case "check": break;
2190
+ }
2191
+ setCheckingNextStep(true);
2192
+ try {
2193
+ return await input.api.checkRegistrationNextStep(plan.request);
2194
+ } catch {
2195
+ return {
2196
+ kind: "unavailable",
2197
+ message: "We could not check availability. Please try again."
2198
+ };
2199
+ } finally {
2200
+ setCheckingNextStep(false);
2201
+ }
2202
+ }
2203
+ async function recoverByEmail(input) {
2204
+ if (input.checkingNextStep) return {
2205
+ ok: false,
2206
+ reason: "blocked",
2207
+ message: "Your booking is being checked."
2208
+ };
2209
+ const emailError = validateFormData(input.formData).email;
2210
+ input.setErrors(emailError ? { email: emailError } : {});
2211
+ input.setRecoveryMessage(null);
2212
+ if (emailError) return {
2213
+ ok: false,
2214
+ reason: "validation-failed",
2215
+ message: emailError
2216
+ };
2217
+ const next = await input.check();
2218
+ if (!input.isCurrent()) return {
2219
+ ok: false,
2220
+ reason: "blocked",
2221
+ message: "Your booking details changed. Please check again."
2222
+ };
2223
+ switch (next.kind) {
2224
+ case "continue":
2225
+ case "waitlist":
2226
+ input.onRecoveryChecked(next);
2227
+ return { ok: true };
2228
+ case "unavailable":
2229
+ input.setRecoveryMessage(next.message);
2230
+ return {
2231
+ ok: false,
2232
+ reason: "blocked",
2233
+ message: next.message
2234
+ };
2235
+ }
2236
+ }
2237
+ function guestNextStepRequest(input) {
2238
+ return input.registrationTarget ? {
2239
+ subject: toRegistrationRequestSubject(input.registrationTarget.subject, input.selectedAttendanceMode),
2240
+ selections: input.ticketSelections,
2241
+ selectedSlidingAmount: input.selectedSlidingAmount ?? void 0,
2242
+ email: input.formData.email,
2243
+ altchaSolution: input.altchaSolution
2244
+ } : null;
2492
2245
  }
2493
2246
  //#endregion
2494
- //#region ../blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.ts
2495
- function isCheckableRecovery(state) {
2496
- return state.kind === "ready" || state.kind === "processing" || state.kind === "temporarilyUnavailable";
2247
+ //#region ../blocks/src/system/runtime/nodes/event-registration/paymentResponse.ts
2248
+ /**
2249
+ * Verified response identity owns every follow-up action, including storage-free returns.
2250
+ * `requested` is the subject the client sent, if any: a subject-bound `notFound`
2251
+ * means the server lost that booking, which no amount of re-checking will fix.
2252
+ */
2253
+ function planPaymentResponse(response, intent, requested) {
2254
+ switch (response.kind) {
2255
+ case "checkout": return planCheckoutOutcome(response, intent);
2256
+ case "notFound": return requested ? assistance(requested) : { kind: "unresolved" };
2257
+ case "reconciliationRequired": return { kind: "unresolved" };
2258
+ default: return assertNever(response);
2259
+ }
2497
2260
  }
2498
- function assistanceRequired(recovery) {
2261
+ function assistance(subject) {
2499
2262
  return {
2500
- kind: "assistanceRequired",
2501
- subject: recovery.subject,
2502
- mailingListSignupNote: recovery.mailingListSignupNote
2263
+ kind: "recovery",
2264
+ state: {
2265
+ kind: "assistanceRequired",
2266
+ subject
2267
+ }
2503
2268
  };
2504
2269
  }
2505
- function applyStatusOutcome(outcome, recovery, setters, setRecovery) {
2506
- if (outcome.kind === "resolved") {
2507
- setters.setPendingRegistrationReference(outcome.reference);
2508
- setters.setRegistrationResult({
2509
- status: "confirmed",
2510
- mailingListSignupNote: recovery.mailingListSignupNote
2511
- });
2512
- setRecovery(null);
2513
- setters.setStep("success");
2514
- return;
2515
- }
2270
+ function planCheckoutOutcome({ outcome, subject }, intent) {
2516
2271
  switch (outcome.kind) {
2517
- case "paymentProcessing":
2518
- setRecovery({
2519
- ...recovery,
2520
- kind: "processing"
2521
- });
2522
- return;
2523
- case "paymentAvailable":
2524
- setRecovery({
2525
- ...recovery,
2272
+ case "resolved": return { kind: "confirmed" };
2273
+ case "paymentAvailable": return intent === "resume" ? {
2274
+ kind: "payment",
2275
+ checkout: outcome.checkout,
2276
+ subject
2277
+ } : {
2278
+ kind: "recovery",
2279
+ state: {
2526
2280
  kind: "paymentAvailable",
2527
- checkoutUrl: outcome.checkoutUrl
2528
- });
2529
- return;
2530
- case "paymentCheckUnavailable":
2531
- setRecovery({
2532
- ...recovery,
2533
- kind: "temporarilyUnavailable"
2534
- });
2535
- return;
2536
- case "paymentRetryAvailable":
2537
- setRecovery({
2538
- ...recovery,
2539
- kind: "retryAvailable"
2540
- });
2541
- return;
2281
+ checkout: outcome.checkout,
2282
+ subject
2283
+ }
2284
+ };
2542
2285
  case "abandoned":
2543
- case "notFound":
2286
+ case "paymentRetryAvailable": return intent === "abandon" ? { kind: "restartBooking" } : {
2287
+ kind: "recovery",
2288
+ state: {
2289
+ kind: "retryAvailable",
2290
+ subject
2291
+ }
2292
+ };
2293
+ case "paymentProcessing": return {
2294
+ kind: "recovery",
2295
+ state: {
2296
+ kind: "processing",
2297
+ subject
2298
+ }
2299
+ };
2300
+ case "paymentCheckUnavailable": return {
2301
+ kind: "recovery",
2302
+ state: {
2303
+ kind: "temporarilyUnavailable",
2304
+ subject
2305
+ }
2306
+ };
2544
2307
  case "reconciliationRequired":
2545
- case "paymentAssistanceRequired":
2546
- setRecovery(assistanceRequired(recovery));
2547
- return;
2308
+ case "paymentAssistanceRequired": return assistance(subject);
2309
+ default: return assertNever(outcome);
2548
2310
  }
2549
2311
  }
2550
- function usePaymentStatusRecovery(options) {
2551
- const [recovery, setRecovery] = useState(null);
2552
- const checkRef = useRef(null);
2312
+ function paymentCheckEvidence(result) {
2313
+ switch (result.kind) {
2314
+ case "unreachable": return { kind: "inconclusive" };
2315
+ case "notFound": return { kind: "bindingLost" };
2316
+ case "reconciliationRequired": return { kind: "paymentUnderway" };
2317
+ case "checkout": return checkoutEvidence(result);
2318
+ default: return assertNever(result);
2319
+ }
2320
+ }
2321
+ function checkoutEvidence(response) {
2322
+ const { outcome } = response;
2323
+ switch (outcome.kind) {
2324
+ case "paymentCheckUnavailable": return { kind: "inconclusive" };
2325
+ case "paymentAvailable": return {
2326
+ kind: "payable",
2327
+ checkout: outcome.checkout,
2328
+ response
2329
+ };
2330
+ case "paymentProcessing":
2331
+ case "reconciliationRequired": return { kind: "paymentUnderway" };
2332
+ case "resolved":
2333
+ case "abandoned":
2334
+ case "paymentRetryAvailable":
2335
+ case "paymentAssistanceRequired": return {
2336
+ kind: "terminal",
2337
+ response
2338
+ };
2339
+ default: return assertNever(outcome);
2340
+ }
2341
+ }
2342
+ function planMountedPaymentRefresh(result, mounted) {
2343
+ const evidence = paymentCheckEvidence(result);
2344
+ switch (evidence.kind) {
2345
+ case "inconclusive": return { kind: "stay" };
2346
+ case "paymentUnderway": return { kind: "poll" };
2347
+ case "bindingLost": return {
2348
+ kind: "leave",
2349
+ transition: assistance(mounted.subject)
2350
+ };
2351
+ case "payable": return evidence.checkout.sessionId === mounted.sessionId ? { kind: "stay" } : {
2352
+ kind: "leave",
2353
+ transition: planCheckoutOutcome(evidence.response, "resume")
2354
+ };
2355
+ case "terminal": return {
2356
+ kind: "leave",
2357
+ transition: planCheckoutOutcome(evidence.response, "check")
2358
+ };
2359
+ default: return assertNever(evidence);
2360
+ }
2361
+ }
2362
+ function paymentResponseNeedsPolling(response) {
2363
+ if (response.kind !== "checkout") return response.kind === "reconciliationRequired";
2364
+ return paymentOutcomeNeedsPolling(response.outcome);
2365
+ }
2366
+ function paymentOutcomeNeedsPolling(outcome) {
2367
+ return outcome.kind === "paymentProcessing" || outcome.kind === "reconciliationRequired" || outcome.kind === "paymentCheckUnavailable";
2368
+ }
2369
+ //#endregion
2370
+ //#region ../blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.ts
2371
+ function usePaymentRuntime() {
2372
+ const [state, setState] = useState({ kind: "idle" });
2373
+ const note = useRef(void 0);
2374
+ const operation = useRef(null);
2375
+ const generation = useRef(0);
2376
+ const appliedResult = useRef(null);
2377
+ const current = useRef(state);
2378
+ current.current = state;
2379
+ useEffect(() => () => {
2380
+ generation.current += 1;
2381
+ }, []);
2553
2382
  return {
2554
- recovery,
2555
- showRecovery: useCallback((next) => setRecovery(next), []),
2556
- check: useCallback(() => {
2557
- if (!recovery || !isCheckableRecovery(recovery) || checkRef.current) return;
2558
- const pending = (async () => {
2559
- setRecovery({
2560
- ...recovery,
2561
- kind: "checking"
2383
+ state,
2384
+ setState,
2385
+ note,
2386
+ operation,
2387
+ generation,
2388
+ appliedResult,
2389
+ current
2390
+ };
2391
+ }
2392
+ function usePaymentTransitions(options, runtime) {
2393
+ const { setState, note, generation, operation } = runtime;
2394
+ const openPayment = useCallback((checkout, subject, mailingListSignupNote) => {
2395
+ if (mailingListSignupNote) note.current = mailingListSignupNote;
2396
+ rememberEventPayment(subject.submissionId, checkout.sessionId);
2397
+ setState({
2398
+ kind: "payment",
2399
+ checkout,
2400
+ subject
2401
+ });
2402
+ options.setStep("payment");
2403
+ }, [
2404
+ options.setStep,
2405
+ setState,
2406
+ note
2407
+ ]);
2408
+ const verify = useCallback((request, resumePayment = true) => {
2409
+ if (operation.current) return;
2410
+ generation.current += 1;
2411
+ setState({
2412
+ kind: "verifying",
2413
+ request,
2414
+ resumePayment
2415
+ });
2416
+ options.setStep("verifying-payment");
2417
+ }, [
2418
+ options.setStep,
2419
+ operation,
2420
+ generation,
2421
+ setState
2422
+ ]);
2423
+ const restartFrom = useCallback((step) => {
2424
+ clearEventPayment(options.api.config.siteId);
2425
+ options.beginNewSubmission();
2426
+ setState({ kind: "idle" });
2427
+ options.setOptionsRefreshKey((value) => value + 1);
2428
+ options.setStep(step);
2429
+ }, [options, setState]);
2430
+ const resetBooking = useCallback(() => restartFrom("select-tickets"), [restartFrom]);
2431
+ const returnToPaymentOptions = useCallback(() => restartFrom(options.detailsInPage() ? "select-payment" : "select-tickets"), [restartFrom, options]);
2432
+ const applyTransition = useCallback((transition) => {
2433
+ switch (transition.kind) {
2434
+ case "confirmed":
2435
+ options.setRegistrationResult({
2436
+ status: "confirmed",
2437
+ mailingListSignupNote: note.current
2562
2438
  });
2563
- try {
2564
- applyStatusOutcome(await options.api.resolveEventCheckoutReturn({
2565
- kind: "checkStatus",
2566
- subject: recovery.subject
2567
- }), recovery, options, setRecovery);
2568
- } catch {
2569
- setRecovery({
2570
- ...recovery,
2571
- kind: "temporarilyUnavailable"
2572
- });
2439
+ clearEventPayment(options.api.config.siteId);
2440
+ setState({ kind: "idle" });
2441
+ options.setStep("success");
2442
+ return;
2443
+ case "payment": return openPayment(transition.checkout, transition.subject);
2444
+ case "restartBooking": return resetBooking();
2445
+ case "recovery":
2446
+ setState({
2447
+ kind: "recovery",
2448
+ state: {
2449
+ ...transition.state,
2450
+ mailingListSignupNote: note.current
2451
+ }
2452
+ });
2453
+ options.setStep("payment-status");
2454
+ return;
2455
+ case "unresolved": return options.setStep("verifying-timeout");
2456
+ default: return assertNever(transition);
2457
+ }
2458
+ }, [
2459
+ openPayment,
2460
+ resetBooking,
2461
+ options,
2462
+ setState,
2463
+ note
2464
+ ]);
2465
+ return {
2466
+ openPayment,
2467
+ verify,
2468
+ applyOutcome: useCallback((response, intent, requested) => applyTransition(planPaymentResponse(response, intent, requested)), [applyTransition]),
2469
+ applyTransition,
2470
+ resetBooking,
2471
+ returnToPaymentOptions
2472
+ };
2473
+ }
2474
+ /** A session-only return has no client subject; the server's envelope must supply one. */
2475
+ function requestedSubject(request) {
2476
+ return request.kind === "resolveSession" ? null : request.subject;
2477
+ }
2478
+ function usePaymentChecks(options, runtime, transitions) {
2479
+ const { state, appliedResult, current } = runtime;
2480
+ const request = state.kind === "verifying" ? state.request : null;
2481
+ const polling = usePolling(request ? {
2482
+ restartKey: JSON.stringify(request),
2483
+ pollFn: () => options.api.resolveEventCheckoutReturn(request),
2484
+ isComplete: (result) => !paymentResponseNeedsPolling(result),
2485
+ maxAttempts: 10,
2486
+ interval: 1e3
2487
+ } : null);
2488
+ useEffect(() => {
2489
+ if (polling.status === "completed" && polling.result && request && appliedResult.current !== polling.result) {
2490
+ appliedResult.current = polling.result;
2491
+ transitions.applyOutcome(polling.result, state.kind === "verifying" && state.resumePayment ? "resume" : "check", requestedSubject(request));
2492
+ } else if (polling.status === "timeout") options.setStep("verifying-timeout");
2493
+ }, [
2494
+ polling.status,
2495
+ polling.result,
2496
+ request,
2497
+ transitions.applyOutcome,
2498
+ options.setStep,
2499
+ appliedResult
2500
+ ]);
2501
+ const check = useCallback((resumePayment = false) => {
2502
+ appliedResult.current = null;
2503
+ const active = current.current;
2504
+ if (active.kind === "payment") transitions.verify({
2505
+ kind: "checkStatus",
2506
+ subject: active.subject
2507
+ }, resumePayment);
2508
+ else if (active.kind === "recovery") transitions.verify({
2509
+ kind: "checkStatus",
2510
+ subject: active.state.subject
2511
+ }, resumePayment);
2512
+ else if (active.kind === "verifying") {
2513
+ options.setStep("verifying-payment");
2514
+ polling.retry();
2515
+ }
2516
+ }, [
2517
+ transitions.verify,
2518
+ polling.retry,
2519
+ options.setStep,
2520
+ appliedResult,
2521
+ current
2522
+ ]);
2523
+ const checkStatus = useCallback(() => check(), [check]);
2524
+ return {
2525
+ check: checkStatus,
2526
+ retryPaymentLoad: useCallback(() => check(true), [check]),
2527
+ verification: {
2528
+ ...polling,
2529
+ retry: checkStatus,
2530
+ retryReturn: checkStatus
2531
+ }
2532
+ };
2533
+ }
2534
+ function usePaymentAbandonment(options, runtime, transitions) {
2535
+ const { operation, generation, current, setState } = runtime;
2536
+ return useCallback(() => {
2537
+ if (operation.current) return operation.current;
2538
+ const active = current.current;
2539
+ if (active.kind !== "payment") return Promise.resolve();
2540
+ const token = ++generation.current;
2541
+ setState({
2542
+ kind: "abandoning",
2543
+ subject: active.subject
2544
+ });
2545
+ const pending = options.api.resolveEventCheckoutReturn({
2546
+ kind: "abandon",
2547
+ subject: active.subject
2548
+ }).then((outcome) => {
2549
+ if (token !== generation.current) return;
2550
+ transitions.applyOutcome(outcome, "abandon", active.subject);
2551
+ }).catch(() => {
2552
+ if (token !== generation.current) return;
2553
+ setState({
2554
+ kind: "recovery",
2555
+ state: {
2556
+ kind: "temporarilyUnavailable",
2557
+ subject: active.subject,
2558
+ mailingListSignupNote: runtime.note.current
2573
2559
  }
2574
- })();
2575
- checkRef.current = pending;
2576
- pending.finally(() => {
2577
- if (checkRef.current === pending) checkRef.current = null;
2578
2560
  });
2579
- }, [options, recovery]),
2580
- continuePayment: useCallback(() => {
2581
- if (recovery?.kind === "paymentAvailable") window.location.assign(recovery.checkoutUrl);
2582
- }, [recovery]),
2583
- returnToPayment: useCallback(() => {
2584
- if (recovery?.kind !== "retryAvailable") return;
2585
- options.beginNewSubmission();
2586
- setRecovery(null);
2587
- options.setOptionsRefreshKey((value) => value + 1);
2588
- options.setStep("select-payment");
2589
- }, [options, recovery]),
2590
- returnToBooking: useCallback(() => {
2591
- if (recovery?.kind !== "assistanceRequired") return;
2592
- options.beginNewSubmission();
2593
- setRecovery(null);
2594
- options.setOptionsRefreshKey((value) => value + 1);
2595
- options.setStep("select-tickets");
2596
- }, [options, recovery])
2561
+ options.setStep("payment-status");
2562
+ });
2563
+ operation.current = pending;
2564
+ pending.finally(() => {
2565
+ if (operation.current === pending) operation.current = null;
2566
+ });
2567
+ return pending;
2568
+ }, [
2569
+ options.api,
2570
+ transitions.applyOutcome,
2571
+ operation,
2572
+ generation,
2573
+ current,
2574
+ setState,
2575
+ options.setStep,
2576
+ runtime.note
2577
+ ]);
2578
+ }
2579
+ /**
2580
+ * Returning to the tab (often from a banking app mid-authentication) must not
2581
+ * unmount Stripe on inconclusive evidence; `planMountedPaymentRefresh` decides.
2582
+ */
2583
+ function usePaymentBackgroundCheck(options, runtime, transitions) {
2584
+ const { current, generation, operation } = runtime;
2585
+ const inFlight = useRef(false);
2586
+ return useCallback(() => {
2587
+ const active = current.current;
2588
+ if (active.kind !== "payment" || operation.current || inFlight.current) return;
2589
+ const token = generation.current;
2590
+ inFlight.current = true;
2591
+ const request = {
2592
+ kind: "checkStatus",
2593
+ subject: active.subject
2594
+ };
2595
+ const unreachable = { kind: "unreachable" };
2596
+ options.api.resolveEventCheckoutReturn(request).catch(() => unreachable).then((result) => {
2597
+ if (token !== generation.current || current.current !== active) return;
2598
+ const refresh = planMountedPaymentRefresh(result, {
2599
+ sessionId: active.checkout.sessionId,
2600
+ subject: active.subject
2601
+ });
2602
+ switch (refresh.kind) {
2603
+ case "stay": return;
2604
+ case "poll": return transitions.verify(request, false);
2605
+ case "leave": return transitions.applyTransition(refresh.transition);
2606
+ default: return assertNever(refresh);
2607
+ }
2608
+ }).finally(() => {
2609
+ inFlight.current = false;
2610
+ });
2611
+ }, [
2612
+ options.api,
2613
+ transitions.verify,
2614
+ transitions.applyTransition,
2615
+ current,
2616
+ generation,
2617
+ operation
2618
+ ]);
2619
+ }
2620
+ function usePaymentRefresh(state, check) {
2621
+ useEffect(() => {
2622
+ if (state.kind !== "payment") return;
2623
+ const timeout = window.setTimeout(check, Math.max(0, Date.parse(state.checkout.expiresAt) - Date.now()));
2624
+ const visible = () => {
2625
+ if (document.visibilityState === "visible") check();
2626
+ };
2627
+ const restored = (event) => {
2628
+ if (event.persisted) check();
2629
+ };
2630
+ document.addEventListener("visibilitychange", visible);
2631
+ window.addEventListener("pageshow", restored);
2632
+ return () => {
2633
+ clearTimeout(timeout);
2634
+ document.removeEventListener("visibilitychange", visible);
2635
+ window.removeEventListener("pageshow", restored);
2636
+ };
2637
+ }, [state, check]);
2638
+ }
2639
+ function usePaymentStatusRecovery(options) {
2640
+ const runtime = usePaymentRuntime();
2641
+ const transitions = usePaymentTransitions(options, runtime);
2642
+ const checks = usePaymentChecks(options, runtime, transitions);
2643
+ const backToBooking = usePaymentAbandonment(options, runtime, transitions);
2644
+ const backgroundCheck = usePaymentBackgroundCheck(options, runtime, transitions);
2645
+ const { state, setState, generation, note } = runtime;
2646
+ usePaymentRefresh(state, backgroundCheck);
2647
+ const recovery = state.kind === "recovery" ? state.state : null;
2648
+ return {
2649
+ state,
2650
+ recovery,
2651
+ showRecovery: useCallback((next) => {
2652
+ generation.current += 1;
2653
+ note.current = next?.mailingListSignupNote;
2654
+ setState(next ? {
2655
+ kind: "recovery",
2656
+ state: next
2657
+ } : { kind: "idle" });
2658
+ }, [
2659
+ generation,
2660
+ note,
2661
+ setState
2662
+ ]),
2663
+ restoreMailingListNote: (value) => {
2664
+ note.current = value;
2665
+ },
2666
+ openPayment: transitions.openPayment,
2667
+ verify: transitions.verify,
2668
+ ...checks,
2669
+ backToBooking,
2670
+ continuePayment: () => {
2671
+ if (recovery?.kind === "paymentAvailable") transitions.openPayment(recovery.checkout, recovery.subject);
2672
+ },
2673
+ returnToPayment: () => {
2674
+ if (recovery?.kind === "retryAvailable") transitions.returnToPaymentOptions();
2675
+ },
2676
+ returnToBooking: () => {
2677
+ if (recovery?.kind === "retryAvailable") transitions.resetBooking();
2678
+ }
2597
2679
  };
2598
2680
  }
2599
2681
  //#endregion
2600
2682
  //#region ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.ts
2683
+ function resolveAvailableAttendanceMode(event, selected) {
2684
+ if (!event) return null;
2685
+ const mode = event.deliveryMode ?? "in_person";
2686
+ if (mode === "online" || mode === "in_person") return mode;
2687
+ return selected && event.attendanceModes?.some((item) => item.mode === selected) ? selected : null;
2688
+ }
2601
2689
  function usesTieredPricing(event) {
2602
2690
  return event !== null && event !== void 0 ? publicEventPricingStrategy(event.pricing) === "tiered" : false;
2603
2691
  }
@@ -2608,13 +2696,15 @@ const eventRegistrationWizardDeps = {
2608
2696
  useWaitlistClaim,
2609
2697
  useEventRegistrationStripeReturn,
2610
2698
  useEventRegistrationSubmit,
2611
- useDiscountCodeUi
2699
+ useDiscountCodeUi,
2700
+ useBookingNowIso
2612
2701
  };
2613
2702
  function useEventRegistrationWizard(options) {
2614
2703
  return useEventRegistrationWizardWithDeps(options, eventRegistrationWizardDeps);
2615
2704
  }
2616
2705
  function useEventRegistrationWizardWithDeps(options, deps) {
2617
2706
  const api = useBlockApi();
2707
+ const nowIso = deps.useBookingNowIso();
2618
2708
  const registrationEvents = useMemo(() => [...toRegistrationEvents(options.events ?? []), ...toWholeSeriesRegistrationEvents(options.events ?? [])], [options.events]);
2619
2709
  const scopedEvents = useMemo(() => {
2620
2710
  return resolveEventRegistrationScopedEvents(registrationEvents, {
@@ -2653,19 +2743,24 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2653
2743
  const [errorAction, setErrorAction] = useState("retry");
2654
2744
  const [optionsRefreshKey, setOptionsRefreshKey] = useState(0);
2655
2745
  const [altchaSolution, setAltchaSolution] = useState(null);
2656
- const [checkingNextStep, setCheckingNextStep] = useState(false);
2657
2746
  const { authState: portalAuthState, profile: portalProfile } = deps.usePortalAuth();
2658
2747
  const waitlistClaim = deps.useWaitlistClaim();
2659
2748
  const [showMagicLinkModal, setShowMagicLinkModal] = useState(false);
2660
- const [pendingRegistrationReference, setPendingRegistrationReference] = useState(null);
2661
2749
  const [paymentStepState, setPaymentStepState] = useState({
2662
2750
  status: "loading",
2663
2751
  canContinue: false,
2664
2752
  buttonText: "Continue",
2665
2753
  showLoginPrompt: false
2666
2754
  });
2755
+ const selectionLocked = [
2756
+ "payment",
2757
+ "submitting",
2758
+ "verifying-payment",
2759
+ "verifying-timeout",
2760
+ "payment-status"
2761
+ ].includes(step);
2667
2762
  useEffect(() => {
2668
- if (portalAuthState !== "authenticated" || !portalProfile) return;
2763
+ if (selectionLocked || portalAuthState !== "authenticated" || !portalProfile) return;
2669
2764
  setFormData((prev) => ({
2670
2765
  ...prev,
2671
2766
  email: portalProfile.email,
@@ -2687,16 +2782,17 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2687
2782
  step: nextStep
2688
2783
  });
2689
2784
  }, []);
2690
- const { recovery: paymentStatusRecovery, showRecovery: setPaymentStatusRecovery, check: handleCheckPaymentStatus, continuePayment: handleContinueExistingPayment, returnToPayment: handleReturnToPayment, returnToBooking: handleReturnToBooking } = usePaymentStatusRecovery({
2785
+ const paymentController = usePaymentStatusRecovery({
2691
2786
  api,
2692
2787
  beginNewSubmission: () => {
2693
2788
  commandRef.current = null;
2694
2789
  },
2695
- setPendingRegistrationReference,
2696
2790
  setRegistrationResult,
2697
2791
  setOptionsRefreshKey,
2698
- setStep
2792
+ setStep,
2793
+ detailsInPage: () => Object.keys(validateFormData(formData)).length === 0
2699
2794
  });
2795
+ const { recovery: paymentStatusRecovery, showRecovery: setPaymentStatusRecovery, check: handleCheckPaymentStatus, continuePayment: handleContinueExistingPayment, returnToPayment: handleReturnToPayment, returnToBooking: handleReturnToBooking } = paymentController;
2700
2796
  const usesTicketTypeSelections = usesTieredPricing(currentEvent);
2701
2797
  const eventTicketTypesForSelection = useMemo(() => getVisibleTicketTypes({
2702
2798
  event: currentEvent,
@@ -2795,29 +2891,17 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2795
2891
  dispatchFlow({
2796
2892
  type: "set-authenticated",
2797
2893
  isAuthenticated: portalAuthState === "authenticated"
2798
- });
2799
- }, [portalAuthState]);
2800
- useEffect(() => {
2801
- const event = selectedEvent ?? preSelectedEvent;
2802
- if (!event) {
2803
- if (selectedAttendanceMode !== null) setSelectedAttendanceMode(null);
2804
- return;
2805
- }
2806
- const deliveryMode = event.deliveryMode ?? "in_person";
2807
- if (deliveryMode === "online") {
2808
- if (selectedAttendanceMode !== "online") setSelectedAttendanceMode("online");
2809
- return;
2810
- }
2811
- if (deliveryMode === "in_person") {
2812
- if (selectedAttendanceMode !== "in_person") setSelectedAttendanceMode("in_person");
2813
- return;
2814
- }
2815
- if (selectedAttendanceMode && (event.attendanceModes ?? []).some((mode) => mode.mode === selectedAttendanceMode)) return;
2816
- setSelectedAttendanceMode(null);
2894
+ });
2895
+ }, [portalAuthState]);
2896
+ useEffect(() => {
2897
+ if (selectionLocked) return;
2898
+ const next = resolveAvailableAttendanceMode(selectedEvent ?? preSelectedEvent, selectedAttendanceMode);
2899
+ if (next !== selectedAttendanceMode) setSelectedAttendanceMode(next);
2817
2900
  }, [
2818
2901
  preSelectedEvent,
2819
2902
  selectedAttendanceMode,
2820
- selectedEvent
2903
+ selectedEvent,
2904
+ selectionLocked
2821
2905
  ]);
2822
2906
  useEffect(() => {
2823
2907
  dispatchFlow({
@@ -2825,25 +2909,26 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2825
2909
  eligibility
2826
2910
  });
2827
2911
  }, [eligibility]);
2828
- const verification = deps.useEventRegistrationStripeReturn({
2829
- api,
2830
- events: registrationEvents,
2831
- registrationSubject: registrationTarget?.subject ?? null,
2832
- step,
2833
- pendingRegistrationReference,
2834
- setFormData,
2835
- setTicketsCount,
2836
- setSelectedEvent,
2837
- setSelectedAttendanceMode,
2838
- setTicketSelections: (lines) => setSelectedTicketLines(normalizeTicketSelectionLines(lines)),
2839
- setPaymentSelection: (selection) => dispatchFlow({
2840
- type: "set-manual-payment-selection",
2841
- selection
2842
- }),
2843
- setPendingRegistrationReference,
2844
- setStep,
2845
- setRegistrationResult: (result) => setRegistrationResult(result),
2846
- setErrorMessage
2912
+ const verification = paymentController.verification;
2913
+ deps.useEventRegistrationStripeReturn({
2914
+ siteId: api.config.siteId,
2915
+ verify: paymentController.verify,
2916
+ restore: (pending) => {
2917
+ paymentController.restoreMailingListNote(pending.mailingListSignupNote);
2918
+ setFormData(pending.formData);
2919
+ setSelectedTicketLines(pending.ticketSelections);
2920
+ setTicketsCount(getTicketSelectionTotal(pending.ticketSelections));
2921
+ setGuests(pending.guests);
2922
+ discountUi.setDiscountCode(pending.discountCode);
2923
+ setSelectedSlidingAmountInput(pending.selectedSlidingAmount === null ? "" : formatCentsAsCurrencyInput(pending.selectedSlidingAmount));
2924
+ setSelectedAttendanceMode(pending.selectedAttendanceMode ?? null);
2925
+ const event = registrationEvents.find((item) => item.selectionId === pending.selectedEventId);
2926
+ if (event) setSelectedEvent(event);
2927
+ dispatchFlow({
2928
+ type: "set-manual-payment-selection",
2929
+ selection: pending.paymentSelection
2930
+ });
2931
+ }
2847
2932
  });
2848
2933
  const requireGuestDetails = useMemo(() => {
2849
2934
  return (selectedEvent ?? preSelectedEvent)?.requireGuestDetails === true;
@@ -2887,6 +2972,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2887
2972
  visibleOptions: visibleTicketTypes
2888
2973
  });
2889
2974
  useEffect(() => {
2975
+ if (selectionLocked) return;
2890
2976
  if (!usesTicketTypeSelections) {
2891
2977
  if (selectedTicketLines.length > 0) setSelectedTicketLines([]);
2892
2978
  return;
@@ -2909,6 +2995,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2909
2995
  usesTicketTypeSelections
2910
2996
  ]);
2911
2997
  useEffect(() => {
2998
+ if (selectionLocked) return;
2912
2999
  if (!requireGuestDetails) {
2913
3000
  if (guests.length > 0) setGuests([]);
2914
3001
  return;
@@ -2918,19 +3005,16 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2918
3005
  if (guests.length > 0) setGuests([]);
2919
3006
  return;
2920
3007
  }
2921
- if (guests.length !== guestCount) {
2922
- const newGuests = [];
2923
- for (let i = 0; i < guestCount; i++) newGuests.push(guests[i] ?? {
2924
- name: "",
2925
- email: "",
2926
- phone: ""
2927
- });
2928
- setGuests(newGuests);
2929
- }
3008
+ if (guests.length !== guestCount) setGuests(Array.from({ length: guestCount }, (_, index) => guests[index] ?? {
3009
+ name: "",
3010
+ email: "",
3011
+ phone: ""
3012
+ }));
2930
3013
  }, [
2931
3014
  ticketsCount,
2932
3015
  requireGuestDetails,
2933
- guests
3016
+ guests,
3017
+ selectionLocked
2934
3018
  ]);
2935
3019
  const validateForm = () => {
2936
3020
  const newErrors = validateFormData(formData, Boolean(currentEvent?.consent));
@@ -2959,49 +3043,55 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2959
3043
  selectedEventSlidingScale,
2960
3044
  selectedSlidingAmount
2961
3045
  ]);
2962
- const checkDetailsNextStep = async () => {
2963
- if (portalAuthState === "authenticated" || waitlistClaim.claim || eligibility?.availability.kind === "available" || !registrationTarget) return { kind: canRecoverToWaitlist(eligibility) ? "waitlist" : "continue" };
2964
- setCheckingNextStep(true);
2965
- try {
2966
- return await api.checkRegistrationNextStep({
2967
- subject: toRegistrationRequestSubject(registrationTarget.subject, selectedAttendanceMode),
2968
- selections: ticketSelections,
2969
- selectedSlidingAmount: selectedSlidingAmount ?? void 0,
2970
- email: formData.email,
2971
- altchaSolution
2972
- });
2973
- } catch {
2974
- return {
2975
- kind: "unavailable",
2976
- message: "We could not check availability. Please try again."
2977
- };
2978
- } finally {
2979
- setCheckingNextStep(false);
2980
- }
2981
- };
3046
+ const { admission, ticketContinuation, handleTicketsComplete, onRecoveryChecked } = useRegistrationAdmission({
3047
+ eligibility,
3048
+ eligibilityError,
3049
+ eligibilityLoading,
3050
+ hasWaitlistClaim: waitlistClaim.claim !== null,
3051
+ isAuthenticated: portalAuthState === "authenticated",
3052
+ existingRegistrationLoading: existingRegistrationResult.isLoading,
3053
+ ticketSelectionRequired: ticketTypeSelectionRequired,
3054
+ hasValidTicketSelection,
3055
+ dispatchFlow
3056
+ });
3057
+ const { checkingNextStep, recoveryMessage, checkDetailsNextStep, handleRecoveryEmail } = useGuestRegistrationRecovery({
3058
+ api,
3059
+ formData,
3060
+ onRecoveryChecked,
3061
+ setErrors,
3062
+ admission,
3063
+ step,
3064
+ portalAuthState,
3065
+ registrationTarget,
3066
+ selectedAttendanceMode,
3067
+ ticketSelections,
3068
+ selectedSlidingAmount,
3069
+ altchaSolution
3070
+ });
2982
3071
  const handleDetailsComplete = async () => {
2983
- const blockDetailsCompletion = (message) => {
2984
- setErrorAction("places");
2985
- setErrorMessage(message);
2986
- setStep("error");
3072
+ const blockDetailsCompletion = (blocker) => {
3073
+ if (blocker.kind === "unavailable") {
3074
+ setErrorAction("places");
3075
+ setErrorMessage(blocker.message);
3076
+ setStep("error");
3077
+ }
2987
3078
  return {
2988
3079
  ok: false,
2989
3080
  reason: "blocked",
2990
- message
3081
+ message: blocker.message
2991
3082
  };
2992
3083
  };
2993
- const availabilityBlocker = resolveRegistrationAvailabilityBlocker({
2994
- hasWaitlistClaim: waitlistClaim.claim !== null,
2995
- eligibility,
2996
- eligibilityError
2997
- });
3084
+ const availabilityBlocker = resolveRegistrationAvailabilityBlocker(admission);
2998
3085
  if (availabilityBlocker) return blockDetailsCompletion(availabilityBlocker);
2999
3086
  const pricingSelectionBlocker = resolvePricingSelectionBlocker({
3000
3087
  selectionRequired: ticketTypeSelectionRequired,
3001
3088
  hasValidSelection: hasValidTicketSelection,
3002
3089
  slidingScaleSelectionMessage
3003
3090
  });
3004
- if (pricingSelectionBlocker) return blockDetailsCompletion(pricingSelectionBlocker);
3091
+ if (pricingSelectionBlocker) return blockDetailsCompletion({
3092
+ kind: "unavailable",
3093
+ message: pricingSelectionBlocker
3094
+ });
3005
3095
  if (!validateForm()) {
3006
3096
  setGuestSectionShake(true);
3007
3097
  return {
@@ -3011,7 +3101,10 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3011
3101
  };
3012
3102
  }
3013
3103
  const next = await checkDetailsNextStep();
3014
- if (next.kind === "unavailable") return blockDetailsCompletion(next.message);
3104
+ if (next.kind === "unavailable") return blockDetailsCompletion({
3105
+ kind: "unavailable",
3106
+ message: next.message
3107
+ });
3015
3108
  const canJoinWaitlist = next.kind === "waitlist";
3016
3109
  if (!canJoinWaitlist && requiresPaymentStep({
3017
3110
  eventRequiresPayment,
@@ -3028,7 +3121,10 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3028
3121
  eventRequiresPayment,
3029
3122
  canJoinWaitlist
3030
3123
  });
3031
- if (!submitPlan.ok) return blockDetailsCompletion(submitPlan.message);
3124
+ if (!submitPlan.ok) return blockDetailsCompletion({
3125
+ kind: "unavailable",
3126
+ message: submitPlan.message
3127
+ });
3032
3128
  const submitResult = await handleSubmit(submitPlan.args.method, submitPlan.args.passId, submitPlan.args.membershipId, submitPlan.args.selection);
3033
3129
  if (!submitResult.ok) return {
3034
3130
  ok: false,
@@ -3076,6 +3172,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3076
3172
  const selectionMethod = paymentSelection.method;
3077
3173
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
3078
3174
  const handleSubmit = deps.useEventRegistrationSubmit({
3175
+ openPayment: paymentController.openPayment,
3079
3176
  commandRef,
3080
3177
  setErrorAction,
3081
3178
  api,
@@ -3091,7 +3188,6 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3091
3188
  discountCode: discountUi.discountCode,
3092
3189
  claimToken: waitlistClaim.token,
3093
3190
  waitlistEntryId: waitlistClaim.entryId,
3094
- setPendingRegistrationReference,
3095
3191
  setStep,
3096
3192
  setRegistrationResult: (result) => setRegistrationResult(result),
3097
3193
  setErrorMessage,
@@ -3113,7 +3209,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3113
3209
  });
3114
3210
  };
3115
3211
  const handleRetry = () => recoverRegistrationError(resolveRegistrationRecoveryAction(step, errorAction), {
3116
- canJoinWaitlist: canRecoverToWaitlist(eligibility),
3212
+ canJoinWaitlist: admission.kind === "waitlist",
3117
3213
  refreshOptions: () => setOptionsRefreshKey((value) => value + 1),
3118
3214
  clearError: () => setErrorMessage(""),
3119
3215
  setStep,
@@ -3124,8 +3220,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3124
3220
  setSelectedTicketLines,
3125
3221
  setSelectedSlidingAmountInput,
3126
3222
  setSelectedAttendanceMode,
3127
- setErrorMessage,
3128
- setPendingRegistrationReference
3223
+ setErrorMessage
3129
3224
  })
3130
3225
  });
3131
3226
  const handlePaymentStepStateChange = useCallback((state) => {
@@ -3145,6 +3240,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3145
3240
  });
3146
3241
  return {
3147
3242
  api,
3243
+ nowIso,
3148
3244
  anchorId: options.anchorId,
3149
3245
  occurrenceContext: options.occurrenceContext,
3150
3246
  registrationTarget,
@@ -3155,21 +3251,35 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3155
3251
  preSelectedEvent,
3156
3252
  hasPreSelectedContext,
3157
3253
  step,
3158
- setStep,
3254
+ setStep: (next) => {
3255
+ if (!selectionLocked) setStep(next);
3256
+ else if (step === "payment") paymentController.backToBooking();
3257
+ },
3258
+ admission,
3259
+ ticketContinuation,
3260
+ handleTicketsComplete,
3159
3261
  selectedEvent,
3160
- setSelectedEvent,
3262
+ setSelectedEvent: (value) => {
3263
+ if (!selectionLocked) setSelectedEvent(value);
3264
+ },
3161
3265
  ticketsCount,
3162
- setTicketsCount,
3266
+ setTicketsCount: (value) => {
3267
+ if (!selectionLocked) setTicketsCount(value);
3268
+ },
3163
3269
  selectedAttendanceMode,
3164
- setSelectedAttendanceMode,
3270
+ setSelectedAttendanceMode: (value) => {
3271
+ if (!selectionLocked) setSelectedAttendanceMode(value);
3272
+ },
3165
3273
  ticketSelections,
3166
3274
  selectedTicketLines: normalizedSelectedTicketLines,
3167
3275
  setSelectedTicketLines: (lines) => {
3168
- if (waitlistClaim.claim) return;
3276
+ if (selectionLocked || waitlistClaim.claim) return;
3169
3277
  setSelectedTicketLines(normalizeTicketSelectionLines(lines));
3170
3278
  },
3171
3279
  selectedSlidingAmountInput,
3172
- setSelectedSlidingAmountInput,
3280
+ setSelectedSlidingAmountInput: (value) => {
3281
+ if (!selectionLocked) setSelectedSlidingAmountInput(value);
3282
+ },
3173
3283
  selectedSlidingAmount,
3174
3284
  selectedEventSlidingScale,
3175
3285
  slidingScaleSelectionMessage,
@@ -3182,10 +3292,14 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3182
3292
  eventRequiresPayment,
3183
3293
  formData,
3184
3294
  mailingListSignup,
3185
- setFormData,
3295
+ setFormData: (value) => {
3296
+ if (!selectionLocked) setFormData(value);
3297
+ },
3186
3298
  errors,
3187
3299
  guests,
3188
- setGuests,
3300
+ setGuests: (value) => {
3301
+ if (!selectionLocked) setGuests(value);
3302
+ },
3189
3303
  guestErrors,
3190
3304
  guestSectionShake,
3191
3305
  handleGuestShakeEnd,
@@ -3202,7 +3316,9 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3202
3316
  handleLogout,
3203
3317
  paymentStepState,
3204
3318
  handlePaymentStepStateChange,
3205
- handlePaymentSelectionChange,
3319
+ handlePaymentSelectionChange: (value) => {
3320
+ if (!selectionLocked) handlePaymentSelectionChange(value);
3321
+ },
3206
3322
  paymentSelection,
3207
3323
  isMembershipFastTrack,
3208
3324
  waitlistClaim,
@@ -3218,7 +3334,9 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3218
3334
  hasMembershipEligibility,
3219
3335
  requireGuestDetails,
3220
3336
  discountCode: discountUi.discountCode,
3221
- setDiscountCode: discountUi.setDiscountCode,
3337
+ setDiscountCode: (value) => {
3338
+ if (!selectionLocked) discountUi.setDiscountCode(value);
3339
+ },
3222
3340
  discountUiOpen: discountUi.discountUiOpen,
3223
3341
  setDiscountUiOpen: discountUi.setDiscountUiOpen,
3224
3342
  discountsAvailable: discountUi.discountsAvailable,
@@ -3228,49 +3346,31 @@ function useEventRegistrationWizardWithDeps(options, deps) {
3228
3346
  discountContext,
3229
3347
  shouldShowDiscountToggle,
3230
3348
  verification,
3349
+ paymentController,
3231
3350
  paymentStatusRecovery,
3232
3351
  handleCheckPaymentStatus,
3233
3352
  handleContinueExistingPayment,
3234
3353
  handleReturnToPayment,
3235
3354
  handleReturnToBooking,
3355
+ recoveryMessage,
3356
+ checkingNextStep,
3357
+ handleRecoveryEmail,
3236
3358
  handleDetailsComplete,
3237
3359
  handlePaymentSelect,
3238
3360
  handleUseCardInsteadOfPass,
3239
3361
  handleBookForSomeoneElse,
3240
- handleSubmit,
3241
- handleEventSelect,
3362
+ handleSubmit: (...args) => selectionLocked ? Promise.resolve({
3363
+ ok: false,
3364
+ reason: "blocked",
3365
+ message: "This registration step cannot continue."
3366
+ }) : handleSubmit(...args),
3367
+ handleEventSelect: (value) => {
3368
+ if (!selectionLocked) handleEventSelect(value);
3369
+ },
3242
3370
  handleRetry
3243
3371
  };
3244
3372
  }
3245
3373
  //#endregion
3246
- //#region ../blocks/src/system/runtime/nodes/event-registration/useEventCheckoutHandoffRestore.ts
3247
- function eventRegistrationReloadTarget(location) {
3248
- return `${location.pathname}${location.search}#${EVENT_REGISTRATION_ANCHOR_ID}`;
3249
- }
3250
- function reloadAtEventRegistration() {
3251
- const target = eventRegistrationReloadTarget(window.location);
3252
- window.history.replaceState(window.history.state, "", target);
3253
- window.location.reload();
3254
- }
3255
- const browserCheckoutHandoffRestoreDeps = {
3256
- addPageShowListener: (listener) => window.addEventListener("pageshow", listener),
3257
- removePageShowListener: (listener) => window.removeEventListener("pageshow", listener),
3258
- reloadAtRegistration: reloadAtEventRegistration
3259
- };
3260
- function useEventCheckoutHandoffRestore(active) {
3261
- useEventCheckoutHandoffRestoreWithDeps(active, browserCheckoutHandoffRestoreDeps);
3262
- }
3263
- function useEventCheckoutHandoffRestoreWithDeps(active, deps) {
3264
- useEffect(() => {
3265
- if (!active) return;
3266
- const handlePageShow = (event) => {
3267
- if (event.persisted) deps.reloadAtRegistration();
3268
- };
3269
- deps.addPageShowListener(handlePageShow);
3270
- return () => deps.removePageShowListener(handlePageShow);
3271
- }, [active, deps]);
3272
- }
3273
- //#endregion
3274
3374
  //#region ../blocks/src/system/runtime/nodes/event-registration/EventRegistrationBookingLayer.tsx
3275
3375
  function BookingEntry(props) {
3276
3376
  const base = props.variant === "teaser" ? "er-booking-teaser" : "er-booking-bar";
@@ -3693,6 +3793,16 @@ function resolveEntitlementSummaryCopy(method, passCreditsRemaining) {
3693
3793
  function getSummaryRow(option, label) {
3694
3794
  return option.summaryRows.find((row) => row.label === label)?.value ?? null;
3695
3795
  }
3796
+ function getFlexibleBalanceSummary(option) {
3797
+ if (option?.kind !== "flexible_balance") return null;
3798
+ return {
3799
+ total: getSummaryRow(option, "Total"),
3800
+ dueToday: getSummaryRow(option, "Due today"),
3801
+ remainingBalance: getSummaryRow(option, "Remaining balance"),
3802
+ balanceDueBy: getSummaryRow(option, "Balance due by"),
3803
+ depositPolicy: getSummaryRow(option, "Deposit policy")
3804
+ };
3805
+ }
3696
3806
  function getSelectedDirectPaymentOption(input) {
3697
3807
  const summaryEvent = input.wizard.selectedEvent ?? input.wizard.preSelectedEvent;
3698
3808
  const eligibility = input.wizard.eligibility;
@@ -3703,7 +3813,8 @@ function getSelectedDirectPaymentOption(input) {
3703
3813
  paymentTerms: eligibility.paymentTerms,
3704
3814
  totalPriceCents: input.totalCents,
3705
3815
  currency: input.currency,
3706
- startsAt: getRegistrationStartAt(summaryEvent, input.wizard.occurrenceContext)
3816
+ startsAt: getRegistrationStartAt(summaryEvent, input.wizard.occurrenceContext),
3817
+ nowIso: input.wizard.nowIso
3707
3818
  }).options;
3708
3819
  const requestedKind = input.wizard.paymentSelection.collectionOptionKind;
3709
3820
  return options.find((option) => option.kind === requestedKind) ?? options.find((option) => option.kind === "upfront") ?? options[0] ?? null;
@@ -3753,18 +3864,11 @@ function EventRegistrationSummaryBar(props) {
3753
3864
  const autoAppliedDiscount = wizard.discountCode.trim().length > 0 ? null : wizard.autoAppliedDiscount;
3754
3865
  const autoAppliedDiscountCents = Math.max(0, autoAppliedDiscount?.computedAmountCents ?? 0);
3755
3866
  const discountedTotalCents = Math.max(0, subtotalCents - autoAppliedDiscountCents);
3756
- const selectedDirectPaymentOption = getSelectedDirectPaymentOption({
3867
+ const flexibleBalanceSummary = getFlexibleBalanceSummary(getSelectedDirectPaymentOption({
3757
3868
  wizard,
3758
3869
  totalCents: discountedTotalCents,
3759
3870
  currency: summaryCurrency
3760
- });
3761
- const flexibleBalanceSummary = selectedDirectPaymentOption?.kind === "flexible_balance" ? {
3762
- total: getSummaryRow(selectedDirectPaymentOption, "Total"),
3763
- dueToday: getSummaryRow(selectedDirectPaymentOption, "Due today"),
3764
- remainingBalance: getSummaryRow(selectedDirectPaymentOption, "Remaining balance"),
3765
- balanceDueBy: getSummaryRow(selectedDirectPaymentOption, "Balance due by"),
3766
- depositPolicy: getSummaryRow(selectedDirectPaymentOption, "Deposit policy")
3767
- } : null;
3871
+ }));
3768
3872
  const scheduleSummary = summaryEvent ? getScheduleSummary(wizard, summaryEvent) : null;
3769
3873
  const bookingUnit = summaryEvent?.registrationSubject.kind === "wholeSeries" ? "place" : "ticket";
3770
3874
  const productMethod = isProductMethod(wizard.paymentSelection.method);
@@ -3868,49 +3972,23 @@ function EventRegistrationSummaryBar(props) {
3868
3972
  */
3869
3973
  function resolveDetailsButtonText(input) {
3870
3974
  const { wizard } = input;
3871
- const availability = wizard.eligibility?.availability;
3872
- const full = availability?.kind === "waitlist" || isSelectionSoldOut(availability);
3873
- if (wizard.portalAuthState !== "authenticated" && full) return "Continue";
3874
- if (canRecoverToWaitlist(wizard.eligibility)) return "Join waitlist";
3875
- if (!input.hasWaitlistClaim && full && wizard.eligibility?.registration.kind !== "pending_payment") return "Sold out";
3975
+ const admission = wizard.admission;
3976
+ switch (admission.kind) {
3977
+ case "checking": return "Checking availability...";
3978
+ case "closed": return admission.reason === "alreadyWaitlisted" ? "Already on waitlist" : "Registration unavailable";
3979
+ case "fullyBooked": return admission.recovery === "byEmail" ? "Continue" : "Sold out";
3980
+ case "waitlist": return admission.recovery === "byEmail" ? "Continue" : "Join waitlist";
3981
+ case "open":
3982
+ case "resume": break;
3983
+ }
3876
3984
  if (wizard.paymentSelection.method === "pass") return "Book using class pass";
3877
3985
  if (wizard.paymentSelection.method === "membership") return input.fallbackText;
3878
3986
  return wizard.eventRequiresPayment ? "Continue to payment" : input.fallbackText;
3879
3987
  }
3880
- function resolveExistingRegistrationLabel(wizard) {
3881
- return wizard.registrationState.kind === "waitlisted" ? "Already on waitlist" : "Already registered";
3882
- }
3883
- function isExistingRegistrationBlocking$1(wizard) {
3884
- return hasBlockingExistingRegistration({
3885
- isAuthenticated: wizard.portalAuthState === "authenticated",
3886
- registration: wizard.registrationState,
3887
- hasWaitlistClaim: wizard.waitlistClaim.claim !== null
3888
- });
3889
- }
3890
- function resolveTicketSelectionContinueLabel(input) {
3891
- if (input.isCheckingExistingRegistration) return "Checking registration...";
3892
- if (input.ticketTypeSelectionBlocked) return "Choose a ticket";
3893
- if (input.registrationOptionsPending) return "Checking availability...";
3894
- if (input.registrationOptionsFailed) return "Availability unavailable";
3895
- if (input.hasExistingRegistration) return resolveExistingRegistrationLabel(input.wizard);
3896
- return "Continue";
3897
- }
3898
3988
  function SelectTicketsActions(props) {
3899
3989
  const { wizard } = props;
3900
- const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidTicketSelection;
3901
- const registrationOptionsPending = !ticketTypeSelectionBlocked && ["idle", "loading"].includes(wizard.registrationOptionsState.tag);
3902
- const registrationOptionsFailed = wizard.registrationOptionsState.tag === "failed";
3903
- const isCheckingExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistrationLoading;
3904
- const hasExistingRegistration = isExistingRegistrationBlocking$1(wizard);
3905
- const isContinueDisabled = registrationOptionsPending || registrationOptionsFailed || isCheckingExistingRegistration || hasExistingRegistration || ticketTypeSelectionBlocked;
3906
- const continueLabel = resolveTicketSelectionContinueLabel({
3907
- wizard,
3908
- registrationOptionsPending,
3909
- registrationOptionsFailed,
3910
- isCheckingExistingRegistration,
3911
- hasExistingRegistration,
3912
- ticketTypeSelectionBlocked
3913
- });
3990
+ const { label: continueLabel, pending: registrationOptionsPending } = wizard.ticketContinuation;
3991
+ const isContinueDisabled = wizard.ticketContinuation.kind === "blocked";
3914
3992
  return /* @__PURE__ */ jsxs("div", {
3915
3993
  className: "rb-wizard-actions er-actions",
3916
3994
  children: [!wizard.hasPreSelectedContext && /* @__PURE__ */ jsx("button", {
@@ -3920,7 +3998,7 @@ function SelectTicketsActions(props) {
3920
3998
  children: "← Back"
3921
3999
  }), /* @__PURE__ */ jsx(ThemedButton, {
3922
4000
  type: "button",
3923
- onClick: () => wizard.setStep("enter-details"),
4001
+ onClick: wizard.handleTicketsComplete,
3924
4002
  disabled: isContinueDisabled,
3925
4003
  variant: props.buttonVariant,
3926
4004
  size: "md",
@@ -3936,18 +4014,11 @@ function SelectTicketsActions(props) {
3936
4014
  }
3937
4015
  function EnterDetailsActions(props) {
3938
4016
  const { wizard } = props;
3939
- const hasWaitlistClaim = wizard.waitlistClaim.claim !== null;
3940
4017
  const isCheckingEligibility = wizard.eligibilityLoading;
3941
- const eligibilityUnavailable = Boolean(resolveRegistrationAvailabilityBlocker({
3942
- hasWaitlistClaim,
3943
- eligibility: wizard.eligibility,
3944
- eligibilityError: wizard.eligibilityError
3945
- }));
3946
- const isSoldOut = !hasWaitlistClaim && wizard.portalAuthState === "authenticated" && wizard.eligibility?.registration.kind !== "pending_payment" && isSelectionSoldOut(wizard.eligibility?.availability);
3947
- const isContinueDisabled = isCheckingEligibility || eligibilityUnavailable || isSoldOut;
4018
+ const eligibilityUnavailable = Boolean(resolveRegistrationAvailabilityBlocker(wizard.admission));
4019
+ const isContinueDisabled = isCheckingEligibility || eligibilityUnavailable;
3948
4020
  const continueLabel = isCheckingEligibility ? "Checking availability..." : wizard.eligibilityError ? "Availability unavailable" : eligibilityUnavailable ? "Registration unavailable" : resolveDetailsButtonText({
3949
4021
  wizard,
3950
- hasWaitlistClaim,
3951
4022
  fallbackText: props.buttonText
3952
4023
  });
3953
4024
  return /* @__PURE__ */ jsxs("div", {
@@ -3990,10 +4061,11 @@ function SelectPaymentActions(props) {
3990
4061
  function EventRegistrationWizardActions(props) {
3991
4062
  const { wizard } = props;
3992
4063
  switch (wizard.step) {
4064
+ case "fully-booked":
3993
4065
  case "error":
3994
4066
  case "success":
3995
4067
  case "submitting":
3996
- case "redirecting":
4068
+ case "payment":
3997
4069
  case "verifying-payment":
3998
4070
  case "verifying-timeout":
3999
4071
  case "payment-status":
@@ -4006,6 +4078,118 @@ function EventRegistrationWizardActions(props) {
4006
4078
  }
4007
4079
  }
4008
4080
  //#endregion
4081
+ //#region ../blocks/src/system/runtime/nodes/event-registration/EventRegistrationPayment.tsx
4082
+ const Payment = lazy(() => import("./EventEmbeddedCheckoutStep-DL6zFAkA.mjs"));
4083
+ /** The one Stripe mount, shared by the standard wizard and headless arrangements. */
4084
+ function EventPaymentMount({ state, onComplete, onRetry, onBack }) {
4085
+ if (state.kind === "abandoning") return /* @__PURE__ */ jsx("p", {
4086
+ role: "status",
4087
+ children: "Returning to booking…"
4088
+ });
4089
+ if (state.kind !== "payment") return null;
4090
+ return /* @__PURE__ */ jsx(Suspense, {
4091
+ fallback: /* @__PURE__ */ jsx("p", {
4092
+ role: "status",
4093
+ children: "Loading secure payment…"
4094
+ }),
4095
+ children: /* @__PURE__ */ jsx(Payment, {
4096
+ checkout: state.checkout,
4097
+ onComplete,
4098
+ onRetry,
4099
+ onBack: () => void onBack()
4100
+ })
4101
+ });
4102
+ }
4103
+ //#endregion
4104
+ //#region ../blocks/src/system/runtime/nodes/event-registration/FullyBookedStep.tsx
4105
+ function RecoveryEmailField({ wizard }) {
4106
+ const id = useId();
4107
+ const errorId = `${id}-error`;
4108
+ return /* @__PURE__ */ jsxs("div", {
4109
+ className: "er-field",
4110
+ children: [
4111
+ /* @__PURE__ */ jsx("label", {
4112
+ className: "er-label",
4113
+ htmlFor: id,
4114
+ children: "Email"
4115
+ }),
4116
+ /* @__PURE__ */ jsx("input", {
4117
+ id,
4118
+ type: "email",
4119
+ autoComplete: "email",
4120
+ className: "er-input",
4121
+ value: wizard.formData.email,
4122
+ onChange: (event) => wizard.setFormData({
4123
+ ...wizard.formData,
4124
+ email: event.target.value
4125
+ }),
4126
+ ...getFieldValidationControlProps({
4127
+ message: wizard.errors.email,
4128
+ messageId: errorId
4129
+ })
4130
+ }),
4131
+ wizard.errors.email && /* @__PURE__ */ jsx(FieldValidationMessage, {
4132
+ id: errorId,
4133
+ className: "er-error",
4134
+ children: wizard.errors.email
4135
+ })
4136
+ ]
4137
+ });
4138
+ }
4139
+ function RecoveryEmail(props) {
4140
+ const { wizard } = props;
4141
+ return /* @__PURE__ */ jsxs("div", {
4142
+ className: "rb-reg-stack er-stack",
4143
+ children: [
4144
+ /* @__PURE__ */ jsx("p", {
4145
+ className: "er-text",
4146
+ children: "Already started a booking? Enter your email to finish paying."
4147
+ }),
4148
+ /* @__PURE__ */ jsx(RecoveryEmailField, { wizard }),
4149
+ props.showSpamProtection && /* @__PURE__ */ jsx("div", {
4150
+ className: "er-altcha",
4151
+ children: /* @__PURE__ */ jsx(AltchaWidget, {
4152
+ challengeUrl: getChallengeUrl(),
4153
+ onSolutionChange: wizard.setAltchaSolution
4154
+ })
4155
+ }),
4156
+ wizard.recoveryMessage && /* @__PURE__ */ jsx("p", {
4157
+ role: "alert",
4158
+ className: "er-error",
4159
+ children: wizard.recoveryMessage
4160
+ }),
4161
+ /* @__PURE__ */ jsx("button", {
4162
+ type: "button",
4163
+ className: "er-btn er-btn--primary",
4164
+ disabled: wizard.checkingNextStep || props.showSpamProtection && !wizard.altchaSolution,
4165
+ onClick: () => void wizard.handleRecoveryEmail(),
4166
+ children: wizard.checkingNextStep ? "Checking booking..." : "Continue"
4167
+ })
4168
+ ]
4169
+ });
4170
+ }
4171
+ function FullyBookedStep(props) {
4172
+ const { wizard } = props;
4173
+ const admission = wizard.admission;
4174
+ const byEmail = admission.kind === "fullyBooked" && admission.recovery === "byEmail";
4175
+ return /* @__PURE__ */ jsxs("div", {
4176
+ className: "rb-reg-stack er-stack",
4177
+ children: [
4178
+ /* @__PURE__ */ jsx("h3", {
4179
+ className: "er-title",
4180
+ children: "This event is fully booked"
4181
+ }),
4182
+ byEmail && /* @__PURE__ */ jsx(RecoveryEmail, { ...props }),
4183
+ /* @__PURE__ */ jsx("button", {
4184
+ type: "button",
4185
+ className: "er-back-btn",
4186
+ onClick: () => wizard.setStep("select-tickets"),
4187
+ children: "← Back"
4188
+ })
4189
+ ]
4190
+ });
4191
+ }
4192
+ //#endregion
4009
4193
  //#region ../blocks/src/system/runtime/nodes/event-registration/siteCreditPreview.ts
4010
4194
  function calculateEventRegistrationPreviewTotal(input) {
4011
4195
  const discountCents = input.manualDiscountCode.trim() ? 0 : input.automaticDiscount?.computedAmountCents ?? 0;
@@ -4081,7 +4265,7 @@ function buildSelectedSiteCreditPreview(input) {
4081
4265
  function resolvePaymentStepStatus(input) {
4082
4266
  if (input.loading) return "loading";
4083
4267
  if (input.error || !input.eligibility) return "error";
4084
- if (input.eligibility.registration.kind === "waitlisted" || input.eligibility.paymentOptions.length === 0) return "not-eligible";
4268
+ if (resolveRegistrationAvailabilityBlocker(input.admission) !== null || input.eligibility.paymentOptions.length === 0) return "not-eligible";
4085
4269
  return "ready";
4086
4270
  }
4087
4271
  function shouldPromptForLogin(status, isAuthenticated, eligibility) {
@@ -4115,8 +4299,8 @@ function UnavailableEntitlementMessage(props) {
4115
4299
  })
4116
4300
  ] });
4117
4301
  }
4118
- function IneligiblePaymentPanel(props) {
4119
- if (isSelectionSoldOut(props.eligibility.availability)) return /* @__PURE__ */ jsxs("div", {
4302
+ function SoldOutPaymentPanel() {
4303
+ return /* @__PURE__ */ jsxs("div", {
4120
4304
  className: "rb-reg-stack er-stack",
4121
4305
  children: [/* @__PURE__ */ jsx("h3", {
4122
4306
  className: "er-title",
@@ -4129,6 +4313,8 @@ function IneligiblePaymentPanel(props) {
4129
4313
  })
4130
4314
  })]
4131
4315
  });
4316
+ }
4317
+ function IneligiblePaymentPanel(props) {
4132
4318
  const promptLogin = shouldPromptForLogin("not-eligible", props.isAuthenticated, props.eligibility);
4133
4319
  return /* @__PURE__ */ jsxs("div", {
4134
4320
  className: "rb-reg-stack er-stack",
@@ -4158,6 +4344,12 @@ function IneligiblePaymentPanel(props) {
4158
4344
  })]
4159
4345
  });
4160
4346
  }
4347
+ function renderRegistrationAvailabilityBlocker(admission) {
4348
+ const blocker = resolveRegistrationAvailabilityBlocker(admission);
4349
+ if (blocker?.kind !== "unavailable") return null;
4350
+ if (admission.kind === "closed" && admission.reason === "alreadyWaitlisted") return /* @__PURE__ */ jsx(AlreadyWaitlistedPaymentPanel, {});
4351
+ return admission.kind === "fullyBooked" ? /* @__PURE__ */ jsx(SoldOutPaymentPanel, {}) : /* @__PURE__ */ jsx(ClosedPaymentPanel, { message: blocker.message });
4352
+ }
4161
4353
  function PaymentBlockingPanel(props) {
4162
4354
  if (props.eligibilityLoading) return /* @__PURE__ */ jsx("div", {
4163
4355
  className: "rb-reg-stack er-stack",
@@ -4183,7 +4375,18 @@ function PaymentBlockingPanel(props) {
4183
4375
  })
4184
4376
  });
4185
4377
  if (!props.eligibility) return /* @__PURE__ */ jsx(MissingEligibilityPanel, {});
4186
- if (props.eligibility.registration.kind === "waitlisted") return /* @__PURE__ */ jsxs("div", {
4378
+ const availabilityPanel = renderRegistrationAvailabilityBlocker(props.admission);
4379
+ if (availabilityPanel) return availabilityPanel;
4380
+ if (props.eligibility.isFree) return /* @__PURE__ */ jsx(FreePaymentSummary, { ticketsCount: props.ticketsCount });
4381
+ return props.eligibility.paymentOptions.length > 0 ? null : /* @__PURE__ */ jsx(IneligiblePaymentPanel, {
4382
+ eligibility: props.eligibility,
4383
+ email: props.email,
4384
+ isAuthenticated: props.isAuthenticated,
4385
+ onRequestLogin: props.onRequestLogin
4386
+ });
4387
+ }
4388
+ function AlreadyWaitlistedPaymentPanel() {
4389
+ return /* @__PURE__ */ jsxs("div", {
4187
4390
  className: "rb-reg-stack er-stack",
4188
4391
  children: [/* @__PURE__ */ jsx("h3", {
4189
4392
  className: "er-title",
@@ -4200,7 +4403,9 @@ function PaymentBlockingPanel(props) {
4200
4403
  })]
4201
4404
  })]
4202
4405
  });
4203
- if (props.eligibility.isFree) return /* @__PURE__ */ jsxs("div", {
4406
+ }
4407
+ function FreePaymentSummary({ ticketsCount }) {
4408
+ return /* @__PURE__ */ jsxs("div", {
4204
4409
  className: "rb-reg-stack er-stack",
4205
4410
  children: [/* @__PURE__ */ jsx("h3", {
4206
4411
  className: "er-title",
@@ -4213,18 +4418,24 @@ function PaymentBlockingPanel(props) {
4213
4418
  }), /* @__PURE__ */ jsxs("p", {
4214
4419
  className: "er-text er-text--muted",
4215
4420
  children: [
4216
- props.ticketsCount,
4421
+ ticketsCount,
4217
4422
  " ticket",
4218
- props.ticketsCount > 1 ? "s" : ""
4423
+ ticketsCount > 1 ? "s" : ""
4219
4424
  ]
4220
4425
  })]
4221
4426
  })]
4222
4427
  });
4223
- return props.eligibility.paymentOptions.length > 0 ? null : /* @__PURE__ */ jsx(IneligiblePaymentPanel, {
4224
- eligibility: props.eligibility,
4225
- email: props.email,
4226
- isAuthenticated: props.isAuthenticated,
4227
- onRequestLogin: props.onRequestLogin
4428
+ }
4429
+ function ClosedPaymentPanel({ message }) {
4430
+ return /* @__PURE__ */ jsx("div", {
4431
+ className: "rb-reg-stack er-stack",
4432
+ children: /* @__PURE__ */ jsx("div", {
4433
+ className: "er-center",
4434
+ children: /* @__PURE__ */ jsx("p", {
4435
+ className: "er-text er-text--error",
4436
+ children: message
4437
+ })
4438
+ })
4228
4439
  });
4229
4440
  }
4230
4441
  //#endregion
@@ -5210,6 +5421,29 @@ function canContinueWithPaymentSelection(selection, directPaymentSelection) {
5210
5421
  case null: return false;
5211
5422
  }
5212
5423
  }
5424
+ function buildEventPaymentSelectionView(input) {
5425
+ const canShowDirectPaymentOptions = Boolean(input.eligibility?.requiresPayment && input.eligibility.price);
5426
+ const directPaymentOptions = input.eligibility && canShowDirectPaymentOptions ? buildEventRegistrationPaymentOptionsViewModel({
5427
+ eventTitle: input.event?.title ?? "Event",
5428
+ approvedPaymentOptions: input.eligibility.paymentOptions,
5429
+ paymentTerms: input.eligibility.paymentTerms,
5430
+ totalPriceCents: input.totalPriceCents,
5431
+ currency: input.eligibility.currency,
5432
+ startsAt: getRegistrationStartAt(input.event, input.occurrenceContext),
5433
+ nowIso: input.nowIso
5434
+ }).options : [];
5435
+ const directPaymentSelectionState = resolveEventRegistrationDirectPaymentSelection({
5436
+ options: directPaymentOptions,
5437
+ selection: input.selection
5438
+ });
5439
+ return {
5440
+ canShowDirectPaymentOptions,
5441
+ directPaymentOptions,
5442
+ directPaymentSelectionState,
5443
+ directPaymentFallbackKind: directPaymentSelectionState.kind === "invalid" ? directPaymentSelectionState.fallbackSelection.collectionOptionKind : void 0,
5444
+ selectedDirectOption: directPaymentSelectionState.kind === "valid" ? directPaymentOptions.find((option) => option.kind === directPaymentSelectionState.selection.collectionOptionKind) ?? null : null
5445
+ };
5446
+ }
5213
5447
  /**
5214
5448
  * Payment Selection Step
5215
5449
  *
@@ -5217,25 +5451,17 @@ function canContinueWithPaymentSelection(selection, directPaymentSelection) {
5217
5451
  * Fetches eligible passes/memberships using the user's email.
5218
5452
  * Also shows available passes/memberships for purchase (upsell).
5219
5453
  */
5220
- const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, isAuthenticated = false, creditState = { kind: "not_authenticated" }, directPaymentTotalCents, manualDiscountPending = false, selectedSelection, onSelectionChange, eligibility, eligibilityLoading, eligibilityError = null, onRequestLogin, onStateChange }) => {
5454
+ const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, isAuthenticated = false, creditState = { kind: "not_authenticated" }, directPaymentTotalCents, manualDiscountPending = false, selectedSelection, onSelectionChange, eligibility, admission, eligibilityLoading, eligibilityError = null, onRequestLogin, onStateChange, nowIso }) => {
5221
5455
  const occurrenceId = occurrenceContext?.id ?? (event?.registrationSubject.kind === "occurrence" ? event.registrationSubject.occurrenceId : "");
5222
5456
  const selectedMethod = selectedSelection.method;
5223
- const totalPrice = directPaymentTotalCents;
5224
- const canShowDirectPaymentOptions = Boolean(eligibility?.requiresPayment && eligibility.price);
5225
- const directPaymentOptions = eligibility && canShowDirectPaymentOptions ? buildEventRegistrationPaymentOptionsViewModel({
5226
- eventTitle: event?.title ?? "Event",
5227
- approvedPaymentOptions: eligibility.paymentOptions,
5228
- paymentTerms: eligibility.paymentTerms,
5229
- totalPriceCents: totalPrice,
5230
- currency: eligibility.currency,
5231
- startsAt: getRegistrationStartAt(event, occurrenceContext)
5232
- }).options : [];
5233
- const directPaymentSelectionState = resolveEventRegistrationDirectPaymentSelection({
5234
- options: directPaymentOptions,
5457
+ const { canShowDirectPaymentOptions, directPaymentOptions, directPaymentSelectionState, directPaymentFallbackKind, selectedDirectOption } = buildEventPaymentSelectionView({
5458
+ event,
5459
+ occurrenceContext,
5460
+ eligibility,
5461
+ totalPriceCents: directPaymentTotalCents,
5462
+ nowIso,
5235
5463
  selection: selectedSelection
5236
5464
  });
5237
- const directPaymentFallbackKind = (directPaymentSelectionState.kind === "invalid" ? directPaymentSelectionState.fallbackSelection : null)?.collectionOptionKind;
5238
- const selectedDirectOption = directPaymentSelectionState.kind === "valid" ? directPaymentOptions.find((option) => option.kind === directPaymentSelectionState.selection.collectionOptionKind) ?? null : null;
5239
5465
  const siteCreditPreview = buildSelectedSiteCreditPreview({
5240
5466
  options: directPaymentOptions,
5241
5467
  selectionState: directPaymentSelectionState,
@@ -5254,7 +5480,8 @@ const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, i
5254
5480
  const paymentStepStatus = resolvePaymentStepStatus({
5255
5481
  loading: eligibilityLoading,
5256
5482
  error: eligibilityError,
5257
- eligibility
5483
+ eligibility,
5484
+ admission
5258
5485
  });
5259
5486
  const paymentButtonText = resolvePaymentButtonText(selectedMethod, siteCreditPreview, eligibility, selectedDirectOption);
5260
5487
  const showLoginPrompt = shouldPromptForLogin(paymentStepStatus, isAuthenticated, eligibility);
@@ -5275,6 +5502,7 @@ const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, i
5275
5502
  ]);
5276
5503
  const blockingPanel = /* @__PURE__ */ jsx(PaymentBlockingPanel, {
5277
5504
  eligibility,
5505
+ admission,
5278
5506
  eligibilityLoading,
5279
5507
  eligibilityError,
5280
5508
  occurrenceId,
@@ -5422,16 +5650,6 @@ function ExistingRegistrationNotice(props) {
5422
5650
  })
5423
5651
  });
5424
5652
  }
5425
- function resolveExistingRegistrationKind(wizard) {
5426
- return wizard.registrationState.kind === "waitlisted" ? "waitlisted" : "booked";
5427
- }
5428
- function isExistingRegistrationBlocking(wizard) {
5429
- return hasBlockingExistingRegistration({
5430
- isAuthenticated: wizard.portalAuthState === "authenticated",
5431
- registration: wizard.registrationState,
5432
- hasWaitlistClaim: wizard.waitlistClaim.claim !== null
5433
- });
5434
- }
5435
5653
  function AttendeeDetailsContent(props) {
5436
5654
  const { wizard } = props;
5437
5655
  const membership = wizard.portalAuthState === "authenticated" && wizard.ticketsCount === 1 ? wizard.eligibility?.eligibleMemberships[0] : void 0;
@@ -5462,12 +5680,29 @@ function AttendeeDetailsContent(props) {
5462
5680
  })
5463
5681
  ] });
5464
5682
  }
5683
+ function EventDiscountCodeDisclosure(props) {
5684
+ const { wizard } = props;
5685
+ if (wizard.discountsAvailableLoading || !wizard.shouldShowDiscountToggle) return null;
5686
+ return /* @__PURE__ */ jsx(DiscountCodeDisclosure, {
5687
+ surface: "event-registration",
5688
+ isOpen: wizard.discountUiOpen,
5689
+ code: wizard.discountCode,
5690
+ onOpen: () => wizard.setDiscountUiOpen(true),
5691
+ onChange: wizard.setDiscountCode,
5692
+ onRemove: () => {
5693
+ wizard.setDiscountCode("");
5694
+ wizard.setDiscountUiOpen(false);
5695
+ },
5696
+ id: "er-discount-code"
5697
+ });
5698
+ }
5465
5699
  function PaymentSelectionContent(props) {
5466
5700
  const { wizard } = props;
5467
5701
  const subtotalCents = wizard.ticketSelectionSubtotalCents;
5468
5702
  if (!wizard.api.config.siteId) return null;
5469
5703
  if (subtotalCents === null) return /* @__PURE__ */ jsx(PaymentBlockingPanel, {
5470
5704
  eligibility: wizard.eligibility,
5705
+ admission: wizard.admission,
5471
5706
  eligibilityLoading: wizard.eligibilityLoading,
5472
5707
  eligibilityError: wizard.eligibilityError,
5473
5708
  occurrenceId: wizard.occurrenceContext?.id ?? "",
@@ -5492,31 +5727,62 @@ function PaymentSelectionContent(props) {
5492
5727
  selectedSelection: wizard.paymentSelection,
5493
5728
  onSelectionChange: wizard.handlePaymentSelectionChange,
5494
5729
  eligibility: wizard.eligibility,
5730
+ admission: wizard.admission,
5495
5731
  eligibilityLoading: wizard.eligibilityLoading,
5496
5732
  eligibilityError: wizard.eligibilityError,
5497
5733
  onRequestLogin: () => wizard.setShowMagicLinkModal(true),
5498
- onStateChange: wizard.handlePaymentStepStateChange
5499
- }), wizard.discountsAvailableLoading ? null : wizard.shouldShowDiscountToggle ? /* @__PURE__ */ jsx(DiscountCodeDisclosure, {
5500
- surface: "event-registration",
5501
- isOpen: wizard.discountUiOpen,
5502
- code: wizard.discountCode,
5503
- onOpen: () => wizard.setDiscountUiOpen(true),
5504
- onChange: wizard.setDiscountCode,
5505
- onRemove: () => {
5506
- wizard.setDiscountCode("");
5507
- wizard.setDiscountUiOpen(false);
5508
- },
5509
- id: "er-discount-code"
5510
- }) : null] });
5734
+ onStateChange: wizard.handlePaymentStepStateChange,
5735
+ nowIso: wizard.nowIso
5736
+ }), /* @__PURE__ */ jsx(EventDiscountCodeDisclosure, { wizard })] });
5737
+ }
5738
+ function ClosedAdmissionNotice({ wizard }) {
5739
+ const admission = wizard.admission;
5740
+ return admission.kind === "closed" && admission.reason !== "alreadyWaitlisted" && !wizard.eligibilityLoading ? /* @__PURE__ */ jsx("p", {
5741
+ role: "alert",
5742
+ className: "er-error",
5743
+ children: admission.message
5744
+ }) : null;
5745
+ }
5746
+ function TicketSelectionContent({ wizard }) {
5747
+ if (!wizard.selectedEvent && !wizard.occurrenceContext) return null;
5748
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5749
+ wizard.admission.kind === "closed" && wizard.admission.reason === "alreadyWaitlisted" && /* @__PURE__ */ jsx(ExistingRegistrationNotice, {
5750
+ kind: "waitlisted",
5751
+ onBookForSomeoneElse: wizard.handleBookForSomeoneElse
5752
+ }),
5753
+ /* @__PURE__ */ jsx(ClosedAdmissionNotice, { wizard }),
5754
+ /* @__PURE__ */ jsx(TicketSelectionStep, {
5755
+ event: wizard.selectedEvent,
5756
+ occurrenceContext: wizard.occurrenceContext,
5757
+ selectedAttendanceMode: wizard.selectedAttendanceMode,
5758
+ onAttendanceModeChange: wizard.setSelectedAttendanceMode,
5759
+ ticketsCount: wizard.ticketsCount,
5760
+ maxTickets: wizard.maxTicketsNum,
5761
+ visibleTicketTypes: wizard.visibleTicketTypes,
5762
+ ticketSelections: wizard.selectedTicketLines,
5763
+ onTicketSelectionsChange: wizard.setSelectedTicketLines,
5764
+ slidingScale: wizard.selectedEventSlidingScale,
5765
+ selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
5766
+ slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
5767
+ onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
5768
+ onTicketsChange: wizard.setTicketsCount,
5769
+ bookingUnit: resolveBookingUnit(wizard.selectedEvent)
5770
+ })
5771
+ ] });
5511
5772
  }
5512
5773
  function EventRegistrationWizardContent(props) {
5513
5774
  const { wizard } = props;
5514
5775
  const showSpamProtection = props.showSpamProtection ?? isSpamProtectionEnabled();
5515
5776
  switch (wizard.step) {
5777
+ case "payment": return /* @__PURE__ */ jsx(EventPaymentMount, {
5778
+ state: wizard.paymentController.state,
5779
+ onComplete: wizard.paymentController.check,
5780
+ onRetry: wizard.paymentController.retryPaymentLoad,
5781
+ onBack: wizard.paymentController.backToBooking
5782
+ });
5516
5783
  case "error":
5517
5784
  case "success":
5518
5785
  case "submitting":
5519
- case "redirecting":
5520
5786
  case "verifying-payment":
5521
5787
  case "verifying-timeout":
5522
5788
  case "payment-status":
@@ -5528,28 +5794,11 @@ function EventRegistrationWizardContent(props) {
5528
5794
  showVenue: props.showVenue,
5529
5795
  showCapacity: props.showCapacity
5530
5796
  });
5531
- case "select-tickets":
5532
- if (!wizard.selectedEvent && !wizard.occurrenceContext) return null;
5533
- return /* @__PURE__ */ jsxs(Fragment, { children: [isExistingRegistrationBlocking(wizard) && /* @__PURE__ */ jsx(ExistingRegistrationNotice, {
5534
- kind: resolveExistingRegistrationKind(wizard),
5535
- onBookForSomeoneElse: wizard.handleBookForSomeoneElse
5536
- }), /* @__PURE__ */ jsx(TicketSelectionStep, {
5537
- event: wizard.selectedEvent,
5538
- occurrenceContext: wizard.occurrenceContext,
5539
- selectedAttendanceMode: wizard.selectedAttendanceMode,
5540
- onAttendanceModeChange: wizard.setSelectedAttendanceMode,
5541
- ticketsCount: wizard.ticketsCount,
5542
- maxTickets: wizard.maxTicketsNum,
5543
- visibleTicketTypes: wizard.visibleTicketTypes,
5544
- ticketSelections: wizard.selectedTicketLines,
5545
- onTicketSelectionsChange: wizard.setSelectedTicketLines,
5546
- slidingScale: wizard.selectedEventSlidingScale,
5547
- selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
5548
- slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
5549
- onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
5550
- onTicketsChange: wizard.setTicketsCount,
5551
- bookingUnit: resolveBookingUnit(wizard.selectedEvent)
5552
- })] });
5797
+ case "select-tickets": return /* @__PURE__ */ jsx(TicketSelectionContent, { wizard });
5798
+ case "fully-booked": return /* @__PURE__ */ jsx(FullyBookedStep, {
5799
+ wizard,
5800
+ showSpamProtection
5801
+ });
5553
5802
  case "enter-details": return /* @__PURE__ */ jsx(AttendeeDetailsContent, {
5554
5803
  wizard,
5555
5804
  showSpamProtection
@@ -5628,7 +5877,10 @@ function presentation(state) {
5628
5877
  case "assistanceRequired": return {
5629
5878
  title: "We still can’t confirm your payment",
5630
5879
  message: "Please don’t make another payment. Contact the organiser so they can check your booking.",
5631
- action: { label: "Return to booking" }
5880
+ action: {
5881
+ label: "Return to booking",
5882
+ disabled: true
5883
+ }
5632
5884
  };
5633
5885
  }
5634
5886
  }
@@ -5879,51 +6131,27 @@ function EventRegistrationMagicLinkModal(props) {
5879
6131
  function EventRegistrationTerminalState(props) {
5880
6132
  const { wizard } = props;
5881
6133
  switch (wizard.step) {
5882
- case "submitting":
5883
- case "redirecting": {
5884
- const label = wizard.step === "redirecting" ? "Redirecting to payment" : "Processing your registration";
5885
- return /* @__PURE__ */ jsxs("div", {
5886
- className: "er-center",
5887
- children: [/* @__PURE__ */ jsx(SpinnerNode, {
5888
- size: "lg",
5889
- srLabel: label
5890
- }), /* @__PURE__ */ jsxs("p", {
5891
- className: "er-text--muted",
5892
- children: [label, "..."]
5893
- })]
5894
- });
5895
- }
6134
+ case "submitting": return /* @__PURE__ */ jsx(RegistrationProcessing, {});
6135
+ case "payment": return null;
5896
6136
  case "verifying-payment": return /* @__PURE__ */ jsx(VerifyingPaymentStep, {});
5897
6137
  case "payment-failed": return /* @__PURE__ */ jsx(PaymentFailedStep, {
5898
6138
  message: wizard.errorMessage,
5899
6139
  onRetry: () => wizard.setStep("select-payment")
5900
6140
  });
5901
- case "verifying-timeout": return /* @__PURE__ */ jsx(VerifyingTimeoutStep, {
5902
- onCheckAgain: wizard.verification.status === "timeout" ? wizard.verification.retry : wizard.verification.retryReturn,
5903
- title: wizard.verification.status === "timeout" ? void 0 : "We’re checking your payment",
5904
- message: wizard.verification.status === "timeout" ? void 0 : wizard.errorMessage,
6141
+ case "verifying-timeout": return /* @__PURE__ */ jsx(RegistrationVerificationTimeout, {
6142
+ wizard,
5905
6143
  supportEmail: props.supportEmail
5906
6144
  });
5907
- case "payment-status": return wizard.paymentStatusRecovery ? /* @__PURE__ */ jsx(PaymentStatusStep, {
5908
- state: wizard.paymentStatusRecovery,
5909
- onCheck: wizard.handleCheckPaymentStatus,
5910
- onContinuePayment: wizard.handleContinueExistingPayment,
5911
- onReturnToPayment: wizard.handleReturnToPayment,
5912
- onReturnToBooking: wizard.handleReturnToBooking
5913
- }) : null;
6145
+ case "payment-status": return /* @__PURE__ */ jsx(RegistrationPaymentStatus, { wizard });
5914
6146
  case "success": return /* @__PURE__ */ jsx(EventRegistrationSuccess, {
5915
6147
  wizard,
5916
6148
  fallbackMessage: props.successMessage,
5917
6149
  demoEmailSuppressed: props.demoEmailSuppressed
5918
6150
  });
5919
- case "error": return /* @__PURE__ */ jsx(ErrorStep, {
5920
- message: wizard.errorMessage,
5921
- action: wizard.errorAction,
5922
- canJoinWaitlist: wizard.eligibility?.availability.kind === "waitlist" && wizard.eligibility.registration.kind !== "pending_payment",
5923
- onRetry: wizard.handleRetry
5924
- });
6151
+ case "error": return /* @__PURE__ */ jsx(RegistrationError, { wizard });
5925
6152
  case "select-event":
5926
6153
  case "select-tickets":
6154
+ case "fully-booked":
5927
6155
  case "enter-details":
5928
6156
  case "select-payment": return null;
5929
6157
  }
@@ -5992,6 +6220,46 @@ function EventRegistrationSurface(props) {
5992
6220
  })]
5993
6221
  });
5994
6222
  }
6223
+ function RegistrationProcessing() {
6224
+ const label = "Processing your registration";
6225
+ return /* @__PURE__ */ jsxs("div", {
6226
+ className: "er-center",
6227
+ children: [/* @__PURE__ */ jsx(SpinnerNode, {
6228
+ size: "lg",
6229
+ srLabel: label
6230
+ }), /* @__PURE__ */ jsxs("p", {
6231
+ className: "er-text--muted",
6232
+ children: [label, "..."]
6233
+ })]
6234
+ });
6235
+ }
6236
+ function RegistrationVerificationTimeout({ wizard, supportEmail }) {
6237
+ const timedOut = wizard.verification.status === "timeout";
6238
+ return /* @__PURE__ */ jsx(VerifyingTimeoutStep, {
6239
+ onCheckAgain: timedOut ? wizard.verification.retry : wizard.verification.retryReturn,
6240
+ title: timedOut ? void 0 : "We’re checking your payment",
6241
+ message: timedOut ? void 0 : wizard.errorMessage,
6242
+ supportEmail
6243
+ });
6244
+ }
6245
+ function RegistrationPaymentStatus({ wizard }) {
6246
+ return wizard.paymentStatusRecovery ? /* @__PURE__ */ jsx(PaymentStatusStep, {
6247
+ state: wizard.paymentStatusRecovery,
6248
+ onCheck: wizard.handleCheckPaymentStatus,
6249
+ onContinuePayment: wizard.handleContinueExistingPayment,
6250
+ onReturnToPayment: wizard.handleReturnToPayment,
6251
+ onReturnToBooking: wizard.handleReturnToBooking
6252
+ }) : null;
6253
+ }
6254
+ function RegistrationError({ wizard }) {
6255
+ const admission = wizard.admission;
6256
+ return /* @__PURE__ */ jsx(ErrorStep, {
6257
+ message: wizard.errorMessage,
6258
+ action: wizard.errorAction,
6259
+ canJoinWaitlist: admission.kind === "waitlist",
6260
+ onRetry: wizard.handleRetry
6261
+ });
6262
+ }
5995
6263
  //#endregion
5996
6264
  //#region ../blocks/src/system/runtime/nodes/event-registration/EventRegistrationWizard.tsx
5997
6265
  const EVENT_PORTAL_AUTH_COPY = {
@@ -6039,11 +6307,12 @@ function getWizardStepLabel(input) {
6039
6307
  case "select-event": return input.eventSelectionLabel;
6040
6308
  case "select-tickets": return input.selectedEvent?.registrationSubject.kind === "wholeSeries" ? "Places" : "Tickets";
6041
6309
  case "enter-details": return "Details";
6042
- case "select-payment": return "Payment";
6310
+ case "select-payment": return "Payment options";
6311
+ case "payment": return "Payment";
6312
+ case "fully-booked":
6043
6313
  case "error":
6044
6314
  case "success":
6045
6315
  case "submitting":
6046
- case "redirecting":
6047
6316
  case "verifying-payment":
6048
6317
  case "verifying-timeout":
6049
6318
  case "payment-status":
@@ -6074,7 +6343,6 @@ function EventRegistrationWizardView(viewProps) {
6074
6343
  const showSpamProtection = spamProtectionEnabled ?? isSpamProtectionEnabled();
6075
6344
  const registrationRootClassName = `event-registration ${className || ""}`;
6076
6345
  const registrationRootRef = React.useRef(null);
6077
- useEventCheckoutHandoffRestore(wizard.step === "redirecting");
6078
6346
  const [bookingOverlayOpen, setBookingOverlayOpen] = useState(false);
6079
6347
  const [floatingPortalRoot, setFloatingPortalRoot] = useState(null);
6080
6348
  useEffect(() => {
@@ -6114,19 +6382,19 @@ function EventRegistrationWizardView(viewProps) {
6114
6382
  })
6115
6383
  })
6116
6384
  });
6117
- const isWizardStep = WIZARD_STEPS.includes(wizard.step);
6118
6385
  const activeWizardSteps = useMemo(() => {
6119
6386
  const needsPaymentStep = requiresPaymentStep({
6120
6387
  eventRequiresPayment: wizard.eventRequiresPayment,
6121
6388
  paymentSelection: wizard.paymentSelection
6122
6389
  });
6123
- return getWizardStepsForContext(wizard.hasPreSelectedContext, needsPaymentStep);
6390
+ return getWizardStepsForContext(wizard.hasPreSelectedContext, needsPaymentStep, wizard.step === "payment");
6124
6391
  }, [
6392
+ wizard.step,
6125
6393
  wizard.hasPreSelectedContext,
6126
6394
  wizard.eventRequiresPayment,
6127
6395
  wizard.paymentSelection
6128
6396
  ]);
6129
- const totalSteps = activeWizardSteps.length;
6397
+ const { isWizardStep, totalSteps } = getRegistrationStepPresentation(wizard.step, activeWizardSteps);
6130
6398
  const summaryEvent = wizard.selectedEvent ?? wizard.preSelectedEvent;
6131
6399
  const eventSelectionStepLabel = getEventSelectionMode(wizard.scopedEvents) === "date" ? "Date" : "Event";
6132
6400
  const stepLabels = activeWizardSteps.map((step) => getWizardStepLabel({
@@ -6186,7 +6454,10 @@ function EventRegistrationWizardView(viewProps) {
6186
6454
  ticketCurrency: bookingCurrency,
6187
6455
  buttonVariant,
6188
6456
  formatTicketPrice: (ticketType) => formatTicketPriceIfKnown(ticketType, bookingCurrency),
6189
- onOpen: () => setBookingOverlayOpen(true),
6457
+ onOpen: () => {
6458
+ if (wizard.paymentController.state.kind === "payment") wizard.paymentController.retryPaymentLoad();
6459
+ setBookingOverlayOpen(true);
6460
+ },
6190
6461
  onClose: () => setBookingOverlayOpen(false),
6191
6462
  overlaySurface: renderRegistrationSurface("overlay")
6192
6463
  })]