@ticketboothapp/booking 1.2.126 → 1.2.128

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.126",
3
+ "version": "1.2.128",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -651,6 +651,7 @@ export function NewBookingFlow({
651
651
  appliedPromoCode,
652
652
  cancellationPolicyId,
653
653
  addOnSelections,
654
+ pricingProfileIdForAvailabilities,
654
655
  dependentAddOnSelection,
655
656
  ticketLineItems,
656
657
  pricing,
@@ -185,6 +185,7 @@ export function PrivateShuttleBookingFlow({
185
185
  activePromoCode,
186
186
  promoCodeError,
187
187
  promoCodeValidating,
188
+ forcedCancellationPolicy,
188
189
  handlePromoInputChange,
189
190
  handleApplyPromo,
190
191
  handleRemovePromo,
@@ -197,6 +198,7 @@ export function PrivateShuttleBookingFlow({
197
198
  resourceCount: Math.ceil(passengerCount / PRIVATE_SHUTTLE_RESOURCE_CAPACITY),
198
199
  productCompanyId: product.companyId,
199
200
  productId: product.productId,
201
+ setCancellationPolicyId,
200
202
  onPromoChangesAvailability: () => clearFetchedAvailabilityRangesRef.current(),
201
203
  t,
202
204
  });
@@ -667,6 +669,7 @@ export function PrivateShuttleBookingFlow({
667
669
  pricingConfig,
668
670
  refreshSelectedDateDetailsForCheckout,
669
671
  reloadAvailabilitiesAfterReserveConflict,
672
+ pricingProfileIdForAvailabilities,
670
673
  totalPrice,
671
674
  subtotal,
672
675
  activePromoCode,
@@ -943,6 +946,7 @@ export function PrivateShuttleBookingFlow({
943
946
  }}
944
947
  cancellationPolicies={pricingConfig?.cancellationPolicies}
945
948
  cancellationPolicyId={cancellationPolicyId}
949
+ forcedCancellationPolicy={forcedCancellationPolicy}
946
950
  onCancellationPolicySelect={setCancellationPolicyId}
947
951
  skipConfirmationCommunications={skipConfirmationCommunications}
948
952
  disableAutoCommunications={disableAutoCommunications}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { formatCurrencyAmount } from '../../lib/currency';
4
- import type { PricingConfig } from '../../lib/booking-api';
4
+ import type { PricingConfig, RefundTierOption } from '../../lib/booking-api';
5
5
  import type { Currency } from './CurrencySwitcher';
6
6
  import type { Locale } from '../../lib/booking/i18n/config';
7
7
  import { CancellationPolicySelector } from './CancellationPolicySelector';
@@ -46,6 +46,12 @@ interface PrivateShuttleCheckoutSectionProps {
46
46
  onPhoneNumberChange: (value: string) => void;
47
47
  cancellationPolicies?: PricingConfig['cancellationPolicies'];
48
48
  cancellationPolicyId: string | null;
49
+ forcedCancellationPolicy?: {
50
+ id: string;
51
+ label: string;
52
+ refundTiers?: RefundTierOption[];
53
+ changeWindowHoursBefore?: number | null;
54
+ } | null;
49
55
  onCancellationPolicySelect: (policyId: string) => void;
50
56
  skipConfirmationCommunications: boolean;
51
57
  disableAutoCommunications: boolean;
@@ -97,6 +103,7 @@ export function PrivateShuttleCheckoutSection({
97
103
  onPhoneNumberChange,
98
104
  cancellationPolicies,
99
105
  cancellationPolicyId,
106
+ forcedCancellationPolicy,
100
107
  onCancellationPolicySelect,
101
108
  skipConfirmationCommunications,
102
109
  disableAutoCommunications,
@@ -117,7 +124,7 @@ export function PrivateShuttleCheckoutSection({
117
124
  }: PrivateShuttleCheckoutSectionProps) {
118
125
  const amountDueToday = depositInfo ? depositInfo.depositAmount : totalPrice;
119
126
  const formattedAmountDueToday = formatCurrencyAmount(amountDueToday, currency, locale);
120
- const hasCancellationPolicies = Boolean(cancellationPolicies?.length);
127
+ const hasCancellationPolicies = Boolean(cancellationPolicies?.length || forcedCancellationPolicy);
121
128
 
122
129
  return (
123
130
  <>
@@ -241,6 +248,7 @@ export function PrivateShuttleCheckoutSection({
241
248
  locale={locale}
242
249
  t={t}
243
250
  onPolicySelect={onCancellationPolicySelect}
251
+ forcedPolicy={forcedCancellationPolicy}
244
252
  rowSubtitle={
245
253
  <>
246
254
  <b>Your deposit today is 100% refundable</b> - only once we align on an itinerary
@@ -79,6 +79,7 @@ export interface UsePrivateShuttleCheckoutControllerParams {
79
79
  pricingConfig: PricingConfig | null;
80
80
  refreshSelectedDateDetailsForCheckout: ReservePrivateShuttleForCheckoutParams['refreshSelectedDateDetailsForCheckout'];
81
81
  reloadAvailabilitiesAfterReserveConflict: () => Promise<Availability[]>;
82
+ pricingProfileIdForAvailabilities: string | null;
82
83
  totalPrice: number;
83
84
  subtotal: number;
84
85
  activePromoCode: string | null;
@@ -151,6 +152,7 @@ export function usePrivateShuttleCheckoutController({
151
152
  pricingConfig,
152
153
  refreshSelectedDateDetailsForCheckout,
153
154
  reloadAvailabilitiesAfterReserveConflict,
155
+ pricingProfileIdForAvailabilities,
154
156
  totalPrice,
155
157
  subtotal,
156
158
  activePromoCode,
@@ -358,6 +360,7 @@ export function usePrivateShuttleCheckoutController({
358
360
  childSafetySeatsCount,
359
361
  foodRestrictions,
360
362
  itineraryDisplayItems,
363
+ pricingProfileIdForAvailabilities,
361
364
  });
362
365
  if (reserveResult.kind === 'blocked') {
363
366
  setError(reserveResult.message);
@@ -78,6 +78,7 @@ export interface ReservePrivateShuttleForCheckoutParams {
78
78
  childSafetySeatsCount: number;
79
79
  foodRestrictions: string;
80
80
  itineraryDisplayItems: PrivateShuttleCheckoutItineraryItem[];
81
+ pricingProfileIdForAvailabilities: string | null;
81
82
  }
82
83
 
83
84
  function buildPrivateShuttleItineraryDisplayForStorage(
@@ -132,6 +133,7 @@ export async function reservePrivateShuttleForCheckout({
132
133
  childSafetySeatsCount,
133
134
  foodRestrictions,
134
135
  itineraryDisplayItems,
136
+ pricingProfileIdForAvailabilities,
135
137
  }: ReservePrivateShuttleForCheckoutParams): Promise<PrivateShuttleCheckoutReserveResult> {
136
138
  if (!selectedOption) {
137
139
  return { kind: 'blocked', message: 'No product option selected' };
@@ -206,6 +208,7 @@ export async function reservePrivateShuttleForCheckout({
206
208
  cancellationPolicyId: cancellationPolicyId || null,
207
209
  addOnSelections: addOnSelections.length > 0 ? addOnSelections : null,
208
210
  additionalHoursCount: isAdmin && additionalHoursCount > 0 ? additionalHoursCount : null,
211
+ pricingProfileId: pricingProfileIdForAvailabilities || null,
209
212
  ...(isAdmin ? { allowOverbook: true } : {}),
210
213
  },
211
214
  });
@@ -234,6 +237,7 @@ export async function reservePrivateShuttleForCheckout({
234
237
  foodRestrictions: foodRestrictions.trim() || undefined,
235
238
  addOnSelections: addOnSelections.length > 0 ? addOnSelections : undefined,
236
239
  additionalHoursCount: isAdmin && additionalHoursCount > 0 ? additionalHoursCount : undefined,
240
+ pricingProfileId: pricingProfileIdForAvailabilities || undefined,
237
241
  ...(isAdmin ? { allowOverbook: true } : {}),
238
242
  ...(bookingSourceContext.sourceMetadata
239
243
  ? {
@@ -77,6 +77,7 @@ export interface UseStandardBookingCheckoutControllerParams {
77
77
  cancellationPolicyId: string | null;
78
78
  addOnSelections: StandardAddOnSelection[];
79
79
  dependentAddOnSelection?: NewBookingFlowProps['dependentAddOnSelection'];
80
+ pricingProfileIdForAvailabilities: string | null;
80
81
  ticketLineItems: OrderSummaryTicketLine[];
81
82
  pricing: BuildStandardBookingCheckoutArtifactsParams['pricing'];
82
83
  getPriceBreakdown: BuildStandardBookingCheckoutArtifactsParams['getPriceBreakdown'];
@@ -143,6 +144,7 @@ export function useStandardBookingCheckoutController({
143
144
  cancellationPolicyId,
144
145
  addOnSelections,
145
146
  dependentAddOnSelection,
147
+ pricingProfileIdForAvailabilities,
146
148
  ticketLineItems,
147
149
  pricing,
148
150
  getPriceBreakdown,
@@ -289,6 +291,8 @@ export function useStandardBookingCheckoutController({
289
291
  cancellationPolicyId,
290
292
  addOnSelections,
291
293
  itineraryDisplay: computeItineraryDisplayForStorage() ?? computeItineraryDisplay(),
294
+ dependentAddOnSelection,
295
+ pricingProfileIdForAvailabilities,
292
296
  });
293
297
  if (reserveResult.kind === 'blocked') {
294
298
  setError(reserveResult.message);
@@ -2,6 +2,7 @@ import {
2
2
  createReservation,
3
3
  describeStandardTourCapacityConflictMessage,
4
4
  type Availability,
5
+ type CheckoutDependentAddOnSelection,
5
6
  type ItineraryDisplayStep,
6
7
  type PricingConfig,
7
8
  type Product,
@@ -65,6 +66,8 @@ export interface ReserveStandardBookingForCheckoutParams {
65
66
  cancellationPolicyId: string | null;
66
67
  addOnSelections: StandardAddOnSelection[];
67
68
  itineraryDisplay: ItineraryDisplayStep[] | null;
69
+ dependentAddOnSelection?: CheckoutDependentAddOnSelection | null;
70
+ pricingProfileIdForAvailabilities: string | null;
68
71
  }
69
72
 
70
73
  function updatePendingStandardBookingSession({
@@ -111,6 +114,8 @@ export async function reserveStandardBookingForCheckout({
111
114
  cancellationPolicyId,
112
115
  addOnSelections,
113
116
  itineraryDisplay,
117
+ dependentAddOnSelection,
118
+ pricingProfileIdForAvailabilities,
114
119
  }: ReserveStandardBookingForCheckoutParams): Promise<StandardCheckoutReserveResult> {
115
120
  const bookingItems = Object.entries(quantities)
116
121
  .filter(([, count]) => count > 0)
@@ -177,6 +182,8 @@ export async function reserveStandardBookingForCheckout({
177
182
  promoCode: appliedPromoCode || null,
178
183
  cancellationPolicyId: cancellationPolicyId || null,
179
184
  addOnSelections: addOnSelections.length > 0 ? addOnSelections : null,
185
+ dependentAddOnSelection: dependentAddOnSelection || null,
186
+ pricingProfileId: pricingProfileIdForAvailabilities || null,
180
187
  ...(isAdmin ? { allowOverbook: true } : {}),
181
188
  },
182
189
  });
@@ -192,6 +199,7 @@ export async function reserveStandardBookingForCheckout({
192
199
  promoCode: appliedPromoCode || undefined,
193
200
  cancellationPolicyId: cancellationPolicyId || undefined,
194
201
  addOnSelections: addOnSelections.length > 0 ? addOnSelections : undefined,
202
+ pricingProfileId: pricingProfileIdForAvailabilities || undefined,
195
203
  ...(isAdmin ? { allowOverbook: true } : {}),
196
204
  travelerHotel: selectedPickupLocation?.name || undefined,
197
205
  ...(bookingSourceContext.sourceMetadata
@@ -2,10 +2,18 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import {
3
3
  validatePromoCode,
4
4
  type Availability,
5
+ type RefundTierOption,
5
6
  } from '../../lib/booking-api';
6
7
 
7
8
  type TranslationFn = (key: string) => string;
8
9
 
10
+ export type PrivateShuttleForcedCancellationPolicy = {
11
+ id: string;
12
+ label: string;
13
+ refundTiers?: RefundTierOption[];
14
+ changeWindowHoursBefore?: number | null;
15
+ } | null;
16
+
9
17
  export interface UsePrivateShuttlePromoControllerParams {
10
18
  initialPromoCode?: string | null;
11
19
  isAdmin: boolean;
@@ -15,6 +23,7 @@ export interface UsePrivateShuttlePromoControllerParams {
15
23
  resourceCount: number;
16
24
  productCompanyId?: string | null;
17
25
  productId: string;
26
+ setCancellationPolicyId: (policyId: string | null) => void;
18
27
  onPromoChangesAvailability: () => void;
19
28
  t: TranslationFn;
20
29
  }
@@ -28,6 +37,7 @@ export function usePrivateShuttlePromoController({
28
37
  resourceCount,
29
38
  productCompanyId,
30
39
  productId,
40
+ setCancellationPolicyId,
31
41
  onPromoChangesAvailability,
32
42
  t,
33
43
  }: UsePrivateShuttlePromoControllerParams) {
@@ -37,6 +47,8 @@ export function usePrivateShuttlePromoController({
37
47
  );
38
48
  const [promoCodeError, setPromoCodeError] = useState('');
39
49
  const [promoCodeValidating, setPromoCodeValidating] = useState(false);
50
+ const [forcedCancellationPolicy, setForcedCancellationPolicy] =
51
+ useState<PrivateShuttleForcedCancellationPolicy>(null);
40
52
  const lastValidatedInputRef = useRef<string | null>(null);
41
53
 
42
54
  const activePromoCode = isAdmin ? appliedPromoCode : null;
@@ -64,9 +76,21 @@ export function usePrivateShuttlePromoController({
64
76
  productCompanyId,
65
77
  productId,
66
78
  hasOngoingDiscount,
79
+ selectedAvailability?.dateTime,
67
80
  );
68
81
  if (result.valid) {
69
82
  setAppliedPromoCode(code);
83
+ if (result.forcedCancellationPolicyId && result.forcedCancellationPolicyLabel) {
84
+ setForcedCancellationPolicy({
85
+ id: result.forcedCancellationPolicyId,
86
+ label: result.forcedCancellationPolicyLabel,
87
+ refundTiers: result.forcedCancellationPolicyRefundTiers,
88
+ changeWindowHoursBefore: result.forcedChangeWindowHoursBefore ?? undefined,
89
+ });
90
+ setCancellationPolicyId(result.forcedCancellationPolicyId);
91
+ } else {
92
+ setForcedCancellationPolicy(null);
93
+ }
70
94
  onPromoChangesAvailability();
71
95
  } else {
72
96
  setPromoCodeError(
@@ -87,6 +111,8 @@ export function usePrivateShuttlePromoController({
87
111
  productCompanyId,
88
112
  productId,
89
113
  hasOngoingDiscount,
114
+ selectedAvailability?.dateTime,
115
+ setCancellationPolicyId,
90
116
  t,
91
117
  onPromoChangesAvailability,
92
118
  ]);
@@ -102,10 +128,12 @@ export function usePrivateShuttlePromoController({
102
128
  const handleRemovePromo = useCallback(() => {
103
129
  lastValidatedInputRef.current = null;
104
130
  setAppliedPromoCode(null);
131
+ setForcedCancellationPolicy(null);
132
+ setCancellationPolicyId(null);
105
133
  setPromoCodeInput('');
106
134
  setPromoCodeError('');
107
135
  onPromoChangesAvailability();
108
- }, [onPromoChangesAvailability]);
136
+ }, [setCancellationPolicyId, onPromoChangesAvailability]);
109
137
 
110
138
  useEffect(() => {
111
139
  if (!isAdmin) return;
@@ -136,6 +164,7 @@ export function usePrivateShuttlePromoController({
136
164
  activePromoCode,
137
165
  promoCodeError,
138
166
  promoCodeValidating,
167
+ forcedCancellationPolicy,
139
168
  handlePromoInputChange,
140
169
  handleApplyPromo,
141
170
  handleRemovePromo,
@@ -26,6 +26,7 @@ export const CheckoutLineType = {
26
26
  PROMO_CODE: 'PROMO_CODE',
27
27
  ROUNDING: 'ROUNDING',
28
28
  ADDITIONAL_HOURS: 'ADDITIONAL_HOURS', // Future: private shuttle extra hours line
29
+ DEPENDENT_ADD_ON: 'DEPENDENT_ADD_ON',
29
30
  } as const;
30
31
 
31
32
  export interface BuildCheckoutBreakdownParams {
@@ -82,6 +83,7 @@ function pricingV2LineTypeToCheckoutType(type?: string | null): string {
82
83
  case 'CANCELLATION_UPGRADE':
83
84
  case 'ROUNDING':
84
85
  case 'BOOKING_CHANGE':
86
+ case 'DEPENDENT_ADD_ON':
85
87
  return normalized;
86
88
  default:
87
89
  return 'FEE';
@@ -1853,6 +1853,8 @@ export interface ReserveRequest {
1853
1853
  foodRestrictions?: string;
1854
1854
  /** Admin only: additional hours add-on (extends duration) */
1855
1855
  additionalHoursCount?: number;
1856
+ /** Optional B2B/partner pricing profile used for availability and server pricing. */
1857
+ pricingProfileId?: string | null;
1856
1858
  /**
1857
1859
  * Admin only: allow outbound/return capacity holds above vacancies. Server requires admin JWT
1858
1860
  * and still validates permission; omit or false for public/partner flows.
@@ -1888,6 +1890,7 @@ export function summarizeReserveRequestForTelemetry(req: ReserveRequest): Record
1888
1890
  promoPresent: Boolean(req.promoCode?.trim()),
1889
1891
  additionalHoursCount: req.additionalHoursCount ?? null,
1890
1892
  childSafetySeatsCount: req.childSafetySeatsCount ?? null,
1893
+ pricingProfileId: req.pricingProfileId ?? null,
1891
1894
  allowOverbook: req.allowOverbook === true,
1892
1895
  };
1893
1896
  }
@@ -1908,6 +1911,8 @@ export interface PublicNewBookingQuoteV2Request {
1908
1911
  cancellationPolicyId?: string | null;
1909
1912
  addOnSelections?: Array<{ addOnId: string; quantity?: number; variantId?: string }> | null;
1910
1913
  additionalHoursCount?: number | null;
1914
+ dependentAddOnSelection?: CheckoutDependentAddOnSelection | null;
1915
+ pricingProfileId?: string | null;
1911
1916
  allowOverbook?: boolean | null;
1912
1917
  }
1913
1918