@ticketboothapp/booking 1.2.164 → 1.2.166

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ticketboothapp/booking",
3
- "version": "1.2.164",
3
+ "version": "1.2.166",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -924,7 +924,7 @@ export function AdminChangeBookingFlow({
924
924
  checkoutModalData,
925
925
  showAdminPaymentChoice,
926
926
  adminChoiceData,
927
- cancelPendingReservation,
927
+ handleCheckoutClose,
928
928
  handleCheckout,
929
929
  handleConfirmWithoutPayment,
930
930
  handlePayNow,
@@ -1022,7 +1022,7 @@ export function AdminChangeBookingFlow({
1022
1022
  ...{ pickupLocationId, pickupLocationSkipped, firstName, lastName, email, getSuccessUrl, t },
1023
1023
  onPayNow: handlePayNow,
1024
1024
  onConfirmWithoutPayment: handleConfirmWithoutPayment, onAdminPaymentChoiceCancel: handleAdminPaymentChoiceCancel,
1025
- onCheckoutClose: cancelPendingReservation, onPaymentSubmitStart: handlePaymentSubmitStart,
1025
+ onCheckoutClose: handleCheckoutClose, onPaymentSubmitStart: handlePaymentSubmitStart,
1026
1026
  onPaymentSubmitError: handlePaymentSubmitError, onPaymentConfirmed: handlePaymentConfirmed,
1027
1027
  isPartialLaunch,
1028
1028
  checkoutVisible: selectedAvailability != null,
@@ -126,6 +126,18 @@ export function buildAdminChangePayNowCheckoutModalData(
126
126
  };
127
127
  }
128
128
 
129
+ export function shouldRestoreAdminChangePaymentChoiceAfterCheckoutClose(
130
+ isProviderDashboardChange: boolean,
131
+ isChangeBookingContext: boolean,
132
+ adminChoiceData: Pick<AdminChangePaymentChoiceData, 'pricingQuoteId'> | null,
133
+ ): boolean {
134
+ return Boolean(
135
+ isProviderDashboardChange &&
136
+ isChangeBookingContext &&
137
+ adminChoiceData?.pricingQuoteId?.trim(),
138
+ );
139
+ }
140
+
129
141
  export interface ConfirmAdminChangeBookingWithoutPaymentParams {
130
142
  adminChoiceData: AdminChangePaymentChoiceData;
131
143
  bookingSourceAttribution: Partial<BookingSourceMetadata>;
@@ -59,6 +59,7 @@ import {
59
59
  buildAdminChangePaymentChoiceData,
60
60
  buildAdminChangePayNowCheckoutModalData,
61
61
  confirmAdminChangeBookingWithoutPayment,
62
+ shouldRestoreAdminChangePaymentChoiceAfterCheckoutClose,
62
63
  type AdminChangeCheckoutModalData,
63
64
  type AdminChangePaymentChoiceData,
64
65
  } from './admin-change-payment-choice-runner';
@@ -292,6 +293,27 @@ export function useAdminChangeCheckoutController({
292
293
  setCheckoutClientSecret('');
293
294
  }, []);
294
295
 
296
+ const handleCheckoutClose = useCallback(() => {
297
+ if (paymentSubmitInFlightRef.current) return;
298
+ cancelPendingReservation();
299
+ setError('');
300
+ // An existing-booking Pay now attempt owns an immutable quote/payment-choice snapshot.
301
+ // Closing Stripe must return to that choice instead of making the user create a new quote.
302
+ if (shouldRestoreAdminChangePaymentChoiceAfterCheckoutClose(
303
+ isProviderDashboardChange,
304
+ isChangeBookingContext,
305
+ adminChoiceData,
306
+ )) {
307
+ setShowAdminPaymentChoice(true);
308
+ }
309
+ }, [
310
+ adminChoiceData?.pricingQuoteId,
311
+ cancelPendingReservation,
312
+ isChangeBookingContext,
313
+ isProviderDashboardChange,
314
+ setError,
315
+ ]);
316
+
295
317
  const cancelPendingReservationBestEffort = useCallback(() => {
296
318
  if (paymentSubmitInFlightRef.current) return;
297
319
  const pending = pendingReservationRef.current;
@@ -323,6 +345,7 @@ export function useAdminChangeCheckoutController({
323
345
  const buildProviderChangePayload = (
324
346
  availabilityProductOptionId: string,
325
347
  bookingItems: AdminChangeBookingItem[],
348
+ authoritativeQuote: AdminChangeLatestQuote | null = latestChangeQuote,
326
349
  ): ProviderDashboardChangeBookingPayload | null => {
327
350
  return buildAdminChangeProviderPayload({
328
351
  selectedAvailability,
@@ -340,8 +363,8 @@ export function useAdminChangeCheckoutController({
340
363
  mergedProviderAdditionalAdjustments,
341
364
  adminStructuredAdjustments,
342
365
  refundDispositionsByOperationId,
343
- pricingV2QuoteId: latestChangeQuote?.quoteId ?? null,
344
- pricingV2QuotedTotal: latestChangeQuote?.quotedTotal ?? latestChangeQuote?.serverDisplay?.total ?? null,
366
+ pricingV2QuoteId: authoritativeQuote?.quoteId ?? null,
367
+ pricingV2QuotedTotal: authoritativeQuote?.quotedTotal ?? authoritativeQuote?.serverDisplay?.total ?? null,
345
368
  providerApplyAuthoritativeReceipt,
346
369
  previousPassengerCount: changeFlowInitialTicketCount,
347
370
  previousAvailabilityId: initialValues?.availabilityId ?? null,
@@ -436,6 +459,7 @@ export function useAdminChangeCheckoutController({
436
459
  let changeIntentIdForCheckout: string | undefined;
437
460
  let changeBookingReferenceForPaidFlow: string | undefined;
438
461
  let confirmedChangeAmountDueForCheckout: number | null = null;
462
+ let authoritativeChangeQuoteForCheckout = latestChangeQuote;
439
463
 
440
464
  if (isCustomerSelfServeChange) {
441
465
  const quoteResult = await quoteAdminCustomerChangeForCheckout({
@@ -464,11 +488,18 @@ export function useAdminChangeCheckoutController({
464
488
  });
465
489
  changeBookingReferenceForPaidFlow = quoteResult.bookingReference;
466
490
  setLatestChangeQuote(quoteResult.mergedQuote);
491
+ // React state is not updated synchronously. Everything in this checkout attempt must
492
+ // use the quote returned above, not the stale latestChangeQuote render closure.
493
+ authoritativeChangeQuoteForCheckout = quoteResult.mergedQuote;
467
494
  confirmedChangeAmountDueForCheckout = quoteResult.decision.amountDueForCheckout;
468
495
 
469
496
  if (quoteResult.decision.kind === 'free') {
470
497
  if (onChangeBooking) {
471
- const providerPayload = buildProviderChangePayload(availabilityProductOptionId, bookingItems);
498
+ const providerPayload = buildProviderChangePayload(
499
+ availabilityProductOptionId,
500
+ bookingItems,
501
+ authoritativeChangeQuoteForCheckout,
502
+ );
472
503
  if (!providerPayload) {
473
504
  setError('No availability selected');
474
505
  setLoading(false);
@@ -501,7 +532,7 @@ export function useAdminChangeCheckoutController({
501
532
  const itineraryDisplay = computeItineraryDisplayForStorage() ?? computeItineraryDisplay();
502
533
  const taxForBreakdown = effectivePromoDiscountAmount > 0 ? effectiveTax : tax;
503
534
  const amountDueForCheckout = isProviderDashboardChange && isChangeBookingContext
504
- ? Math.max(0, latestChangeQuote?.priceDiff ?? 0)
535
+ ? Math.max(0, authoritativeChangeQuoteForCheckout?.priceDiff ?? 0)
505
536
  : isCustomerSelfServeChange
506
537
  ? confirmedChangeAmountDueForCheckout ??
507
538
  Math.max(
@@ -546,14 +577,21 @@ export function useAdminChangeCheckoutController({
546
577
  // quote-bound Stripe authorization and applies only after that authorization succeeds.
547
578
  if (isProviderDashboardChange && isChangeBookingContext) {
548
579
  const bookingReference = initialValues?.bookingReference?.trim();
549
- const quoteId = latestChangeQuote?.quoteId?.trim();
580
+ const quoteId = authoritativeChangeQuoteForCheckout?.quoteId?.trim();
550
581
  if (!bookingReference) throw new Error('Missing booking reference.');
551
- if (!quoteId || latestChangeQuote?.canProceed === false) {
552
- throw new Error(latestChangeQuote?.reasonIfBlocked || 'The authoritative change quote is not ready.');
582
+ if (!quoteId || authoritativeChangeQuoteForCheckout?.canProceed === false) {
583
+ throw new Error(
584
+ authoritativeChangeQuoteForCheckout?.reasonIfBlocked ||
585
+ 'The authoritative change quote is not ready.',
586
+ );
553
587
  }
554
588
  if (amountDueForCheckout <= 0) {
555
589
  if (!onChangeBooking) throw new Error('Provider change handler is unavailable.');
556
- const providerPayload = buildProviderChangePayload(availabilityProductOptionId, bookingItems);
590
+ const providerPayload = buildProviderChangePayload(
591
+ availabilityProductOptionId,
592
+ bookingItems,
593
+ authoritativeChangeQuoteForCheckout,
594
+ );
557
595
  if (!providerPayload) throw new Error('No availability selected');
558
596
  await onChangeBooking(providerPayload);
559
597
  onSuccess?.({ reservationReference: bookingReference });
@@ -590,7 +628,9 @@ export function useAdminChangeCheckoutController({
590
628
  'Pay later to apply the change and add the amount to the booking balance, or collect a new card payment before applying it.',
591
629
  confirmWithoutPaymentLabel: 'Pay later',
592
630
  previousTotal: originalReceipt?.total,
593
- newTotal: latestChangeQuote?.serverDisplay?.total ?? latestChangeQuote?.quotedTotal,
631
+ newTotal:
632
+ authoritativeChangeQuoteForCheckout?.serverDisplay?.total ??
633
+ authoritativeChangeQuoteForCheckout?.quotedTotal,
594
634
  finalizeExistingChangeInline: true,
595
635
  preferConfirmWithoutPayment: true,
596
636
  }));
@@ -940,6 +980,7 @@ export function useAdminChangeCheckoutController({
940
980
  showAdminPaymentChoice,
941
981
  adminChoiceData,
942
982
  cancelPendingReservation,
983
+ handleCheckoutClose,
943
984
  handleCheckout,
944
985
  handleConfirmWithoutPayment,
945
986
  handlePayNow,
@@ -37,6 +37,7 @@ import { buildPublicChangeAmountDueSummary } from '../src/components/booking/cha
37
37
  import {
38
38
  buildAdminChangePaymentChoiceData,
39
39
  buildAdminChangePayNowCheckoutModalData,
40
+ shouldRestoreAdminChangePaymentChoiceAfterCheckoutClose,
40
41
  } from '../src/components/booking/admin-change-payment-choice-runner';
41
42
 
42
43
  function quote(overrides: Partial<ChangeBookingQuoteResponse>): ChangeBookingQuoteResponse {
@@ -117,6 +118,16 @@ test('admin existing-booking pay-now review keeps previous, updated, and exact d
117
118
  assert.equal(checkout.finalizeExistingChangeInline, true);
118
119
  assert.equal(choice.confirmWithoutPaymentLabel, 'Pay later');
119
120
  assert.equal(choice.preferConfirmWithoutPayment, true);
121
+ assert.equal(
122
+ shouldRestoreAdminChangePaymentChoiceAfterCheckoutClose(true, true, choice),
123
+ true,
124
+ );
125
+ assert.equal(
126
+ shouldRestoreAdminChangePaymentChoiceAfterCheckoutClose(true, true, {
127
+ pricingQuoteId: ' ',
128
+ }),
129
+ false,
130
+ );
120
131
  assert.equal(
121
132
  buildAdminChangePayNowCheckoutModalData(
122
133
  { ...choice, finalizeExistingChangeInline: undefined },