@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 +1 -1
- package/src/components/booking/NewBookingFlow.tsx +1 -0
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +4 -0
- package/src/components/booking/PrivateShuttleCheckoutSection.tsx +10 -2
- package/src/components/booking/private-shuttle-checkout-controller.ts +3 -0
- package/src/components/booking/private-shuttle-reservation-runner.ts +4 -0
- package/src/components/booking/standard-booking-checkout-controller.ts +4 -0
- package/src/components/booking/standard-booking-reservation-runner.ts +8 -0
- package/src/components/booking/usePrivateShuttlePromoController.ts +30 -1
- package/src/lib/booking/checkout-breakdown.ts +2 -0
- package/src/lib/booking-api.ts +5 -0
package/package.json
CHANGED
|
@@ -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';
|
package/src/lib/booking-api.ts
CHANGED
|
@@ -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
|
|