@ticketboothapp/booking 1.2.70 → 1.2.72
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
|
@@ -826,17 +826,12 @@ export function AdminChangeBookingFlow({
|
|
|
826
826
|
/** Any change from an existing booking (public or provider). */
|
|
827
827
|
const isChangeBookingContext = Boolean(initialValues?.bookingReference?.trim());
|
|
828
828
|
/**
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
* `handleConfirmWithoutPayment` (or after paying in the checkout modal).
|
|
829
|
+
* Public `POST .../change/quote` path whenever we are editing an existing booking — including when the host passes
|
|
830
|
+
* `onChangeBooking`. Provider apply (`onChangeBooking`) runs only after Pay now / Confirm without payment, never from
|
|
831
|
+
* Continue alone.
|
|
833
832
|
*/
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
isChangeBookingContext &&
|
|
837
|
-
bookingAppMode === 'provider-dashboard' &&
|
|
838
|
-
isProviderDashboardChange;
|
|
839
|
-
const isCustomerSelfServeChange = !isProviderDashboardChange || deferProviderApplyToAdminPaymentChoice;
|
|
833
|
+
const isCustomerSelfServeChange =
|
|
834
|
+
!isProviderDashboardChange || isChangeBookingContext;
|
|
840
835
|
/** Do not render catalog-/FE-derived dollar amounts in UI until `quoteChangeBooking` returns `serverDisplay`. */
|
|
841
836
|
const suppressSelfServeCurrencyUi = isCustomerSelfServeChange;
|
|
842
837
|
|
|
@@ -1722,14 +1717,14 @@ export function AdminChangeBookingFlow({
|
|
|
1722
1717
|
}, [initialValues?.productId, product.productId]);
|
|
1723
1718
|
|
|
1724
1719
|
/**
|
|
1725
|
-
* Receipt pricing on protected seats/fees
|
|
1726
|
-
*
|
|
1720
|
+
* Receipt pricing on protected seats/fees: Rule A / B per `change-flow-pricing.ts`, same as public self-serve whenever
|
|
1721
|
+
* `POST .../change/quote` runs (`isCustomerSelfServeChange`, including provider dashboard with `onChangeBooking`).
|
|
1727
1722
|
*/
|
|
1728
1723
|
const changeFlowApplyReceiptPaidFloors = useMemo(
|
|
1729
1724
|
() =>
|
|
1730
|
-
|
|
1725
|
+
isCustomerSelfServeChange &&
|
|
1731
1726
|
changeFlowBookingParentProductIdForFloors === product.productId.trim(),
|
|
1732
|
-
[
|
|
1727
|
+
[isCustomerSelfServeChange, changeFlowBookingParentProductIdForFloors, product.productId],
|
|
1733
1728
|
);
|
|
1734
1729
|
|
|
1735
1730
|
useEffect(() => {
|
|
@@ -2585,7 +2580,7 @@ export function AdminChangeBookingFlow({
|
|
|
2585
2580
|
latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId,
|
|
2586
2581
|
]);
|
|
2587
2582
|
|
|
2588
|
-
/** Order-summary return row uses self-serve floors via [selectedReturnOptionWithFloor]
|
|
2583
|
+
/** Order-summary return row uses self-serve floors via [selectedReturnOptionWithFloor] when `isCustomerSelfServeChange`. */
|
|
2589
2584
|
const returnOptionForOrderSummary = useMemo(
|
|
2590
2585
|
() => selectedReturnOptionWithFloor,
|
|
2591
2586
|
[selectedReturnOptionWithFloor],
|
|
@@ -2839,7 +2834,7 @@ export function AdminChangeBookingFlow({
|
|
|
2839
2834
|
*/
|
|
2840
2835
|
const changeFlowProtectedReturnAdjustment = useMemo(() => {
|
|
2841
2836
|
if (totalQuantity <= 0) return returnPriceAdjustment;
|
|
2842
|
-
if (
|
|
2837
|
+
if (!isCustomerSelfServeChange) return returnPriceAdjustment;
|
|
2843
2838
|
if (effectiveChangeFlowReturnUnitFloorPerPerson == null) return returnPriceAdjustment;
|
|
2844
2839
|
const livePerPerson =
|
|
2845
2840
|
returnOptionCatalogPerPerson ?? (selectedReturnOption?.priceAdjustmentByCurrency?.[currency] ?? 0);
|
|
@@ -2853,13 +2848,13 @@ export function AdminChangeBookingFlow({
|
|
|
2853
2848
|
}, [
|
|
2854
2849
|
totalQuantity,
|
|
2855
2850
|
returnPriceAdjustment,
|
|
2856
|
-
isProviderDashboardChange,
|
|
2857
2851
|
effectiveChangeFlowReturnUnitFloorPerPerson,
|
|
2858
2852
|
selectedReturnOption,
|
|
2859
2853
|
returnOptionCatalogPerPerson,
|
|
2860
2854
|
currency,
|
|
2861
2855
|
changeFlowInitialTicketCount,
|
|
2862
2856
|
changeFlowReturnPricingRuleA,
|
|
2857
|
+
isCustomerSelfServeChange,
|
|
2863
2858
|
]);
|
|
2864
2859
|
|
|
2865
2860
|
/** Return row amount for PriceSummary, Stripe breakdown, and CheckoutModal (catalog vs protected same-product-option). */
|
|
@@ -3110,6 +3105,7 @@ export function AdminChangeBookingFlow({
|
|
|
3110
3105
|
ticketLineItems: [] as Array<{ category: string; qty: number }>,
|
|
3111
3106
|
effectiveSubtotal: 0,
|
|
3112
3107
|
appliedPromoCode: null as string | null,
|
|
3108
|
+
changeBookingPromo: null as { priorAmount?: number } | null,
|
|
3113
3109
|
});
|
|
3114
3110
|
|
|
3115
3111
|
const promoDiscountFetchKey = useMemo(() => {
|
|
@@ -3128,6 +3124,7 @@ export function AdminChangeBookingFlow({
|
|
|
3128
3124
|
currency,
|
|
3129
3125
|
quantitiesSignature,
|
|
3130
3126
|
String(Math.round(effectiveSubtotal * 100)),
|
|
3127
|
+
lockedPromoCode ? String(Math.round((originalReceipt?.promoAmount ?? 0) * 100)) : '',
|
|
3131
3128
|
].join('::');
|
|
3132
3129
|
}, [
|
|
3133
3130
|
appliedPromoCode,
|
|
@@ -3140,6 +3137,8 @@ export function AdminChangeBookingFlow({
|
|
|
3140
3137
|
currency,
|
|
3141
3138
|
quantitiesSignature,
|
|
3142
3139
|
effectiveSubtotal,
|
|
3140
|
+
lockedPromoCode,
|
|
3141
|
+
originalReceipt?.promoAmount,
|
|
3143
3142
|
]);
|
|
3144
3143
|
|
|
3145
3144
|
promoDiscountParamsRef.current = {
|
|
@@ -3147,6 +3146,9 @@ export function AdminChangeBookingFlow({
|
|
|
3147
3146
|
ticketLineItems: ticketLineItems.map((l) => ({ category: l.category, qty: l.qty })),
|
|
3148
3147
|
effectiveSubtotal,
|
|
3149
3148
|
appliedPromoCode,
|
|
3149
|
+
changeBookingPromo: lockedPromoCode
|
|
3150
|
+
? { priorAmount: originalReceipt?.promoAmount }
|
|
3151
|
+
: null,
|
|
3150
3152
|
};
|
|
3151
3153
|
|
|
3152
3154
|
useEffect(() => {
|
|
@@ -3166,6 +3168,7 @@ export function AdminChangeBookingFlow({
|
|
|
3166
3168
|
ticketLineItems: lines,
|
|
3167
3169
|
effectiveSubtotal: sub,
|
|
3168
3170
|
appliedPromoCode: code,
|
|
3171
|
+
changeBookingPromo,
|
|
3169
3172
|
} = promoDiscountParamsRef.current;
|
|
3170
3173
|
if (!code || !sel) return;
|
|
3171
3174
|
const companyId = product.companyId ?? env.COMPANY_ID;
|
|
@@ -3183,7 +3186,13 @@ export function AdminChangeBookingFlow({
|
|
|
3183
3186
|
currency,
|
|
3184
3187
|
items,
|
|
3185
3188
|
sel.dateTime,
|
|
3186
|
-
sub
|
|
3189
|
+
sub,
|
|
3190
|
+
changeBookingPromo
|
|
3191
|
+
? {
|
|
3192
|
+
forBookingChange: true,
|
|
3193
|
+
priorPromoDiscountAmount: changeBookingPromo.priorAmount,
|
|
3194
|
+
}
|
|
3195
|
+
: undefined
|
|
3187
3196
|
)
|
|
3188
3197
|
.then((res) => {
|
|
3189
3198
|
if (cancelled) return;
|
|
@@ -3209,7 +3218,7 @@ export function AdminChangeBookingFlow({
|
|
|
3209
3218
|
|
|
3210
3219
|
// Percentage/fixed promos: tax on discounted amount (promo before GST per CRA guidance).
|
|
3211
3220
|
// Vouchers and gift cards: tax on full subtotal (voucher discount includes tax on free portion; gift card is payment).
|
|
3212
|
-
// Change booking:
|
|
3221
|
+
// Change booking: get-promo-discount uses forBookingChange + prior promo from receipt so expired codes stay locked (BE).
|
|
3213
3222
|
const lockedPromoFallbackAmount =
|
|
3214
3223
|
lockedPromoCode
|
|
3215
3224
|
? Math.max(0, originalReceipt?.promoAmount ?? 0)
|
|
@@ -4476,49 +4485,6 @@ export function AdminChangeBookingFlow({
|
|
|
4476
4485
|
return;
|
|
4477
4486
|
}
|
|
4478
4487
|
|
|
4479
|
-
if (onChangeBooking && !deferProviderApplyToAdminPaymentChoice) {
|
|
4480
|
-
const pickupForChange = pickupLocationId
|
|
4481
|
-
? product.pickupLocations?.find((loc) => loc.id === pickupLocationId)
|
|
4482
|
-
: null;
|
|
4483
|
-
await onChangeBooking({
|
|
4484
|
-
productId: availabilityProductOptionId,
|
|
4485
|
-
dateTime: selectedAvailability.dateTime,
|
|
4486
|
-
bookingItems,
|
|
4487
|
-
returnAvailabilityId: selectedReturnOption?.returnAvailabilityId ?? null,
|
|
4488
|
-
pickupLocationId: pickupLocationId ?? null,
|
|
4489
|
-
travelerHotel: pickupForChange?.name ?? null,
|
|
4490
|
-
startTime: selectedAvailability.dateTime ?? null,
|
|
4491
|
-
passengerCount: null,
|
|
4492
|
-
childSafetySeatsCount: null,
|
|
4493
|
-
foodRestrictions: null,
|
|
4494
|
-
addOnSelections: addOnSelections.length > 0 ? addOnSelections : null,
|
|
4495
|
-
cancellationPolicyId: cancellationPolicyId ?? initialValues?.cancellationPolicyId ?? null,
|
|
4496
|
-
promoCode: appliedPromoCode ?? null,
|
|
4497
|
-
newTotalAmount: displayChangeFlowProposedTotal,
|
|
4498
|
-
additionalHoursCount: null,
|
|
4499
|
-
pricingAdjustment:
|
|
4500
|
-
providerPricingOverrides.length > 0 || providerAdditionalAdjustments.length > 0
|
|
4501
|
-
? {
|
|
4502
|
-
mode: 'MANUAL_LINES',
|
|
4503
|
-
lineOverrides: providerPricingOverrides,
|
|
4504
|
-
additionalAdjustments: providerAdditionalAdjustments,
|
|
4505
|
-
}
|
|
4506
|
-
: undefined,
|
|
4507
|
-
capacitySeatCredit: {
|
|
4508
|
-
enabled: true,
|
|
4509
|
-
previousPassengerCount: changeFlowInitialTicketCount,
|
|
4510
|
-
previousAvailabilityId: initialValues?.availabilityId ?? null,
|
|
4511
|
-
previousReturnAvailabilityId: initialValues?.returnAvailabilityId ?? null,
|
|
4512
|
-
},
|
|
4513
|
-
});
|
|
4514
|
-
const refAfterChange = initialValues?.bookingReference?.trim();
|
|
4515
|
-
if (refAfterChange) {
|
|
4516
|
-
onSuccess?.({ reservationReference: refAfterChange });
|
|
4517
|
-
}
|
|
4518
|
-
setLoading(false);
|
|
4519
|
-
return;
|
|
4520
|
-
}
|
|
4521
|
-
|
|
4522
4488
|
const bookingSourceContext = buildBookingSourceContext(bookingSourceAttribution, {
|
|
4523
4489
|
clientChannelSource: inferClientBookingSourceFromProductIds(
|
|
4524
4490
|
product.productId,
|
|
@@ -2995,6 +2995,8 @@ export function ChangeBookingFlow({
|
|
|
2995
2995
|
ticketLineItems: [] as Array<{ category: string; qty: number }>,
|
|
2996
2996
|
effectiveSubtotal: 0,
|
|
2997
2997
|
appliedPromoCode: null as string | null,
|
|
2998
|
+
/** Change flow: pass BE stored promo discount for expired / fixed promo locking. */
|
|
2999
|
+
changeBookingPromo: null as { priorAmount?: number } | null,
|
|
2998
3000
|
});
|
|
2999
3001
|
|
|
3000
3002
|
const promoDiscountFetchKey = useMemo(() => {
|
|
@@ -3013,6 +3015,7 @@ export function ChangeBookingFlow({
|
|
|
3013
3015
|
currency,
|
|
3014
3016
|
quantitiesSignature,
|
|
3015
3017
|
String(Math.round(effectiveSubtotal * 100)),
|
|
3018
|
+
lockedPromoCode ? String(Math.round((originalReceipt?.promoAmount ?? 0) * 100)) : '',
|
|
3016
3019
|
].join('::');
|
|
3017
3020
|
}, [
|
|
3018
3021
|
appliedPromoCode,
|
|
@@ -3025,6 +3028,8 @@ export function ChangeBookingFlow({
|
|
|
3025
3028
|
currency,
|
|
3026
3029
|
quantitiesSignature,
|
|
3027
3030
|
effectiveSubtotal,
|
|
3031
|
+
lockedPromoCode,
|
|
3032
|
+
originalReceipt?.promoAmount,
|
|
3028
3033
|
]);
|
|
3029
3034
|
|
|
3030
3035
|
promoDiscountParamsRef.current = {
|
|
@@ -3032,6 +3037,9 @@ export function ChangeBookingFlow({
|
|
|
3032
3037
|
ticketLineItems: ticketLineItems.map((l) => ({ category: l.category, qty: l.qty })),
|
|
3033
3038
|
effectiveSubtotal,
|
|
3034
3039
|
appliedPromoCode,
|
|
3040
|
+
changeBookingPromo: lockedPromoCode
|
|
3041
|
+
? { priorAmount: originalReceipt?.promoAmount }
|
|
3042
|
+
: null,
|
|
3035
3043
|
};
|
|
3036
3044
|
|
|
3037
3045
|
useEffect(() => {
|
|
@@ -3051,6 +3059,7 @@ export function ChangeBookingFlow({
|
|
|
3051
3059
|
ticketLineItems: lines,
|
|
3052
3060
|
effectiveSubtotal: sub,
|
|
3053
3061
|
appliedPromoCode: code,
|
|
3062
|
+
changeBookingPromo,
|
|
3054
3063
|
} = promoDiscountParamsRef.current;
|
|
3055
3064
|
if (!code || !sel) return;
|
|
3056
3065
|
const companyId = product.companyId ?? env.COMPANY_ID;
|
|
@@ -3068,7 +3077,13 @@ export function ChangeBookingFlow({
|
|
|
3068
3077
|
currency,
|
|
3069
3078
|
items,
|
|
3070
3079
|
sel.dateTime,
|
|
3071
|
-
sub
|
|
3080
|
+
sub,
|
|
3081
|
+
changeBookingPromo
|
|
3082
|
+
? {
|
|
3083
|
+
forBookingChange: true,
|
|
3084
|
+
priorPromoDiscountAmount: changeBookingPromo.priorAmount,
|
|
3085
|
+
}
|
|
3086
|
+
: undefined
|
|
3072
3087
|
)
|
|
3073
3088
|
.then((res) => {
|
|
3074
3089
|
if (cancelled) return;
|
|
@@ -3094,7 +3109,7 @@ export function ChangeBookingFlow({
|
|
|
3094
3109
|
|
|
3095
3110
|
// Percentage/fixed promos: tax on discounted amount (promo before GST per CRA guidance).
|
|
3096
3111
|
// Vouchers and gift cards: tax on full subtotal (voucher discount includes tax on free portion; gift card is payment).
|
|
3097
|
-
// Change booking:
|
|
3112
|
+
// Change booking: get-promo-discount uses forBookingChange + receipt prior promo (BE locks expired % / fixed / voucher).
|
|
3098
3113
|
const lockedPromoFallbackAmount =
|
|
3099
3114
|
lockedPromoCode
|
|
3100
3115
|
? Math.max(0, originalReceipt?.promoAmount ?? 0)
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* channel) on top of these formulas.
|
|
6
6
|
*
|
|
7
7
|
* ### 1. When receipt “paid floors” apply
|
|
8
|
-
*
|
|
9
|
-
* (`
|
|
10
|
-
*
|
|
8
|
+
* When the booking’s **parent catalog product** matches the **loaded product** and the flow uses the public
|
|
9
|
+
* `POST .../change/quote` path (`isCustomerSelfServeChange` in AdminChangeBookingFlow, including provider dashboard with
|
|
10
|
+
* `onChangeBooking`).
|
|
11
11
|
*
|
|
12
12
|
* ### 2. Tickets (per category)
|
|
13
13
|
* **Unchanged itinerary** (same calendar departure date **and** same product option as the booking): among seats up to the
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
* BE `PublicChangeBookingQuotePricing`: **baseline party** (originally booked headcount) vs **incremental** seats. **Rule A**
|
|
27
27
|
* (same `returnAvailabilityId` as the booking **and** unchanged outbound itinerary): protected pay exact locked per person;
|
|
28
28
|
* incremental pay **live catalog** return only ($0 when free). **Else Rule B:** protected pay **`max(floor, live)`**;
|
|
29
|
-
* incremental pay **live** only.
|
|
29
|
+
* incremental pay **live** only.
|
|
30
30
|
*
|
|
31
31
|
* ### 5. Return option — **picker UI only** (BookingFlow)
|
|
32
32
|
* **Self-serve:** return cards use the floored per-person price when a floor exists (aligned with §4), for every return
|
|
33
33
|
* slot, regardless of date/option change.
|
|
34
|
-
*
|
|
34
|
+
* With `POST .../change/quote`, return cards follow the same floor behavior as self-serve.
|
|
35
35
|
*
|
|
36
36
|
* ### 6. Quote “new booking” total & balance
|
|
37
37
|
* **FE proposed total** = full cart math (subtotal + tax − promo), cent-rounded; optional **1¢ reconcile** to old receipt
|
package/src/lib/booking-api.ts
CHANGED
|
@@ -651,6 +651,13 @@ export interface GetPromoDiscountResponse {
|
|
|
651
651
|
isVoucher?: boolean;
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
/** Booking change: server locks discount using stored receipt promo when windows expire / fixed codes. */
|
|
655
|
+
export interface GetPromoDiscountBookingChangeOptions {
|
|
656
|
+
forBookingChange?: boolean;
|
|
657
|
+
/** Stored promo discount from the existing booking (major units, same currency as quote). */
|
|
658
|
+
priorPromoDiscountAmount?: number;
|
|
659
|
+
}
|
|
660
|
+
|
|
654
661
|
export async function getPromoDiscount(
|
|
655
662
|
promoCode: string,
|
|
656
663
|
companyId: string,
|
|
@@ -659,7 +666,8 @@ export async function getPromoDiscount(
|
|
|
659
666
|
currency: string,
|
|
660
667
|
items: Array<{ category: string; qty: number }>,
|
|
661
668
|
dateTime?: string,
|
|
662
|
-
subtotal?: number
|
|
669
|
+
subtotal?: number,
|
|
670
|
+
bookingChange?: GetPromoDiscountBookingChangeOptions
|
|
663
671
|
): Promise<GetPromoDiscountResponse> {
|
|
664
672
|
const itemsStr = items.map((i) => `${i.category}:${i.qty}`).join(',');
|
|
665
673
|
const params = new URLSearchParams({
|
|
@@ -672,6 +680,15 @@ export async function getPromoDiscount(
|
|
|
672
680
|
});
|
|
673
681
|
if (dateTime) params.set('dateTime', dateTime);
|
|
674
682
|
if (subtotal != null && subtotal > 0) params.set('subtotal', String(subtotal));
|
|
683
|
+
if (bookingChange?.forBookingChange === true) {
|
|
684
|
+
params.set('forBookingChange', 'true');
|
|
685
|
+
}
|
|
686
|
+
if (
|
|
687
|
+
bookingChange?.priorPromoDiscountAmount != null &&
|
|
688
|
+
Number.isFinite(bookingChange.priorPromoDiscountAmount)
|
|
689
|
+
) {
|
|
690
|
+
params.set('priorPromoDiscountAmount', String(bookingChange.priorPromoDiscountAmount));
|
|
691
|
+
}
|
|
675
692
|
const res = await fetchBookingGetWithRetry(`${API_BASE}/1/get-promo-discount?${params}`);
|
|
676
693
|
if (!res.ok) {
|
|
677
694
|
const err = await res.json();
|