@ticketboothapp/booking 1.2.123 → 1.2.125

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 (112) hide show
  1. package/CHANGE_BOOKING_BE_HANDOFF.md +15 -4
  2. package/package.json +1 -1
  3. package/src/components/booking/AdminChangeBookingContent.tsx +276 -0
  4. package/src/components/booking/AdminChangeBookingFlow.tsx +651 -6202
  5. package/src/components/booking/AdminChangeCheckoutDialogs.tsx +105 -0
  6. package/src/components/booking/AdminChangeCheckoutPanel.tsx +307 -0
  7. package/src/components/booking/AdminChangePricingPanel.tsx +244 -0
  8. package/src/components/booking/AdminChangeProductSwitcher.tsx +50 -0
  9. package/src/components/booking/AdminChangeReceiptComparison.tsx +167 -0
  10. package/src/components/booking/ChangeBookingCalendarPanel.tsx +126 -0
  11. package/src/components/booking/ChangeBookingCheckoutPanel.tsx +204 -0
  12. package/src/components/booking/ChangeBookingFlow.tsx +576 -4648
  13. package/src/components/booking/ChangeBookingItineraryPanel.tsx +115 -0
  14. package/src/components/booking/ChangeBookingQuoteStatusPlaceholder.tsx +57 -0
  15. package/src/components/booking/ChangeBookingSelectionControlsPanel.tsx +157 -0
  16. package/src/components/booking/ChangeBookingTicketsAndAddOnsPanel.tsx +102 -0
  17. package/src/components/booking/CheckoutModal.tsx +68 -49
  18. package/src/components/booking/NewBookingFlow.tsx +485 -3505
  19. package/src/components/booking/PriceBreakdown.tsx +17 -3
  20. package/src/components/booking/PriceSummary.tsx +21 -3
  21. package/src/components/booking/PrivateShuttleAddOnsSection.tsx +102 -0
  22. package/src/components/booking/PrivateShuttleBookingFlow.tsx +407 -2788
  23. package/src/components/booking/PrivateShuttleCheckoutDialogs.tsx +106 -0
  24. package/src/components/booking/PrivateShuttleCheckoutSection.tsx +333 -0
  25. package/src/components/booking/PrivateShuttleItinerarySection.tsx +95 -0
  26. package/src/components/booking/PrivateShuttleMediaIntro.tsx +62 -0
  27. package/src/components/booking/PrivateShuttleOptionSection.tsx +81 -0
  28. package/src/components/booking/PrivateShuttlePassengerSection.tsx +148 -0
  29. package/src/components/booking/PrivateShuttlePickupSection.tsx +91 -0
  30. package/src/components/booking/PrivateShuttlePreferencesSection.tsx +75 -0
  31. package/src/components/booking/PrivateShuttleStartTimeSection.tsx +88 -0
  32. package/src/components/booking/StandardBookingCheckoutDialogs.tsx +106 -0
  33. package/src/components/booking/StandardBookingCheckoutSection.tsx +185 -0
  34. package/src/components/booking/StandardBookingItineraryPanel.tsx +125 -0
  35. package/src/components/booking/StandardBookingMediaIntro.tsx +69 -0
  36. package/src/components/booking/StandardBookingSelectionControlsPanel.tsx +236 -0
  37. package/src/components/booking/admin-change-customer-quote-runner.ts +119 -0
  38. package/src/components/booking/admin-change-flow-state-helpers.ts +124 -0
  39. package/src/components/booking/admin-change-payment-choice-runner.ts +172 -0
  40. package/src/components/booking/admin-change-provider-payload.ts +101 -0
  41. package/src/components/booking/change-booking-checkout-builders.ts +185 -0
  42. package/src/components/booking/change-booking-checkout-quote-runner.ts +207 -0
  43. package/src/components/booking/change-booking-flow-helpers.ts +706 -0
  44. package/src/components/booking/change-booking-payment-modal-builders.ts +151 -0
  45. package/src/components/booking/change-booking-quote-guards.ts +223 -0
  46. package/src/components/booking/change-booking-quote-state.ts +64 -0
  47. package/src/components/booking/pricing-v2-checkout-summary.ts +144 -0
  48. package/src/components/booking/private-shuttle-availability.ts +111 -0
  49. package/src/components/booking/private-shuttle-checkout-builders.ts +264 -0
  50. package/src/components/booking/private-shuttle-checkout-controller.ts +744 -0
  51. package/src/components/booking/private-shuttle-payment-choice-runner.ts +271 -0
  52. package/src/components/booking/private-shuttle-provider-change-runner.ts +73 -0
  53. package/src/components/booking/private-shuttle-reservation-runner.ts +260 -0
  54. package/src/components/booking/standard-booking-availability.ts +305 -0
  55. package/src/components/booking/standard-booking-checkout-builders.ts +289 -0
  56. package/src/components/booking/standard-booking-checkout-controller.ts +659 -0
  57. package/src/components/booking/standard-booking-payment-choice-runner.ts +274 -0
  58. package/src/components/booking/standard-booking-reservation-runner.ts +229 -0
  59. package/src/components/booking/use-private-shuttle-availability.ts +602 -0
  60. package/src/components/booking/use-standard-booking-availability.ts +806 -0
  61. package/src/components/booking/useActiveProductOptions.ts +26 -0
  62. package/src/components/booking/useAdminChangeCheckoutController.ts +806 -0
  63. package/src/components/booking/useAdminChangeOrderSummaryModel.ts +246 -0
  64. package/src/components/booking/useAdminChangePriceSummary.ts +416 -0
  65. package/src/components/booking/useAdminChangePricingDebugPanel.tsx +178 -0
  66. package/src/components/booking/useAdminChangeProductReset.ts +60 -0
  67. package/src/components/booking/useAdminChangeProducts.ts +102 -0
  68. package/src/components/booking/useAdminChangePromoDiscount.ts +235 -0
  69. package/src/components/booking/useAdminChangeProtectedPricing.ts +280 -0
  70. package/src/components/booking/useAdminChangeQuoteDisplayState.tsx +437 -0
  71. package/src/components/booking/useAdminChangeQuotePreview.ts +506 -0
  72. package/src/components/booking/useAdminChangeReceiptDerivation.ts +203 -0
  73. package/src/components/booking/useAdminCustomReceiptLines.ts +57 -0
  74. package/src/components/booking/useAdminProviderPricingAdjustments.ts +146 -0
  75. package/src/components/booking/useBookingAvailabilityAddOns.ts +54 -0
  76. package/src/components/booking/useBookingPromoAndQuantityController.ts +163 -0
  77. package/src/components/booking/useBookingQuantityCapTrim.ts +56 -0
  78. package/src/components/booking/useBookingViewItemAnalytics.ts +30 -0
  79. package/src/components/booking/useChangeBookingAddOnFloorController.ts +109 -0
  80. package/src/components/booking/useChangeBookingAutoSelections.ts +148 -0
  81. package/src/components/booking/useChangeBookingAvailabilities.ts +692 -0
  82. package/src/components/booking/useChangeBookingCheckoutController.ts +461 -0
  83. package/src/components/booking/useChangeBookingInitialHydration.ts +204 -0
  84. package/src/components/booking/useChangeBookingItineraryUiState.ts +133 -0
  85. package/src/components/booking/useChangeBookingPickupItinerary.ts +472 -0
  86. package/src/components/booking/useChangeBookingPriceSummary.ts +199 -0
  87. package/src/components/booking/useChangeBookingPromoDiscount.ts +218 -0
  88. package/src/components/booking/useChangeBookingProtectedPricing.ts +256 -0
  89. package/src/components/booking/useChangeBookingQuotePreview.ts +442 -0
  90. package/src/components/booking/useChangeBookingReceiptDerivation.ts +128 -0
  91. package/src/components/booking/useChangeBookingReceiptFloorController.ts +449 -0
  92. package/src/components/booking/useChangeBookingSelection.ts +362 -0
  93. package/src/components/booking/useChangeBookingSelectionDetails.ts +200 -0
  94. package/src/components/booking/useChangeBookingSummaryLines.ts +169 -0
  95. package/src/components/booking/usePrivateShuttlePriceSummary.ts +513 -0
  96. package/src/components/booking/usePrivateShuttlePromoController.ts +143 -0
  97. package/src/components/booking/usePrivateShuttlePromoDiscount.ts +124 -0
  98. package/src/components/booking/useSeedPromoCodeInput.ts +13 -0
  99. package/src/components/booking/useStandardBookingAutoSelections.ts +224 -0
  100. package/src/components/booking/useStandardBookingPickupItinerary.ts +473 -0
  101. package/src/components/booking/useStandardBookingPriceSummary.ts +438 -0
  102. package/src/components/booking/useStandardBookingPromoController.ts +258 -0
  103. package/src/components/booking/useStandardBookingPromoDiscount.ts +203 -0
  104. package/src/data/products-config.json +1 -1
  105. package/src/lib/booking/change-booking-server-preview.ts +134 -20
  106. package/src/lib/booking/change-flow-pricing.ts +31 -11
  107. package/src/lib/booking/checkout-breakdown.ts +66 -1
  108. package/src/lib/booking/pricing-config.ts +10 -0
  109. package/src/lib/booking/source-metadata.ts +58 -0
  110. package/src/lib/booking-api.ts +155 -3
  111. package/src/lib/pricing-v2-shadow.ts +272 -0
  112. package/test/change-booking-helpers.test.ts +206 -0
@@ -0,0 +1,274 @@
1
+ import {
2
+ confirmBookingWithoutPayment,
3
+ type CheckoutBreakdown,
4
+ type ItineraryDisplayStep,
5
+ } from '../../lib/booking-api';
6
+ import type { OrderSummary } from '../../lib/booking/pricing';
7
+ import {
8
+ buildBookingSourceContext,
9
+ inferClientBookingSourceFromProductIds,
10
+ type BookingSourceMetadata,
11
+ } from '../../lib/booking/source-metadata';
12
+ import { optionalCheckoutContactFields } from '../../lib/booking/checkout-contact';
13
+ import { formatBookingRefForDisplay } from '../../lib/booking-ref';
14
+ import type { CheckoutModalLineItem, CheckoutModalSummaryOverride } from './CheckoutModal';
15
+ import type { Currency } from './CurrencySwitcher';
16
+
17
+ export interface StandardCheckoutModalData {
18
+ reservationReference: string;
19
+ reservationExpiration?: string;
20
+ customerLastName?: string;
21
+ bookingDate?: string;
22
+ successUrlOverride?: string;
23
+ ticketLines: CheckoutModalLineItem[];
24
+ feeLineItems: OrderSummary['feeLineItems'];
25
+ returnPriceAdjustment: number;
26
+ cancellationPolicyFee: number;
27
+ cancellationPolicyLabel?: string;
28
+ subtotal: number;
29
+ tax: number;
30
+ total: number;
31
+ promoDiscountAmount?: number;
32
+ discountLabel?: string | null;
33
+ summaryOverride?: CheckoutModalSummaryOverride;
34
+ totalQuantity: number;
35
+ isTaxIncludedInPrice: boolean;
36
+ taxRate: number;
37
+ changeTotals?: {
38
+ previousTotal: number;
39
+ newTotal: number;
40
+ differenceTotal: number;
41
+ };
42
+ }
43
+
44
+ export interface StandardAdminChoiceData {
45
+ reservationReference: string;
46
+ reservationExpiration?: string;
47
+ checkoutBreakdown: CheckoutBreakdown;
48
+ totalAmount: number;
49
+ datePart: string;
50
+ timePart: string;
51
+ availabilityProductOptionId: string;
52
+ itineraryDisplay?: ItineraryDisplayStep[] | null;
53
+ clientSecret: string;
54
+ ticketLinesForModal: CheckoutModalLineItem[];
55
+ feeLineItems: OrderSummary['feeLineItems'];
56
+ returnPriceAdjustment: number;
57
+ cancellationPolicyFee: number;
58
+ cancellationPolicyLabel?: string;
59
+ subtotal: number;
60
+ tax: number;
61
+ totalQuantity: number;
62
+ isTaxIncludedInPrice: boolean;
63
+ taxRate: number;
64
+ promoDiscountAmount: number;
65
+ discountLabel?: string | null;
66
+ summaryOverride?: CheckoutModalSummaryOverride;
67
+ pricingQuoteId?: string;
68
+ pricingQuoteInputsHash?: string;
69
+ }
70
+
71
+ export interface BuildStandardAdminChoiceDataParams {
72
+ reservation: { reservationReference: string; expiresAt?: string };
73
+ checkoutBreakdown: CheckoutBreakdown;
74
+ totalAmount: number;
75
+ datePart: string;
76
+ timePart: string;
77
+ availabilityProductOptionId: string;
78
+ itineraryDisplay?: ItineraryDisplayStep[] | null;
79
+ clientSecret: string;
80
+ ticketLinesForModal: CheckoutModalLineItem[];
81
+ feeLineItems: OrderSummary['feeLineItems'];
82
+ returnPriceAdjustment: number;
83
+ cancellationPolicyFee: number;
84
+ cancellationPolicyLabel?: string;
85
+ subtotal: number;
86
+ tax: number;
87
+ totalQuantity: number;
88
+ isTaxIncludedInPrice: boolean;
89
+ taxRate: number;
90
+ promoDiscountAmount: number;
91
+ discountLabel?: string | null;
92
+ summaryOverride?: CheckoutModalSummaryOverride;
93
+ pricingQuoteId?: string;
94
+ pricingQuoteInputsHash?: string;
95
+ }
96
+
97
+ export function buildStandardAdminChoiceData({
98
+ reservation,
99
+ ...rest
100
+ }: BuildStandardAdminChoiceDataParams): StandardAdminChoiceData {
101
+ return {
102
+ reservationReference: reservation.reservationReference,
103
+ reservationExpiration: reservation.expiresAt,
104
+ ...rest,
105
+ };
106
+ }
107
+
108
+ export interface BuildStandardPaidCheckoutModalDataParams {
109
+ reservation: { reservationReference: string; expiresAt?: string };
110
+ customerLastName: string;
111
+ bookingDate: string;
112
+ ticketLines: CheckoutModalLineItem[];
113
+ feeLineItems: OrderSummary['feeLineItems'];
114
+ returnPriceAdjustment: number;
115
+ cancellationPolicyFee: number;
116
+ cancellationPolicyLabel?: string;
117
+ subtotal: number;
118
+ tax: number;
119
+ total: number;
120
+ totalQuantity: number;
121
+ isTaxIncludedInPrice: boolean;
122
+ taxRate: number;
123
+ promoDiscountAmount: number;
124
+ discountLabel?: string | null;
125
+ summaryOverride?: CheckoutModalSummaryOverride;
126
+ }
127
+
128
+ export function buildStandardPaidCheckoutModalData({
129
+ reservation,
130
+ customerLastName,
131
+ bookingDate,
132
+ ticketLines,
133
+ feeLineItems,
134
+ returnPriceAdjustment,
135
+ cancellationPolicyFee,
136
+ cancellationPolicyLabel,
137
+ subtotal,
138
+ tax,
139
+ total,
140
+ totalQuantity,
141
+ isTaxIncludedInPrice,
142
+ taxRate,
143
+ promoDiscountAmount,
144
+ discountLabel,
145
+ summaryOverride,
146
+ }: BuildStandardPaidCheckoutModalDataParams): StandardCheckoutModalData {
147
+ return {
148
+ reservationReference: reservation.reservationReference,
149
+ reservationExpiration: reservation.expiresAt,
150
+ customerLastName,
151
+ bookingDate,
152
+ successUrlOverride: undefined,
153
+ ticketLines,
154
+ feeLineItems,
155
+ returnPriceAdjustment,
156
+ cancellationPolicyFee,
157
+ cancellationPolicyLabel,
158
+ subtotal,
159
+ tax,
160
+ total,
161
+ totalQuantity,
162
+ isTaxIncludedInPrice,
163
+ taxRate,
164
+ promoDiscountAmount,
165
+ discountLabel,
166
+ summaryOverride,
167
+ changeTotals: undefined,
168
+ };
169
+ }
170
+
171
+ export function buildStandardPayNowCheckoutModalData(
172
+ adminChoiceData: StandardAdminChoiceData,
173
+ customerLastName: string
174
+ ): StandardCheckoutModalData {
175
+ return {
176
+ reservationReference: adminChoiceData.reservationReference,
177
+ reservationExpiration: adminChoiceData.reservationExpiration,
178
+ customerLastName,
179
+ ticketLines: adminChoiceData.ticketLinesForModal,
180
+ feeLineItems: adminChoiceData.feeLineItems,
181
+ returnPriceAdjustment: adminChoiceData.returnPriceAdjustment,
182
+ cancellationPolicyFee: adminChoiceData.cancellationPolicyFee,
183
+ cancellationPolicyLabel: adminChoiceData.cancellationPolicyLabel,
184
+ subtotal: adminChoiceData.subtotal,
185
+ tax: adminChoiceData.tax,
186
+ total: adminChoiceData.totalAmount,
187
+ totalQuantity: adminChoiceData.totalQuantity,
188
+ isTaxIncludedInPrice: adminChoiceData.isTaxIncludedInPrice,
189
+ taxRate: adminChoiceData.taxRate,
190
+ promoDiscountAmount: adminChoiceData.promoDiscountAmount,
191
+ discountLabel: adminChoiceData.discountLabel,
192
+ summaryOverride: adminChoiceData.summaryOverride,
193
+ };
194
+ }
195
+
196
+ export interface ConfirmStandardAdminBookingWithoutPaymentParams {
197
+ adminChoiceData: StandardAdminChoiceData;
198
+ bookingSourceAttribution: Partial<BookingSourceMetadata>;
199
+ partnerPortalBooking: boolean;
200
+ forceDashboardSource: boolean;
201
+ productId: string;
202
+ email: string;
203
+ firstName: string;
204
+ lastName: string;
205
+ phoneNumber: string;
206
+ currency: Currency;
207
+ travelerHotel?: string;
208
+ pickupLocationId?: string;
209
+ termsAcceptedAt?: string | null;
210
+ skipConfirmationCommunications: boolean;
211
+ disableAutoCommunications: boolean;
212
+ }
213
+
214
+ export async function confirmStandardAdminBookingWithoutPayment({
215
+ adminChoiceData,
216
+ bookingSourceAttribution,
217
+ partnerPortalBooking,
218
+ forceDashboardSource,
219
+ productId,
220
+ email,
221
+ firstName,
222
+ lastName,
223
+ phoneNumber,
224
+ currency,
225
+ travelerHotel,
226
+ pickupLocationId,
227
+ termsAcceptedAt,
228
+ skipConfirmationCommunications,
229
+ disableAutoCommunications,
230
+ }: ConfirmStandardAdminBookingWithoutPaymentParams): Promise<{
231
+ bookingReference: string;
232
+ displayReference: string;
233
+ lastName: string;
234
+ }> {
235
+ const bookingSourceContext = buildBookingSourceContext(bookingSourceAttribution, {
236
+ clientChannelSource: inferClientBookingSourceFromProductIds(
237
+ productId,
238
+ adminChoiceData.availabilityProductOptionId,
239
+ ),
240
+ forcePartnerPortalChannel: partnerPortalBooking,
241
+ forceDashboardSource,
242
+ });
243
+ const result = await confirmBookingWithoutPayment({
244
+ reservationReference: adminChoiceData.reservationReference,
245
+ productId,
246
+ optionId: adminChoiceData.availabilityProductOptionId,
247
+ date: adminChoiceData.datePart,
248
+ time: adminChoiceData.timePart,
249
+ customerEmail: email || undefined,
250
+ customerFirstName: firstName.trim() || undefined,
251
+ customerLastName: lastName.trim() || undefined,
252
+ ...optionalCheckoutContactFields(phoneNumber),
253
+ currency,
254
+ travelerHotel,
255
+ pickupLocationId,
256
+ itineraryDisplay: adminChoiceData.itineraryDisplay ?? undefined,
257
+ termsAcceptedAt: termsAcceptedAt ?? undefined,
258
+ skipConfirmationCommunications: skipConfirmationCommunications ? true : undefined,
259
+ disableAutoCommunications: disableAutoCommunications ? true : undefined,
260
+ checkoutBreakdown: adminChoiceData.checkoutBreakdown,
261
+ depositAmount: 0,
262
+ balanceAmount: adminChoiceData.totalAmount,
263
+ totalAmount: adminChoiceData.totalAmount,
264
+ pricingQuoteId: adminChoiceData.pricingQuoteId,
265
+ pricingQuoteInputsHash: adminChoiceData.pricingQuoteInputsHash,
266
+ ...bookingSourceContext,
267
+ });
268
+
269
+ return {
270
+ bookingReference: result.bookingReference,
271
+ displayReference: formatBookingRefForDisplay(result.bookingReference),
272
+ lastName: lastName.trim(),
273
+ };
274
+ }
@@ -0,0 +1,229 @@
1
+ import {
2
+ createReservation,
3
+ describeStandardTourCapacityConflictMessage,
4
+ type Availability,
5
+ type ItineraryDisplayStep,
6
+ type PricingConfig,
7
+ type Product,
8
+ type PublicNewBookingQuoteV2Data,
9
+ type ReserveResponse,
10
+ type ReturnOption,
11
+ } from '../../lib/booking-api';
12
+ import { shouldReplacePricingConfig } from '../../lib/booking/pricing-config';
13
+ import {
14
+ buildBookingSourceContext,
15
+ inferClientBookingSourceFromProductIds,
16
+ type BookingSourceMetadata,
17
+ } from '../../lib/booking/source-metadata';
18
+ import { quotePublicNewBookingV2ForCheckout } from '../../lib/pricing-v2-shadow';
19
+ import type { Currency } from './CurrencySwitcher';
20
+ import {
21
+ findMergedAvailabilityForSelection,
22
+ findMergedReturnVacancies,
23
+ getAvailabilityOptionId,
24
+ } from './standard-booking-availability';
25
+
26
+ type StandardBookingItem = { category: string; count: number };
27
+ export type StandardAddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
28
+ type StandardBookingSourceContext = ReturnType<typeof buildBookingSourceContext>;
29
+
30
+ export type StandardCheckoutReserveResult =
31
+ | { kind: 'blocked'; message: string }
32
+ | {
33
+ kind: 'ready';
34
+ bookingItems: StandardBookingItem[];
35
+ availabilityProductOptionId: string;
36
+ bookingSourceContext: StandardBookingSourceContext;
37
+ selectedPickupLocation: NonNullable<Product['pickupLocations']>[number] | null;
38
+ pricingV2Quote: PublicNewBookingQuoteV2Data | null;
39
+ reservation: ReserveResponse;
40
+ datePart: string;
41
+ timePart: string;
42
+ itineraryDisplay: ItineraryDisplayStep[] | null;
43
+ };
44
+
45
+ export interface ReserveStandardBookingForCheckoutParams {
46
+ quantities: Record<string, number>;
47
+ selectedAvailability: Availability;
48
+ selectedReturnOption: ReturnOption | null;
49
+ product: Product;
50
+ pickupLocationId: string | null;
51
+ bookingSourceAttribution: Partial<BookingSourceMetadata>;
52
+ partnerPortalBooking: boolean;
53
+ forceDashboardSource: boolean;
54
+ isAdmin: boolean;
55
+ totalQuantity: number;
56
+ pricingConfig: PricingConfig | null;
57
+ refreshSelectedDateDetailsForCheckout: () => Promise<{
58
+ availabilities: Availability[];
59
+ pricingConfig?: PricingConfig | null;
60
+ }>;
61
+ displayTotalPrice: number;
62
+ totalPrice: number;
63
+ currency: Currency;
64
+ appliedPromoCode: string | null;
65
+ cancellationPolicyId: string | null;
66
+ addOnSelections: StandardAddOnSelection[];
67
+ itineraryDisplay: ItineraryDisplayStep[] | null;
68
+ }
69
+
70
+ function updatePendingStandardBookingSession({
71
+ reservation,
72
+ totalPrice,
73
+ currency,
74
+ selectedAvailability,
75
+ }: {
76
+ reservation: ReserveResponse;
77
+ totalPrice: number;
78
+ currency: Currency;
79
+ selectedAvailability: Availability;
80
+ }): void {
81
+ try {
82
+ const storedBooking = sessionStorage.getItem('pendingBooking');
83
+ if (!storedBooking) return;
84
+ const booking = JSON.parse(storedBooking);
85
+ booking.reservationReference = reservation.reservationReference;
86
+ booking.totalPrice = reservation.totalAmount || totalPrice;
87
+ booking.currency = reservation.currency || currency || selectedAvailability.currency || 'CAD';
88
+ sessionStorage.setItem('pendingBooking', JSON.stringify(booking));
89
+ } catch (e) {
90
+ console.warn('Failed to update booking data', e);
91
+ }
92
+ }
93
+
94
+ export async function reserveStandardBookingForCheckout({
95
+ quantities,
96
+ selectedAvailability,
97
+ selectedReturnOption,
98
+ product,
99
+ pickupLocationId,
100
+ bookingSourceAttribution,
101
+ partnerPortalBooking,
102
+ forceDashboardSource,
103
+ isAdmin,
104
+ totalQuantity,
105
+ pricingConfig,
106
+ refreshSelectedDateDetailsForCheckout,
107
+ displayTotalPrice,
108
+ totalPrice,
109
+ currency,
110
+ appliedPromoCode,
111
+ cancellationPolicyId,
112
+ addOnSelections,
113
+ itineraryDisplay,
114
+ }: ReserveStandardBookingForCheckoutParams): Promise<StandardCheckoutReserveResult> {
115
+ const bookingItems = Object.entries(quantities)
116
+ .filter(([, count]) => count > 0)
117
+ .map(([category, count]) => ({ category, count }));
118
+ const availabilityProductOptionId = getAvailabilityOptionId(selectedAvailability);
119
+
120
+ if (!availabilityProductOptionId) {
121
+ return { kind: 'blocked', message: 'No product option selected' };
122
+ }
123
+
124
+ const bookingSourceContext = buildBookingSourceContext(bookingSourceAttribution, {
125
+ clientChannelSource: inferClientBookingSourceFromProductIds(
126
+ product.productId,
127
+ availabilityProductOptionId,
128
+ ),
129
+ forcePartnerPortalChannel: partnerPortalBooking,
130
+ forceDashboardSource,
131
+ });
132
+ const selectedPickupLocation = pickupLocationId
133
+ ? product.pickupLocations?.find((loc) => loc.id === pickupLocationId) ?? null
134
+ : null;
135
+
136
+ try {
137
+ const refreshed = await refreshSelectedDateDetailsForCheckout();
138
+ const outbound = findMergedAvailabilityForSelection(refreshed.availabilities, selectedAvailability);
139
+ const outboundVacancies = outbound?.vacancies ?? null;
140
+ const returnVacancies = findMergedReturnVacancies(outbound, selectedReturnOption);
141
+ if (
142
+ !isAdmin &&
143
+ ((outboundVacancies != null && outboundVacancies < totalQuantity) ||
144
+ (selectedReturnOption && returnVacancies != null && returnVacancies < totalQuantity))
145
+ ) {
146
+ return {
147
+ kind: 'blocked',
148
+ message: describeStandardTourCapacityConflictMessage({
149
+ partySize: totalQuantity,
150
+ outboundVacancies,
151
+ returnVacancies,
152
+ hasReturnSelection: !!selectedReturnOption,
153
+ }),
154
+ };
155
+ }
156
+ if (shouldReplacePricingConfig(pricingConfig, refreshed.pricingConfig)) {
157
+ return {
158
+ kind: 'blocked',
159
+ message: 'Pricing was refreshed. Please review the updated total and continue to payment again.',
160
+ };
161
+ }
162
+ } catch (refreshErr) {
163
+ console.warn('Fresh availability check failed before reserve; falling back to reserve validation.', refreshErr);
164
+ }
165
+
166
+ const pricingV2Quote = await quotePublicNewBookingV2ForCheckout({
167
+ flow: 'standard',
168
+ clientProposedTotal: displayTotalPrice,
169
+ request: {
170
+ productId: product.productId,
171
+ optionId: availabilityProductOptionId,
172
+ availabilityId: selectedAvailability.availabilityId || '',
173
+ dateTime: selectedAvailability.dateTime,
174
+ bookingItems,
175
+ currency,
176
+ returnAvailabilityId: selectedReturnOption?.returnAvailabilityId ?? null,
177
+ promoCode: appliedPromoCode || null,
178
+ cancellationPolicyId: cancellationPolicyId || null,
179
+ addOnSelections: addOnSelections.length > 0 ? addOnSelections : null,
180
+ ...(isAdmin ? { allowOverbook: true } : {}),
181
+ },
182
+ });
183
+
184
+ const reservation = await createReservation({
185
+ productId: availabilityProductOptionId,
186
+ dateTime: selectedAvailability.dateTime,
187
+ availabilityId: selectedAvailability.availabilityId || undefined,
188
+ bookingItems,
189
+ pickupLocationId: pickupLocationId || undefined,
190
+ returnAvailabilityId: selectedReturnOption?.returnAvailabilityId,
191
+ currency,
192
+ promoCode: appliedPromoCode || undefined,
193
+ cancellationPolicyId: cancellationPolicyId || undefined,
194
+ addOnSelections: addOnSelections.length > 0 ? addOnSelections : undefined,
195
+ ...(isAdmin ? { allowOverbook: true } : {}),
196
+ travelerHotel: selectedPickupLocation?.name || undefined,
197
+ ...(bookingSourceContext.sourceMetadata
198
+ ? {
199
+ source: bookingSourceContext.source,
200
+ sourceMetadata: bookingSourceContext.sourceMetadata,
201
+ source_metadata: bookingSourceContext.source_metadata,
202
+ }
203
+ : {}),
204
+ });
205
+
206
+ if (!reservation.reservationReference) {
207
+ throw new Error('Invalid reservation response: missing reservationReference');
208
+ }
209
+
210
+ updatePendingStandardBookingSession({
211
+ reservation,
212
+ totalPrice,
213
+ currency,
214
+ selectedAvailability,
215
+ });
216
+
217
+ return {
218
+ kind: 'ready',
219
+ bookingItems,
220
+ availabilityProductOptionId,
221
+ bookingSourceContext,
222
+ selectedPickupLocation,
223
+ pricingV2Quote,
224
+ reservation,
225
+ datePart: selectedAvailability.dateTime.split('T')[0],
226
+ timePart: selectedAvailability.dateTime.split('T')[1]?.substring(0, 5) || '00:00',
227
+ itineraryDisplay,
228
+ };
229
+ }