@bigz-app/booking-widget 1.1.6 → 1.1.8

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 (29) hide show
  1. package/README.md +3 -1
  2. package/dist/booking-widget.js +176 -181
  3. package/dist/booking-widget.js.map +1 -1
  4. package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
  5. package/dist/components/booking/BookingForm.d.ts +0 -1
  6. package/dist/components/booking/BookingForm.d.ts.map +1 -1
  7. package/dist/components/booking/BookingSuccessModal.d.ts.map +1 -1
  8. package/dist/components/booking/GiftCardOnlyBooking.d.ts +22 -0
  9. package/dist/components/booking/GiftCardOnlyBooking.d.ts.map +1 -0
  10. package/dist/components/booking/MolliePaymentForm.d.ts +2 -6
  11. package/dist/components/booking/MolliePaymentForm.d.ts.map +1 -1
  12. package/dist/components/booking/{PaymentForm.d.ts → StripePaymentForm.d.ts} +4 -8
  13. package/dist/components/booking/StripePaymentForm.d.ts.map +1 -0
  14. package/dist/components/booking/VoucherInput.d.ts +8 -0
  15. package/dist/components/booking/VoucherInput.d.ts.map +1 -1
  16. package/dist/components/booking/index.d.ts +2 -1
  17. package/dist/components/booking/index.d.ts.map +1 -1
  18. package/dist/components/upsells/UpsellCard.d.ts +1 -2
  19. package/dist/components/upsells/UpsellCard.d.ts.map +1 -1
  20. package/dist/components/upsells/UpsellsStep.d.ts +1 -4
  21. package/dist/components/upsells/UpsellsStep.d.ts.map +1 -1
  22. package/dist/components/upsells/index.d.ts +1 -1
  23. package/dist/components/upsells/index.d.ts.map +1 -1
  24. package/dist/index.cjs +176 -181
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.esm.js +176 -181
  27. package/dist/index.esm.js.map +1 -1
  28. package/package.json +1 -1
  29. package/dist/components/booking/PaymentForm.d.ts.map +0 -1
package/dist/index.cjs CHANGED
@@ -223,7 +223,7 @@ const de$1 = {
223
223
  "error.loadBookingData": "Fehler beim Abrufen der Buchungsdaten",
224
224
  "error.processingError": "Ein Fehler ist bei der Verarbeitung aufgetreten.",
225
225
  "error.createBooking": "Fehler beim Erstellen der Buchung",
226
- "error.createPaymentIntent": "Fehler beim Erstellen der Zahlungsabsicht",
226
+ "error.createPayment": "Fehler beim Erstellen der Zahlung",
227
227
  "error.paymentProcessing": "Fehler beim Verarbeiten der Zahlung",
228
228
  "error.paymentFailed": "Die Zahlung war nicht erfolgreich. Bitte versuche es erneut.",
229
229
  "error.paymentIncomplete": "Die Zahlung konnte nicht abgeschlossen werden. Bitte versuche es erneut.",
@@ -337,7 +337,7 @@ const de$1 = {
337
337
  "voucher.giftCardApplied": "−{{amount}} Gutschein",
338
338
  "voucher.remaining": "Rest: {{amount}}",
339
339
  "voucher.remove": "Entfernen",
340
- "voucher.alreadyHasDiscount": "Es wurde bereits ein Rabattcode angewendet. Du kannst weitere Gutscheine hinzufügen.",
340
+ "voucher.alreadyHasDiscount": "Du kannst weitere Gutscheine hinzufügen.",
341
341
  // Booking success
342
342
  "success.title": "Reservierung erfolgreich!",
343
343
  "success.bookingDetails": "Buchungsdetails",
@@ -432,7 +432,7 @@ const en = {
432
432
  "error.loadBookingData": "Error fetching booking data",
433
433
  "error.processingError": "An error occurred during processing.",
434
434
  "error.createBooking": "Error creating booking",
435
- "error.createPaymentIntent": "Error creating payment intent",
435
+ "error.createPayment": "Error creating payment",
436
436
  "error.paymentProcessing": "Error processing payment",
437
437
  "error.paymentFailed": "The payment was not successful. Please try again.",
438
438
  "error.paymentIncomplete": "The payment could not be completed. Please try again.",
@@ -641,7 +641,7 @@ const es = {
641
641
  "error.loadBookingData": "Error al obtener datos de la reserva",
642
642
  "error.processingError": "Ocurrió un error durante el procesamiento.",
643
643
  "error.createBooking": "Error al crear la reserva",
644
- "error.createPaymentIntent": "Error al crear la intención de pago",
644
+ "error.createPayment": "Error al crear el pago",
645
645
  "error.paymentProcessing": "Error al procesar el pago",
646
646
  "error.paymentFailed": "El pago no fue exitoso. Por favor, inténtalo de nuevo.",
647
647
  "error.paymentIncomplete": "El pago no pudo completarse. Por favor, inténtalo de nuevo.",
@@ -850,7 +850,7 @@ const pt = {
850
850
  "error.loadBookingData": "Erro ao obter dados da reserva",
851
851
  "error.processingError": "Ocorreu um erro durante o processamento.",
852
852
  "error.createBooking": "Erro ao criar reserva",
853
- "error.createPaymentIntent": "Erro ao criar intenção de pagamento",
853
+ "error.createPayment": "Erro ao criar pagamento",
854
854
  "error.paymentProcessing": "Erro ao processar pagamento",
855
855
  "error.paymentFailed": "O pagamento não foi bem-sucedido. Por favor, tente novamente.",
856
856
  "error.paymentIncomplete": "O pagamento não pôde ser concluído. Por favor, tente novamente.",
@@ -1059,7 +1059,7 @@ const sv = {
1059
1059
  "error.loadBookingData": "Fel vid hämtning av bokningsdata",
1060
1060
  "error.processingError": "Ett fel uppstod vid bearbetningen.",
1061
1061
  "error.createBooking": "Fel vid skapande av bokning",
1062
- "error.createPaymentIntent": "Fel vid skapande av betalningsavsikt",
1062
+ "error.createPayment": "Fel vid skapande av betalning",
1063
1063
  "error.paymentProcessing": "Fel vid bearbetning av betalning",
1064
1064
  "error.paymentFailed": "Betalningen lyckades inte. Försök igen.",
1065
1065
  "error.paymentIncomplete": "Betalningen kunde inte slutföras. Försök igen.",
@@ -4418,6 +4418,108 @@ const IconChevronLeft = ({ size = 20, color = "white", className, }) => (jsxRunt
4418
4418
  // Chevron Right icon - used for carousel navigation
4419
4419
  const IconChevronRight = ({ size = 20, color = "white", className, }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" }) }));
4420
4420
 
4421
+ function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, upsellSelections = [], }) {
4422
+ const t = useTranslations();
4423
+ const [isLoading, setIsLoading] = React.useState(false);
4424
+ const [error, setError] = React.useState(null);
4425
+ const handleBooking = async () => {
4426
+ setIsLoading(true);
4427
+ setError(null);
4428
+ try {
4429
+ const requestData = {
4430
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
4431
+ organizationId: config.organizationId,
4432
+ participants: formData.participants.filter((p) => p.name?.trim()),
4433
+ discountCode: discountCode?.code,
4434
+ giftCardCodes: giftCards.map((gc) => gc.code),
4435
+ customerName: formData.customerName?.trim(),
4436
+ customerEmail: formData.customerEmail?.trim(),
4437
+ customerPhone: formData.customerPhone?.trim(),
4438
+ comment: formData.comment?.trim(),
4439
+ paymentMethod: "gift_card",
4440
+ ...(upsellSelections.length > 0 && { upsellSelections }),
4441
+ };
4442
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
4443
+ method: "POST",
4444
+ headers: createApiHeaders(config),
4445
+ body: JSON.stringify(createRequestBody(config, requestData)),
4446
+ });
4447
+ const data = await response.json();
4448
+ if (response.ok) {
4449
+ onSuccess({
4450
+ booking: data.booking,
4451
+ order: data.order,
4452
+ giftCardRedemptions: data.giftCardRedemptions,
4453
+ });
4454
+ }
4455
+ else {
4456
+ setError(data.error || t("error.createBooking"));
4457
+ onError(data.error || t("error.createBooking"));
4458
+ }
4459
+ }
4460
+ catch (err) {
4461
+ setError(err.message || t("error.createBooking"));
4462
+ onError(err.message || t("error.createBooking"));
4463
+ }
4464
+ finally {
4465
+ setIsLoading(false);
4466
+ }
4467
+ };
4468
+ const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
4469
+ return (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [jsxRuntime.jsxs("div", { style: {
4470
+ backgroundColor: "rgba(var(--bw-success-color), 0.15)",
4471
+ border: "1px solid rgba(var(--bw-success-color), 0.4)",
4472
+ borderRadius: "var(--bw-border-radius)",
4473
+ padding: "16px",
4474
+ }, children: [jsxRuntime.jsxs("div", { style: {
4475
+ display: "flex",
4476
+ alignItems: "center",
4477
+ gap: "8px",
4478
+ marginBottom: "8px",
4479
+ color: "var(--bw-success-color)",
4480
+ fontFamily: "var(--bw-font-family)",
4481
+ fontWeight: 600,
4482
+ }, children: ["\uD83C\uDF81 ", t("payment.giftCardCovered")] }), jsxRuntime.jsx("div", { style: {
4483
+ fontSize: "16px",
4484
+ color: "var(--bw-text-muted)",
4485
+ fontFamily: "var(--bw-font-family)",
4486
+ }, children: t("payment.giftCardBalance", { amount: formatCurrency(totalGiftCardAmount) }) })] }), error && (jsxRuntime.jsxs("div", { style: {
4487
+ backgroundColor: "rgba(var(--bw-error-color), 0.15)",
4488
+ border: "1px solid rgba(var(--bw-error-color), 0.4)",
4489
+ borderRadius: "var(--bw-border-radius)",
4490
+ padding: "16px",
4491
+ color: "var(--bw-error-color)",
4492
+ fontSize: "16px",
4493
+ fontFamily: "var(--bw-font-family)",
4494
+ }, children: ["\u26A0\uFE0F ", error] })), jsxRuntime.jsx("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
4495
+ width: "100%",
4496
+ padding: "12px 24px",
4497
+ backgroundColor: "var(--bw-highlight-color)",
4498
+ color: "#ffffff",
4499
+ border: "none",
4500
+ borderRadius: "var(--bw-border-radius)",
4501
+ fontSize: "16px",
4502
+ fontWeight: 600,
4503
+ fontFamily: "var(--bw-font-family)",
4504
+ transition: "all 0.2s ease",
4505
+ display: "flex",
4506
+ alignItems: "center",
4507
+ justifyContent: "center",
4508
+ gap: "8px",
4509
+ cursor: isLoading ? "not-allowed" : "pointer",
4510
+ opacity: isLoading ? 0.6 : 1,
4511
+ }, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [spinner("#ffffff"), t("button.creatingBooking")] })) : (t("button.bookWithGiftCard")) })] }));
4512
+ }
4513
+ /**
4514
+ * Calculate whether gift cards fully cover the order after discounts.
4515
+ */
4516
+ function isGiftCardFullyCovered(giftCards, eventPrice, participantCount, discountAmount) {
4517
+ const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
4518
+ const baseTotal = eventPrice * participantCount;
4519
+ const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
4520
+ return totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
4521
+ }
4522
+
4421
4523
  const mollieLocaleMap = {
4422
4524
  de: "de_DE",
4423
4525
  en: "en_US",
@@ -4478,13 +4580,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
4478
4580
  const mollieRef = React.useRef(null);
4479
4581
  const cardFormRef = React.useRef(null);
4480
4582
  const cardContainerRef = React.useRef(null);
4481
- const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
4482
- const baseTotal = eventDetails?.price
4483
- ? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
4484
- : 0;
4485
- const discountAmount = discountCode?.discountAmount || 0;
4486
- const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
4487
- const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
4583
+ const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
4584
+ const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
4488
4585
  const isCreditCard = selectedMethod === "creditcard";
4489
4586
  React.useEffect(() => {
4490
4587
  if (isFullyCoveredByGiftCards)
@@ -4591,7 +4688,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
4591
4688
  };
4592
4689
  }, [isCreditCard, mollieProfileId, mollieTestmode, isFullyCoveredByGiftCards]);
4593
4690
  if (isFullyCoveredByGiftCards && totalAmount <= 0) {
4594
- return null;
4691
+ return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
4595
4692
  }
4596
4693
  const validateBeforePayment = () => {
4597
4694
  const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
@@ -4633,7 +4730,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
4633
4730
  }
4634
4731
  const { token, error: tokenError } = await mollieRef.current.createToken();
4635
4732
  if (tokenError || !token) {
4636
- setPaymentError(tokenError?.message || t("error.createPaymentIntent"));
4733
+ setPaymentError(tokenError?.message || t("error.createPayment"));
4637
4734
  return;
4638
4735
  }
4639
4736
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
@@ -4649,8 +4746,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
4649
4746
  _onSuccess({ paymentIntent: { id: data.molliePaymentId } });
4650
4747
  }
4651
4748
  else {
4652
- setPaymentError(data.error || t("error.createPaymentIntent"));
4653
- onError(data.error || t("error.createPaymentIntent"));
4749
+ setPaymentError(data.error || t("error.createPayment"));
4750
+ onError(data.error || t("error.createPayment"));
4654
4751
  }
4655
4752
  }
4656
4753
  catch (err) {
@@ -4682,8 +4779,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
4682
4779
  window.location.href = data.checkoutUrl;
4683
4780
  }
4684
4781
  else {
4685
- setPaymentError(data.error || t("error.createPaymentIntent"));
4686
- onError(data.error || t("error.createPaymentIntent"));
4782
+ setPaymentError(data.error || t("error.createPayment"));
4783
+ onError(data.error || t("error.createPayment"));
4687
4784
  }
4688
4785
  }
4689
4786
  catch (err) {
@@ -6153,106 +6250,6 @@ var reactStripe_umd = {exports: {}};
6153
6250
 
6154
6251
  var reactStripe_umdExports = reactStripe_umd.exports;
6155
6252
 
6156
- // Component for bookings fully covered by gift cards (no Stripe payment needed)
6157
- function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, upsellSelections = [], }) {
6158
- const t = useTranslations();
6159
- const [isLoading, setIsLoading] = React.useState(false);
6160
- const [error, setError] = React.useState(null);
6161
- const handleBooking = async () => {
6162
- setIsLoading(true);
6163
- setError(null);
6164
- try {
6165
- const requestData = {
6166
- eventInstanceId: config.eventInstanceId || eventDetails.id,
6167
- organizationId: config.organizationId,
6168
- participants: formData.participants.filter((p) => p.name?.trim()),
6169
- discountCode: discountCode?.code,
6170
- giftCardCodes: giftCards.map((gc) => gc.code),
6171
- customerName: formData.customerName?.trim(),
6172
- customerEmail: formData.customerEmail?.trim(),
6173
- customerPhone: formData.customerPhone?.trim(),
6174
- comment: formData.comment?.trim(),
6175
- paymentMethod: "gift_card",
6176
- ...(upsellSelections.length > 0 && { upsellSelections }),
6177
- };
6178
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
6179
- method: "POST",
6180
- headers: createApiHeaders(config),
6181
- body: JSON.stringify(createRequestBody(config, requestData)),
6182
- });
6183
- const data = await response.json();
6184
- if (response.ok) {
6185
- onSuccess({
6186
- booking: data.booking,
6187
- order: data.order,
6188
- giftCardRedemptions: data.giftCardRedemptions,
6189
- });
6190
- }
6191
- else {
6192
- setError(data.error || t("error.createBooking"));
6193
- onError(data.error || t("error.createBooking"));
6194
- }
6195
- }
6196
- catch (err) {
6197
- setError(err.message || t("error.createBooking"));
6198
- onError(err.message || t("error.createBooking"));
6199
- }
6200
- finally {
6201
- setIsLoading(false);
6202
- }
6203
- };
6204
- const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
6205
- return (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [jsxRuntime.jsxs("div", { style: {
6206
- backgroundColor: "rgba(var(--bw-success-color), 0.15)",
6207
- border: "1px solid rgba(var(--bw-success-color), 0.4)",
6208
- borderRadius: "var(--bw-border-radius)",
6209
- padding: "16px",
6210
- }, children: [jsxRuntime.jsxs("div", { style: {
6211
- display: "flex",
6212
- alignItems: "center",
6213
- gap: "8px",
6214
- marginBottom: "8px",
6215
- color: "var(--bw-success-color)",
6216
- fontFamily: "var(--bw-font-family)",
6217
- fontWeight: 600,
6218
- }, children: ["\uD83C\uDF81 ", t("payment.giftCardCovered")] }), jsxRuntime.jsx("div", { style: {
6219
- fontSize: "16px",
6220
- color: "var(--bw-text-muted)",
6221
- fontFamily: "var(--bw-font-family)",
6222
- }, children: t("payment.giftCardBalance", { amount: formatCurrency(totalGiftCardAmount) }) })] }), error && (jsxRuntime.jsxs("div", { style: {
6223
- backgroundColor: "rgba(var(--bw-error-color), 0.15)",
6224
- border: "1px solid rgba(var(--bw-error-color), 0.4)",
6225
- borderRadius: "var(--bw-border-radius)",
6226
- padding: "16px",
6227
- color: "var(--bw-error-color)",
6228
- fontSize: "16px",
6229
- fontFamily: "var(--bw-font-family)",
6230
- }, children: ["\u26A0\uFE0F ", error] })), jsxRuntime.jsx("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
6231
- width: "100%",
6232
- padding: "12px 24px",
6233
- backgroundColor: "var(--bw-highlight-color)",
6234
- color: "#ffffff",
6235
- border: "none",
6236
- borderRadius: "var(--bw-border-radius)",
6237
- fontSize: "16px",
6238
- fontWeight: 600,
6239
- fontFamily: "var(--bw-font-family)",
6240
- transition: "all 0.2s ease",
6241
- display: "flex",
6242
- alignItems: "center",
6243
- justifyContent: "center",
6244
- gap: "8px",
6245
- cursor: isLoading ? "not-allowed" : "pointer",
6246
- opacity: isLoading ? 0.6 : 1,
6247
- }, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: {
6248
- width: "16px",
6249
- height: "16px",
6250
- border: "2px solid #ffffff",
6251
- borderTopColor: "transparent",
6252
- borderRadius: "50%",
6253
- animation: "spin 1s linear infinite",
6254
- } }), t("button.creatingBooking")] })) : (t("button.bookWithGiftCard")) })] }));
6255
- }
6256
6253
  // Inner component that uses the Stripe hooks
6257
6254
  function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSuccess, onError, }) {
6258
6255
  const t = useTranslations();
@@ -6289,7 +6286,7 @@ function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSucce
6289
6286
  redirect: "if_required",
6290
6287
  });
6291
6288
  if (error) {
6292
- console.error("[PAYMENT_FORM] Payment confirmation error:", {
6289
+ console.error("[STRIPE_PAYMENT] Payment confirmation error:", {
6293
6290
  type: error.type,
6294
6291
  code: error.code,
6295
6292
  message: error.message,
@@ -6365,8 +6362,7 @@ function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSucce
6365
6362
  ? t("button.depositAndBook")
6366
6363
  : t("button.bookNow") })) })] }));
6367
6364
  }
6368
- // Main PaymentForm component that handles payment intent creation and Elements wrapper
6369
- function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
6365
+ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
6370
6366
  const t = useTranslations();
6371
6367
  const [clientSecret, setClientSecret] = React.useState(null);
6372
6368
  const [paymentIntentId, setPaymentIntentId] = React.useState(null);
@@ -6427,7 +6423,7 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
6427
6423
  }
6428
6424
  }, [paymentIntentId]);
6429
6425
  React.useEffect(() => {
6430
- const createPaymentIntent = async () => {
6426
+ const createStripePayment = async () => {
6431
6427
  if (!systemConfig || !eventDetails || !formData.participants?.length) {
6432
6428
  return;
6433
6429
  }
@@ -6468,35 +6464,38 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
6468
6464
  if (!requestData.customerEmail) {
6469
6465
  throw new Error("Customer email is required");
6470
6466
  }
6471
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-payment-intent"), {
6467
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-stripe-payment"), {
6472
6468
  method: "POST",
6473
6469
  headers: createApiHeaders(config),
6474
6470
  body: JSON.stringify(createRequestBody(config, requestData)),
6475
6471
  });
6476
6472
  const data = await response.json();
6477
6473
  if (response.ok) {
6474
+ if (data.stalePaymentIntent) {
6475
+ clearPersistedPaymentIntent();
6476
+ }
6478
6477
  setClientSecret(data.clientSecret);
6479
6478
  setPaymentIntentId(data.paymentIntentId);
6480
6479
  }
6481
6480
  else {
6482
- console.error("[PAYMENT_FORM] Payment intent creation failed:", {
6481
+ console.error("[STRIPE_PAYMENT] Payment creation failed:", {
6483
6482
  status: response.status,
6484
6483
  error: data.error,
6485
6484
  details: data.details,
6486
6485
  requestData: { ...requestData, customerEmail: "[redacted]" },
6487
6486
  });
6488
- setPaymentError(data.error || t("error.createPaymentIntent"));
6487
+ setPaymentError(data.error || t("error.createPayment"));
6489
6488
  }
6490
6489
  }
6491
6490
  catch (err) {
6492
- console.error("[PAYMENT_FORM] Payment intent creation error:", err);
6493
- setPaymentError(err.message || t("error.createPaymentIntent"));
6491
+ console.error("[STRIPE_PAYMENT] Payment creation error:", err);
6492
+ setPaymentError(err.message || t("error.createPayment"));
6494
6493
  }
6495
6494
  finally {
6496
6495
  setIsCreatingPaymentIntent(false);
6497
6496
  }
6498
6497
  };
6499
- const timer = setTimeout(createPaymentIntent, 500);
6498
+ const timer = setTimeout(createStripePayment, 500);
6500
6499
  return () => clearTimeout(timer);
6501
6500
  }, [
6502
6501
  systemConfig,
@@ -6504,19 +6503,15 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
6504
6503
  formData.participants,
6505
6504
  formData.customerEmail,
6506
6505
  formData.customerName,
6506
+ formData.comment,
6507
6507
  totalAmount,
6508
6508
  discountCode,
6509
6509
  giftCards,
6510
6510
  config,
6511
6511
  upsellSelections,
6512
6512
  ]);
6513
- const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
6514
- const baseTotal = eventDetails?.price
6515
- ? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
6516
- : 0;
6517
- const discountAmount = discountCode?.discountAmount || 0;
6518
- const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
6519
- const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
6513
+ const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
6514
+ const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6520
6515
  if (isFullyCoveredByGiftCards && totalAmount <= 0) {
6521
6516
  return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
6522
6517
  }
@@ -11026,13 +11021,6 @@ function mergeStyles(...styles) {
11026
11021
  return Object.assign({}, ...styles.filter(Boolean));
11027
11022
  }
11028
11023
 
11029
- function getEffectivePrice(upsell, round) {
11030
- const dp = upsell.discountPercent ?? 0;
11031
- if (dp <= 0)
11032
- return upsell.price;
11033
- const raw = Math.round(upsell.price * (100 - dp) / 100);
11034
- return round ? Math.floor(raw / 100) * 100 : raw;
11035
- }
11036
11024
  // Local style aliases from shared styles
11037
11025
  const cardStyles = cardStyles$1.base;
11038
11026
  const sectionHeaderStyles = sectionStyles.header;
@@ -11044,6 +11032,11 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11044
11032
  const { locale } = useLocale();
11045
11033
  const timezone = useTimezone();
11046
11034
  const roundEnabled = systemConfig?.roundPricesEnabled !== false;
11035
+ const roundDiscountUp = (minorUnits) => Math.ceil(minorUnits / 100) * 100;
11036
+ const calcPercentDiscountAmount = (baseAmount, basisPoints, round) => {
11037
+ const raw = Math.round((baseAmount * basisPoints) / 10000);
11038
+ return round ? roundDiscountUp(raw) : raw;
11039
+ };
11047
11040
  const [appliedVouchers, setAppliedVouchers] = React.useState([]);
11048
11041
  const paymentSectionRef = React.useRef(null);
11049
11042
  // Payment option: "deposit" or "full" - only relevant when deposit is available
@@ -11065,6 +11058,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11065
11058
  const watchedParticipants = form.watch("participants");
11066
11059
  const watchedCustomerName = form.watch("customerName");
11067
11060
  const watchedCustomerEmail = form.watch("customerEmail");
11061
+ const watchedComment = form.watch("comment");
11062
+ const watchedCustomerPhone = form.watch("customerPhone");
11068
11063
  const customerNameError = form.formState.errors.customerName;
11069
11064
  const customerEmailError = form.formState.errors.customerEmail;
11070
11065
  const watchedAcceptTerms = form.watch("acceptTerms");
@@ -11114,7 +11109,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11114
11109
  participantUpsellIds.forEach(upsellId => {
11115
11110
  const upsell = upsells.find(u => u.id === upsellId);
11116
11111
  if (upsell) {
11117
- total += getEffectivePrice(upsell, roundEnabled);
11112
+ total += upsell.price;
11118
11113
  }
11119
11114
  });
11120
11115
  }
@@ -11176,6 +11171,13 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11176
11171
  participantIndices,
11177
11172
  }));
11178
11173
  }, [participantUpsells, watchedParticipants]);
11174
+ const paymentFormData = React.useMemo(() => ({
11175
+ customerName: watchedCustomerName,
11176
+ customerEmail: watchedCustomerEmail,
11177
+ customerPhone: watchedCustomerPhone,
11178
+ participants: watchedParticipants,
11179
+ comment: watchedComment,
11180
+ }), [watchedCustomerName, watchedCustomerEmail, watchedCustomerPhone, watchedParticipants, watchedComment]);
11179
11181
  const appliedDiscountCode = appliedVouchers.find((v) => v.type === "discount");
11180
11182
  const appliedGiftCards = appliedVouchers.filter((v) => v.type === "giftCard");
11181
11183
  const handleVoucherValidated = React.useCallback((voucher, _error) => {
@@ -11212,7 +11214,10 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11212
11214
  // TODO: discounts currently apply to base total only; extend when discount-applies-to-upsells flag is added
11213
11215
  let newDiscountAmount = 0;
11214
11216
  if (voucher.discountType === "percentage") {
11215
- newDiscountAmount = Math.round((newBaseTotal * (voucher.discountValue || 0)) / 10000);
11217
+ newDiscountAmount = calcPercentDiscountAmount(newBaseTotal, voucher.discountValue || 0, roundEnabled);
11218
+ if (voucher.restrictions?.maxDiscount) {
11219
+ newDiscountAmount = Math.min(newDiscountAmount, voucher.restrictions.maxDiscount);
11220
+ }
11216
11221
  }
11217
11222
  else {
11218
11223
  newDiscountAmount = voucher.discountValue || 0;
@@ -11390,7 +11395,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11390
11395
  padding: 0,
11391
11396
  }, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsxRuntime.jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11392
11397
  const isSelected = (participantUpsells[index] || []).includes(upsell.id);
11393
- return (jsxRuntime.jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsxRuntime.jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxRuntime.jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(getEffectivePrice(upsell, roundEnabled)), ")"] })] }, upsell.id));
11398
+ return (jsxRuntime.jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsxRuntime.jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxRuntime.jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(upsell.price), ")"] })] }, upsell.id));
11394
11399
  }) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsxRuntime.jsx("div", { style: {
11395
11400
  display: "flex",
11396
11401
  flexDirection: "column",
@@ -11422,7 +11427,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11422
11427
  const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
11423
11428
  if (countWithUpsell === 0)
11424
11429
  return null;
11425
- const upsellLineTotal = getEffectivePrice(upsell, roundEnabled) * countWithUpsell;
11430
+ const upsellLineTotal = upsell.price * countWithUpsell;
11426
11431
  return (jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "13px" }, children: [jsxRuntime.jsxs("span", { style: { color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+ ", upsell.name, " (", countWithUpsell, "\u00D7)"] }), jsxRuntime.jsx("span", { style: { color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: formatCurrency(upsellLineTotal) })] }, upsell.id));
11427
11432
  })] })), appliedVouchers.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("summary.subtotal") }), jsxRuntime.jsx("span", { style: {
11428
11433
  fontFamily: "var(--bw-font-family)",
@@ -11565,9 +11570,9 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11565
11570
  }
11566
11571
  : null;
11567
11572
  if (systemConfig?.paymentProvider === "mollie") {
11568
- return (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData: form.getValues(), totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, upsellSelections: aggregatedUpsellSelections(), mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode })] }));
11573
+ return (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, upsellSelections: aggregatedUpsellSelections(), mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode })] }));
11569
11574
  }
11570
- return (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsxRuntime.jsx(PaymentForm, { config: config, eventDetails: eventDetails, formData: form.getValues(), totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig ?? null, stripePromise: stripePromise, stripeAppearance: stripeAppearance, upsellSelections: aggregatedUpsellSelections() })] }));
11575
+ return (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsxRuntime.jsx(StripePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig ?? null, stripePromise: stripePromise, stripeAppearance: stripeAppearance, upsellSelections: aggregatedUpsellSelections() })] }));
11571
11576
  })() })] })] })] }) }));
11572
11577
  }
11573
11578
 
@@ -11701,7 +11706,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
11701
11706
  if (targetPaymentIntentId) {
11702
11707
  setIsLoading(true);
11703
11708
  try {
11704
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment-intent"), {
11709
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
11705
11710
  method: "POST",
11706
11711
  headers: createApiHeaders(config),
11707
11712
  body: JSON.stringify(createRequestBody(config, {
@@ -11717,7 +11722,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
11717
11722
  orderItems: data.orderItems,
11718
11723
  purchases: data.purchases || [],
11719
11724
  discount: data.discount || null,
11720
- stripePaymentIntent: data.stripePaymentIntent,
11725
+ providerPaymentDetails: data.stripePaymentIntent,
11721
11726
  });
11722
11727
  setEventDetails({
11723
11728
  id: data.booking.eventInstance.id,
@@ -11725,7 +11730,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
11725
11730
  description: data.booking.eventInstance.eventType.description,
11726
11731
  startTime: data.booking.eventInstance.startTime,
11727
11732
  endTime: data.booking.eventInstance.endTime,
11728
- price: data.booking.eventInstance.price || 0, // Use actual event instance price
11733
+ price: data.booking.eventInstance.price || 0,
11729
11734
  });
11730
11735
  setFormData({
11731
11736
  customerEmail: data.booking.customerEmail,
@@ -11733,9 +11738,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
11733
11738
  customerPhone: data.booking.customerPhone,
11734
11739
  participants: data.booking.participants || [],
11735
11740
  });
11736
- // Set payment status from Stripe data or order status
11737
11741
  setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
11738
- // Trigger Google Ads conversion tracking if payment is successful
11739
11742
  const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
11740
11743
  if (finalPaymentStatus === "succeeded" &&
11741
11744
  config.googleAds?.tagId &&
@@ -13685,11 +13688,10 @@ function formatUnavailableReason(reason, t) {
13685
13688
  return t("upsells.reason.notEnoughSpots", { eventTypeName: reason.eventTypeName });
13686
13689
  }
13687
13690
  }
13688
- function UpsellCard({ upsell, isSelected, participantCount, onSelect, roundPricesEnabled = true, }) {
13691
+ function UpsellCard({ upsell, isSelected, participantCount, onSelect, }) {
13689
13692
  const t = useTranslations();
13690
13693
  const { locale } = useLocale();
13691
- const effectivePrice = getEffectiveUpsellPrice(upsell, roundPricesEnabled);
13692
- const totalPrice = effectivePrice * participantCount;
13694
+ const totalPrice = upsell.price * participantCount;
13693
13695
  const isDisabled = !upsell.available;
13694
13696
  const getCardStyles = () => {
13695
13697
  if (isDisabled)
@@ -13707,19 +13709,12 @@ function UpsellCard({ upsell, isSelected, participantCount, onSelect, roundPrice
13707
13709
  weekday: "short",
13708
13710
  day: "numeric",
13709
13711
  month: "short",
13710
- })] }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)" }, children: t("upsells.spotsFree", { count: upsell.suggestedEventInstance.availableSpots }) })] }))] }), jsxRuntime.jsxs("div", { style: priceContainerStyles, children: [jsxRuntime.jsxs("span", { style: pricePerPersonStyles, children: [formatCurrency(effectivePrice), "/", t("common.perPerson")] }), participantCount > 1 && (jsxRuntime.jsxs("span", { style: priceTotalStyles, children: ["= ", formatCurrency(totalPrice)] }))] }), isDisabled && (jsxRuntime.jsx("div", { style: unavailableOverlayStyles, children: jsxRuntime.jsx("span", { children: upsell.unavailableReason
13712
+ })] }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)" }, children: t("upsells.spotsFree", { count: upsell.suggestedEventInstance.availableSpots }) })] }))] }), jsxRuntime.jsxs("div", { style: priceContainerStyles, children: [jsxRuntime.jsxs("span", { style: pricePerPersonStyles, children: [formatCurrency(upsell.price), "/", t("common.perPerson")] }), participantCount > 1 && (jsxRuntime.jsxs("span", { style: priceTotalStyles, children: ["= ", formatCurrency(totalPrice)] }))] }), isDisabled && (jsxRuntime.jsx("div", { style: unavailableOverlayStyles, children: jsxRuntime.jsx("span", { children: upsell.unavailableReason
13711
13713
  ? formatUnavailableReason(upsell.unavailableReason, t)
13712
13714
  : t("upsells.notAvailable") }) }))] }));
13713
13715
  }
13714
13716
 
13715
- function getEffectiveUpsellPrice(upsell, round = true) {
13716
- const dp = upsell.discountPercent ?? 0;
13717
- if (dp <= 0)
13718
- return upsell.price;
13719
- const raw = Math.round(upsell.price * (100 - dp) / 100);
13720
- return round ? Math.floor(raw / 100) * 100 : raw;
13721
- }
13722
- function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, isOpen, onClose, onSelect, onContinue, onBack, roundPricesEnabled = true, }) {
13717
+ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, isOpen, onClose, onSelect, onContinue, onBack, }) {
13723
13718
  const t = useTranslations();
13724
13719
  const selectUpsell = (upsellId) => {
13725
13720
  const exists = selectedUpsells.find((s) => s.upsellPackageId === upsellId);
@@ -13738,7 +13733,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
13738
13733
  return selectedUpsells.reduce((total, selection) => {
13739
13734
  const upsell = upsells.find((u) => u.id === selection.upsellPackageId);
13740
13735
  if (upsell) {
13741
- return total + getEffectiveUpsellPrice(upsell, roundPricesEnabled) * selection.quantity;
13736
+ return total + upsell.price * selection.quantity;
13742
13737
  }
13743
13738
  return total;
13744
13739
  }, 0);
@@ -13746,7 +13741,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
13746
13741
  const selectedTotal = calculateTotal();
13747
13742
  const selectedCount = selectedUpsells.length;
13748
13743
  const footerContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", onClick: onBack, style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), children: t("common.back") }), jsxRuntime.jsx("button", { type: "button", onClick: onContinue, style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), children: selectedCount === 0 ? t("button.continueWithout") : t("button.continue") })] }));
13749
- return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsxRuntime.jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsxRuntime.jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsxRuntime.jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id), roundPricesEnabled: roundPricesEnabled }, upsell.id))) })), selectedCount > 0 && (jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: "16px", paddingBottom: "16px", paddingTop: "16px", borderTop: "1px solid var(--bw-border-color)", fontSize: "14px" }, children: [jsxRuntime.jsx("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
13744
+ return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsxRuntime.jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsxRuntime.jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsxRuntime.jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id) }, upsell.id))) })), selectedCount > 0 && (jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: "16px", paddingBottom: "16px", paddingTop: "16px", borderTop: "1px solid var(--bw-border-color)", fontSize: "14px" }, children: [jsxRuntime.jsx("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
13750
13745
  }
13751
13746
 
13752
13747
  // Main widget component
@@ -13792,7 +13787,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
13792
13787
  const [isLoadingShowAll, setIsLoadingShowAll] = React.useState(false);
13793
13788
  const [error, setError] = React.useState(null);
13794
13789
  const [isSuccess, setIsSuccess] = React.useState(false);
13795
- const [successPaymentIntentId, setSuccessPaymentIntentId] = React.useState(null);
13790
+ const [successPaymentId, setSuccessPaymentId] = React.useState(null);
13796
13791
  const [systemConfig, setSystemConfig] = React.useState(null);
13797
13792
  // PERFORMANCE OPTIMIZATION: Lazy component loading
13798
13793
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
@@ -13858,7 +13853,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
13858
13853
  const stripeReturn = detectStripeReturn();
13859
13854
  if (stripeReturn) {
13860
13855
  if (stripeReturn.redirectStatus === "succeeded") {
13861
- setSuccessPaymentIntentId(stripeReturn.paymentIntent);
13856
+ setSuccessPaymentId(stripeReturn.paymentIntent);
13862
13857
  setIsSuccess(true);
13863
13858
  }
13864
13859
  else if (stripeReturn.redirectStatus === "failed") {
@@ -14213,7 +14208,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14213
14208
  };
14214
14209
  const handleBookingSuccess = (result) => {
14215
14210
  setIsSuccess(true);
14216
- setSuccessPaymentIntentId(result.paymentIntent.id);
14211
+ setSuccessPaymentId(result.paymentIntent.id);
14217
14212
  setSidebarOpen(false);
14218
14213
  setShouldRenderBookingForm(false);
14219
14214
  config.onSuccess?.(result);
@@ -14411,7 +14406,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14411
14406
  setIsSuccess(false);
14412
14407
  setCurrentStep("eventTypes");
14413
14408
  setShowingPreview(true);
14414
- setSuccessPaymentIntentId(null);
14409
+ setSuccessPaymentId(null);
14415
14410
  setShouldRenderInstanceSelection(false);
14416
14411
  setShouldRenderUpsells(false);
14417
14412
  setShouldRenderBookingForm(false);
@@ -14422,7 +14417,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14422
14417
  url.searchParams.delete("payment_intent_client_secret");
14423
14418
  url.searchParams.delete("redirect_status");
14424
14419
  window.history.replaceState({}, "", url.toString());
14425
- }, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
14420
+ }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
14426
14421
  }
14427
14422
  if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
14428
14423
  return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
@@ -14435,7 +14430,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14435
14430
  setIsSuccess(false);
14436
14431
  setCurrentStep("eventTypes");
14437
14432
  setShowingPreview(true);
14438
- setSuccessPaymentIntentId(null);
14433
+ setSuccessPaymentId(null);
14439
14434
  setShouldRenderInstanceSelection(false);
14440
14435
  setShouldRenderBookingForm(false);
14441
14436
  const url = new URL(window.location.href);
@@ -14443,7 +14438,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14443
14438
  url.searchParams.delete("payment_intent_client_secret");
14444
14439
  url.searchParams.delete("redirect_status");
14445
14440
  window.history.replaceState({}, "", url.toString());
14446
- }, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
14441
+ }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
14447
14442
  }
14448
14443
  if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
14449
14444
  return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, style: {
@@ -14472,11 +14467,11 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14472
14467
  setShouldRenderInstanceSelection(true);
14473
14468
  }
14474
14469
  }, children: config.buttonText ||
14475
- (isDirectInstanceMode ? t("button.bookNow") : t("button.selectDate")) }), shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack, roundPricesEnabled: systemConfig?.roundPricesEnabled !== false })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => setCurrentStep(isDirectInstanceMode ? "eventTypes" : "eventInstances"), onBackToEventTypes: () => setSidebarOpen(false), selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: () => setCurrentStep(isDirectInstanceMode ? "eventTypes" : "eventInstances"), systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
14470
+ (isDirectInstanceMode ? t("button.bookNow") : t("button.selectDate")) }), shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => setCurrentStep(isDirectInstanceMode ? "eventTypes" : "eventInstances"), onBackToEventTypes: () => setSidebarOpen(false), selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: () => setCurrentStep(isDirectInstanceMode ? "eventTypes" : "eventInstances"), systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
14476
14471
  setIsSuccess(false);
14477
14472
  setCurrentStep("eventTypes");
14478
14473
  setSidebarOpen(false);
14479
- setSuccessPaymentIntentId(null);
14474
+ setSuccessPaymentId(null);
14480
14475
  setShouldRenderInstanceSelection(false);
14481
14476
  setShouldRenderUpsells(false);
14482
14477
  setShouldRenderBookingForm(false);
@@ -14487,7 +14482,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14487
14482
  url.searchParams.delete("payment_intent_client_secret");
14488
14483
  url.searchParams.delete("redirect_status");
14489
14484
  window.history.replaceState({}, "", url.toString());
14490
- }, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
14485
+ }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
14491
14486
  }
14492
14487
  // Cards mode (default) - show event type selection
14493
14488
  const cardsView = (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, isLoading: isLoading, skeletonCount: getSkeletonCount() }));
@@ -14519,10 +14514,10 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14519
14514
  };
14520
14515
  };
14521
14516
  const backHandlers = getBackHandlers();
14522
- return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack, roundPricesEnabled: systemConfig?.roundPricesEnabled !== false })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: backHandlers.onBackToEventInstances, onBackToEventTypes: backHandlers.onBackToEventTypes, selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
14517
+ return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: backHandlers.onBackToEventInstances, onBackToEventTypes: backHandlers.onBackToEventTypes, selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
14523
14518
  setIsSuccess(false);
14524
14519
  setCurrentStep("eventTypes");
14525
- setSuccessPaymentIntentId(null);
14520
+ setSuccessPaymentId(null);
14526
14521
  setShouldRenderInstanceSelection(false);
14527
14522
  setShouldRenderUpsells(false);
14528
14523
  setShouldRenderBookingForm(false);
@@ -14533,7 +14528,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
14533
14528
  url.searchParams.delete("payment_intent_client_secret");
14534
14529
  url.searchParams.delete("redirect_status");
14535
14530
  window.history.replaceState({}, "", url.toString());
14536
- }, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
14531
+ }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
14537
14532
  }
14538
14533
  function UniversalBookingWidget(props) {
14539
14534
  const [languagePolicy, setLanguagePolicy] = React.useState(null);