@ticketboothapp/booking 1.2.131 → 1.2.133
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/AdminChangeBookingFlow.tsx +2 -0
- package/src/components/booking/ChangeBookingFlow.tsx +2 -0
- package/src/components/booking/useAdminChangePriceSummary.ts +16 -0
- package/src/components/booking/useAdminChangePromoDiscount.ts +66 -2
- package/src/components/booking/useAdminChangeProtectedPricing.ts +31 -16
- package/src/components/booking/useChangeBookingPriceSummary.ts +16 -0
- package/src/components/booking/useChangeBookingPromoDiscount.ts +67 -2
- package/src/components/booking/useChangeBookingProtectedPricing.ts +31 -16
- package/src/components/booking/useStandardBookingPriceSummary.ts +2 -0
- package/src/components/booking/useStandardBookingPromoDiscount.ts +8 -0
- package/src/lib/booking-api.ts +41 -0
package/package.json
CHANGED
|
@@ -517,6 +517,8 @@ export function AdminChangeBookingFlow({
|
|
|
517
517
|
feeLineItems,
|
|
518
518
|
changeFlowBookedFeeUnitByNormalizedLabel,
|
|
519
519
|
addOnSelections,
|
|
520
|
+
originalAddOnSelections: initialValues?.addOnSelections ?? null,
|
|
521
|
+
originalProductOptionId: initialValues?.productOptionId ?? null,
|
|
520
522
|
addOns,
|
|
521
523
|
adminCustomReceiptLines,
|
|
522
524
|
quantities,
|
|
@@ -528,6 +528,8 @@ export function ChangeBookingFlow({
|
|
|
528
528
|
feeLineItems,
|
|
529
529
|
changeFlowBookedFeeUnitByNormalizedLabel,
|
|
530
530
|
addOnSelections,
|
|
531
|
+
originalAddOnSelections: initialValues?.addOnSelections ?? null,
|
|
532
|
+
originalProductOptionId: initialValues?.productOptionId ?? null,
|
|
531
533
|
addOns,
|
|
532
534
|
quantities,
|
|
533
535
|
pricing,
|
|
@@ -59,6 +59,8 @@ export interface UseAdminChangePriceSummaryParams {
|
|
|
59
59
|
feeLineItems: OrderSummaryFeeLine[];
|
|
60
60
|
changeFlowBookedFeeUnitByNormalizedLabel: Map<string, number>;
|
|
61
61
|
addOnSelections: AddOnSelection[];
|
|
62
|
+
originalAddOnSelections?: AddOnSelection[] | null;
|
|
63
|
+
originalProductOptionId?: string | null;
|
|
62
64
|
addOns: AddOn[];
|
|
63
65
|
adminCustomReceiptLines: AdminCustomReceiptLine[];
|
|
64
66
|
quantities: Record<string, number>;
|
|
@@ -106,6 +108,8 @@ export function useAdminChangePriceSummary({
|
|
|
106
108
|
feeLineItems,
|
|
107
109
|
changeFlowBookedFeeUnitByNormalizedLabel,
|
|
108
110
|
addOnSelections,
|
|
111
|
+
originalAddOnSelections,
|
|
112
|
+
originalProductOptionId,
|
|
109
113
|
addOns,
|
|
110
114
|
adminCustomReceiptLines,
|
|
111
115
|
quantities,
|
|
@@ -138,6 +142,9 @@ export function useAdminChangePriceSummary({
|
|
|
138
142
|
effectiveSubtotal,
|
|
139
143
|
adminCustomAdjustmentTotal,
|
|
140
144
|
effectiveSubtotalForCheckout,
|
|
145
|
+
promoEligibleSubtotalForCheckout,
|
|
146
|
+
addOnTotal,
|
|
147
|
+
originalAddOnSubtotal,
|
|
141
148
|
feeLineItemsForChangeFlowDisplay,
|
|
142
149
|
feeLineItemsWithAddOns,
|
|
143
150
|
} = useAdminChangeProtectedPricing({
|
|
@@ -160,9 +167,14 @@ export function useAdminChangePriceSummary({
|
|
|
160
167
|
feeLineItems,
|
|
161
168
|
changeFlowBookedFeeUnitByNormalizedLabel,
|
|
162
169
|
addOnSelections,
|
|
170
|
+
originalAddOnSelections,
|
|
163
171
|
addOns,
|
|
164
172
|
adminCustomReceiptLines,
|
|
165
173
|
});
|
|
174
|
+
const promoEligibleSubtotalForCheckoutExcludingCancellation = Math.max(
|
|
175
|
+
0,
|
|
176
|
+
promoEligibleSubtotalForCheckout - cancellationPolicyFee,
|
|
177
|
+
);
|
|
166
178
|
|
|
167
179
|
const providerPricingUi = flowUi?.providerDashboardChangePricingUi;
|
|
168
180
|
const providerQuotedLines = useMemo(
|
|
@@ -307,6 +319,10 @@ export function useAdminChangePriceSummary({
|
|
|
307
319
|
ticketLineItemsForChangeFlowDisplay,
|
|
308
320
|
feeLineItemsWithAddOns,
|
|
309
321
|
effectiveSubtotalForCheckout,
|
|
322
|
+
promoEligibleSubtotalForCheckout: promoEligibleSubtotalForCheckoutExcludingCancellation,
|
|
323
|
+
addOnTotal,
|
|
324
|
+
originalAddOnSubtotal,
|
|
325
|
+
originalProductOptionId,
|
|
310
326
|
lockedPromoCode,
|
|
311
327
|
originalReceipt,
|
|
312
328
|
isTaxIncludedInPrice,
|
|
@@ -24,12 +24,29 @@ interface UseAdminChangePromoDiscountParams {
|
|
|
24
24
|
ticketLineItemsForChangeFlowDisplay: OrderSummaryTicketLine[];
|
|
25
25
|
feeLineItemsWithAddOns: OrderSummaryFeeLine[];
|
|
26
26
|
effectiveSubtotalForCheckout: number;
|
|
27
|
+
promoEligibleSubtotalForCheckout?: number;
|
|
28
|
+
addOnTotal?: number;
|
|
29
|
+
originalAddOnSubtotal?: number;
|
|
30
|
+
originalProductOptionId?: string | null;
|
|
27
31
|
lockedPromoCode: string | null;
|
|
28
32
|
originalReceipt: ChangeBookingFlowProps['originalReceipt'];
|
|
29
33
|
isTaxIncludedInPrice: boolean;
|
|
30
34
|
pricingConfig: PricingConfig | null;
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
function originalReceiptGrossSubtotalBeforePromo(
|
|
38
|
+
originalReceipt: ChangeBookingFlowProps['originalReceipt'],
|
|
39
|
+
): number {
|
|
40
|
+
const chargeTypes = new Set(['TICKET', 'FEE', 'RETURN_OPTION']);
|
|
41
|
+
return (originalReceipt?.lineItems ?? []).reduce((sum, line) => {
|
|
42
|
+
const amount = Number(line.amount) || 0;
|
|
43
|
+
if (amount <= 0) return sum;
|
|
44
|
+
const type = String(line.type ?? '').trim().toUpperCase();
|
|
45
|
+
if (!chargeTypes.has(type)) return sum;
|
|
46
|
+
return sum + amount;
|
|
47
|
+
}, 0);
|
|
48
|
+
}
|
|
49
|
+
|
|
33
50
|
export function useAdminChangePromoDiscount({
|
|
34
51
|
appliedPromoCode,
|
|
35
52
|
selectedAvailability,
|
|
@@ -42,6 +59,10 @@ export function useAdminChangePromoDiscount({
|
|
|
42
59
|
ticketLineItemsForChangeFlowDisplay,
|
|
43
60
|
feeLineItemsWithAddOns,
|
|
44
61
|
effectiveSubtotalForCheckout,
|
|
62
|
+
promoEligibleSubtotalForCheckout = effectiveSubtotalForCheckout,
|
|
63
|
+
addOnTotal = Math.max(0, effectiveSubtotalForCheckout - promoEligibleSubtotalForCheckout),
|
|
64
|
+
originalAddOnSubtotal = 0,
|
|
65
|
+
originalProductOptionId,
|
|
45
66
|
lockedPromoCode,
|
|
46
67
|
originalReceipt,
|
|
47
68
|
isTaxIncludedInPrice,
|
|
@@ -65,20 +86,40 @@ export function useAdminChangePromoDiscount({
|
|
|
65
86
|
selectedAvailability: null as Availability | null,
|
|
66
87
|
ticketLineItems: [] as Array<{ category: string; qty: number; itemTotal: number }>,
|
|
67
88
|
effectiveSubtotal: 0,
|
|
89
|
+
promoEligibleSubtotal: 0,
|
|
68
90
|
displayedProductFeeTotal: 0,
|
|
69
91
|
appliedPromoCode: null as string | null,
|
|
70
|
-
changeBookingPromo: null as {
|
|
92
|
+
changeBookingPromo: null as {
|
|
93
|
+
priorAmount?: number;
|
|
94
|
+
originalFullSubtotal?: number;
|
|
95
|
+
originalAddOnSubtotal?: number;
|
|
96
|
+
newFullSubtotal?: number;
|
|
97
|
+
newAddOnSubtotal?: number;
|
|
98
|
+
} | null,
|
|
71
99
|
});
|
|
72
100
|
|
|
73
101
|
const originalReceiptPromoAdjustment = useMemo(
|
|
74
102
|
() => deriveOriginalReceiptPromoAdjustment(originalReceipt),
|
|
75
103
|
[originalReceipt],
|
|
76
104
|
);
|
|
105
|
+
const originalFullSubtotalBeforePromo = useMemo(
|
|
106
|
+
() => originalReceiptGrossSubtotalBeforePromo(originalReceipt),
|
|
107
|
+
[originalReceipt],
|
|
108
|
+
);
|
|
77
109
|
|
|
78
110
|
const selectedAvailabilityOptionId = useMemo(
|
|
79
111
|
() => (selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : ''),
|
|
80
112
|
[selectedAvailability],
|
|
81
113
|
);
|
|
114
|
+
const includeLegacyPromoRetentionContext = useMemo(() => {
|
|
115
|
+
const originalOptionId = originalProductOptionId?.trim();
|
|
116
|
+
return Boolean(
|
|
117
|
+
lockedPromoCode &&
|
|
118
|
+
originalOptionId &&
|
|
119
|
+
selectedAvailabilityOptionId &&
|
|
120
|
+
selectedAvailabilityOptionId === originalOptionId,
|
|
121
|
+
);
|
|
122
|
+
}, [lockedPromoCode, originalProductOptionId, selectedAvailabilityOptionId]);
|
|
82
123
|
|
|
83
124
|
const promoDiscountFetchKey = useMemo(() => {
|
|
84
125
|
if (!appliedPromoCode || !selectedAvailability || totalQuantity === 0) return '';
|
|
@@ -100,6 +141,10 @@ export function useAdminChangePromoDiscount({
|
|
|
100
141
|
.join('|'),
|
|
101
142
|
String(Math.round(feeLineItemsWithAddOns.reduce((sum, fee) => sum + fee.totalAmount, 0) * 100)),
|
|
102
143
|
String(Math.round(effectiveSubtotalForCheckout * 100)),
|
|
144
|
+
String(Math.round(promoEligibleSubtotalForCheckout * 100)),
|
|
145
|
+
String(Math.round(addOnTotal * 100)),
|
|
146
|
+
includeLegacyPromoRetentionContext ? String(Math.round(originalFullSubtotalBeforePromo * 100)) : '',
|
|
147
|
+
includeLegacyPromoRetentionContext ? String(Math.round(originalAddOnSubtotal * 100)) : '',
|
|
103
148
|
lockedPromoCode ? String(Math.round(originalReceiptPromoAdjustment.amount * 100)) : '',
|
|
104
149
|
].join('::');
|
|
105
150
|
}, [
|
|
@@ -115,6 +160,11 @@ export function useAdminChangePromoDiscount({
|
|
|
115
160
|
ticketLineItemsForChangeFlowDisplay,
|
|
116
161
|
feeLineItemsWithAddOns,
|
|
117
162
|
effectiveSubtotalForCheckout,
|
|
163
|
+
promoEligibleSubtotalForCheckout,
|
|
164
|
+
addOnTotal,
|
|
165
|
+
includeLegacyPromoRetentionContext,
|
|
166
|
+
originalFullSubtotalBeforePromo,
|
|
167
|
+
originalAddOnSubtotal,
|
|
118
168
|
lockedPromoCode,
|
|
119
169
|
originalReceiptPromoAdjustment.amount,
|
|
120
170
|
]);
|
|
@@ -127,10 +177,17 @@ export function useAdminChangePromoDiscount({
|
|
|
127
177
|
itemTotal: line.itemTotal,
|
|
128
178
|
})),
|
|
129
179
|
effectiveSubtotal: effectiveSubtotalForCheckout,
|
|
180
|
+
promoEligibleSubtotal: promoEligibleSubtotalForCheckout,
|
|
130
181
|
displayedProductFeeTotal: feeLineItemsWithAddOns.reduce((sum, fee) => sum + fee.totalAmount, 0),
|
|
131
182
|
appliedPromoCode,
|
|
132
183
|
changeBookingPromo: lockedPromoCode
|
|
133
|
-
? {
|
|
184
|
+
? {
|
|
185
|
+
priorAmount: originalReceiptPromoAdjustment.amount,
|
|
186
|
+
originalFullSubtotal: includeLegacyPromoRetentionContext ? originalFullSubtotalBeforePromo : undefined,
|
|
187
|
+
originalAddOnSubtotal: includeLegacyPromoRetentionContext ? originalAddOnSubtotal : undefined,
|
|
188
|
+
newFullSubtotal: includeLegacyPromoRetentionContext ? effectiveSubtotalForCheckout : undefined,
|
|
189
|
+
newAddOnSubtotal: includeLegacyPromoRetentionContext ? addOnTotal : undefined,
|
|
190
|
+
}
|
|
134
191
|
: null,
|
|
135
192
|
};
|
|
136
193
|
|
|
@@ -151,6 +208,7 @@ export function useAdminChangePromoDiscount({
|
|
|
151
208
|
ticketLineItems: lines,
|
|
152
209
|
effectiveSubtotal: sub,
|
|
153
210
|
displayedProductFeeTotal,
|
|
211
|
+
promoEligibleSubtotal: promoSub,
|
|
154
212
|
appliedPromoCode: code,
|
|
155
213
|
changeBookingPromo,
|
|
156
214
|
} = promoDiscountParamsRef.current;
|
|
@@ -177,10 +235,16 @@ export function useAdminChangePromoDiscount({
|
|
|
177
235
|
priorPromoDiscountAmount: changeBookingPromo.priorAmount,
|
|
178
236
|
displayedTicketLines: lines,
|
|
179
237
|
displayedProductFeeTotal,
|
|
238
|
+
promoEligibleSubtotal: promoSub,
|
|
239
|
+
legacyPromoOriginalFullSubtotal: changeBookingPromo.originalFullSubtotal,
|
|
240
|
+
legacyPromoOriginalAddOnSubtotal: changeBookingPromo.originalAddOnSubtotal,
|
|
241
|
+
legacyPromoNewFullSubtotal: changeBookingPromo.newFullSubtotal,
|
|
242
|
+
legacyPromoNewAddOnSubtotal: changeBookingPromo.newAddOnSubtotal,
|
|
180
243
|
}
|
|
181
244
|
: {
|
|
182
245
|
displayedTicketLines: lines,
|
|
183
246
|
displayedProductFeeTotal,
|
|
247
|
+
promoEligibleSubtotal: promoSub,
|
|
184
248
|
},
|
|
185
249
|
)
|
|
186
250
|
.then((res) => {
|
|
@@ -17,6 +17,23 @@ import { normalizeLineLabelForCompare } from './change-booking-flow-helpers';
|
|
|
17
17
|
type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
|
|
18
18
|
type AdminCustomReceiptLine = { label: string; amountInput: string; amountSign?: number };
|
|
19
19
|
|
|
20
|
+
function addOnSelectionTotal(selections: AddOnSelection[] | null | undefined, addOns: AddOn[]): number {
|
|
21
|
+
let sum = 0;
|
|
22
|
+
for (const sel of selections ?? []) {
|
|
23
|
+
const addOn = addOns.find((a) => a.addOnId === sel.addOnId);
|
|
24
|
+
if (!addOn) continue;
|
|
25
|
+
const basePrice = addOn.price ?? 0;
|
|
26
|
+
const hasVariant =
|
|
27
|
+
(addOn.variantType === 'single_choice' || addOn.variantType === 'multi_quantity') &&
|
|
28
|
+
sel.variantId;
|
|
29
|
+
const variantAdjustment = hasVariant
|
|
30
|
+
? (addOn.variants?.find((v) => v.id === sel.variantId)?.priceAdjustment ?? 0)
|
|
31
|
+
: 0;
|
|
32
|
+
sum += (basePrice + variantAdjustment) * (sel.quantity ?? 1);
|
|
33
|
+
}
|
|
34
|
+
return sum;
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
interface UseAdminChangeProtectedPricingParams {
|
|
21
38
|
selectedAvailability: Availability | null;
|
|
22
39
|
selectedReturnOption: ReturnOption | null;
|
|
@@ -37,6 +54,7 @@ interface UseAdminChangeProtectedPricingParams {
|
|
|
37
54
|
feeLineItems: OrderSummaryFeeLine[];
|
|
38
55
|
changeFlowBookedFeeUnitByNormalizedLabel: Map<string, number>;
|
|
39
56
|
addOnSelections: AddOnSelection[];
|
|
57
|
+
originalAddOnSelections?: AddOnSelection[] | null;
|
|
40
58
|
addOns: AddOn[];
|
|
41
59
|
adminCustomReceiptLines: AdminCustomReceiptLine[];
|
|
42
60
|
}
|
|
@@ -61,25 +79,18 @@ export function useAdminChangeProtectedPricing({
|
|
|
61
79
|
feeLineItems,
|
|
62
80
|
changeFlowBookedFeeUnitByNormalizedLabel,
|
|
63
81
|
addOnSelections,
|
|
82
|
+
originalAddOnSelections,
|
|
64
83
|
addOns,
|
|
65
84
|
adminCustomReceiptLines,
|
|
66
85
|
}: UseAdminChangeProtectedPricingParams) {
|
|
67
|
-
const addOnTotal = useMemo(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
sel.variantId;
|
|
76
|
-
const variantAdjustment = hasVariant
|
|
77
|
-
? (addOn.variants?.find((v) => v.id === sel.variantId)?.priceAdjustment ?? 0)
|
|
78
|
-
: 0;
|
|
79
|
-
sum += (basePrice + variantAdjustment) * (sel.quantity ?? 1);
|
|
80
|
-
}
|
|
81
|
-
return sum;
|
|
82
|
-
}, [addOnSelections, addOns]);
|
|
86
|
+
const addOnTotal = useMemo(
|
|
87
|
+
() => addOnSelectionTotal(addOnSelections, addOns),
|
|
88
|
+
[addOnSelections, addOns],
|
|
89
|
+
);
|
|
90
|
+
const originalAddOnSubtotal = useMemo(
|
|
91
|
+
() => addOnSelectionTotal(originalAddOnSelections, addOns),
|
|
92
|
+
[originalAddOnSelections, addOns],
|
|
93
|
+
);
|
|
83
94
|
|
|
84
95
|
const currentTicketSubtotal = useMemo(
|
|
85
96
|
() => ticketLineItems.reduce((sum, line) => sum + Math.max(0, Number(line.itemTotal) || 0), 0),
|
|
@@ -215,6 +226,7 @@ export function useAdminChangeProtectedPricing({
|
|
|
215
226
|
[adminCustomReceiptLines],
|
|
216
227
|
);
|
|
217
228
|
const effectiveSubtotalForCheckout = roundMoney(effectiveSubtotal + adminCustomAdjustmentTotal);
|
|
229
|
+
const promoEligibleSubtotalForCheckout = roundMoney(effectiveSubtotalBeforeAddOns + adminCustomAdjustmentTotal);
|
|
218
230
|
|
|
219
231
|
const feeLineItemsForChangeFlowDisplay = useMemo(() => {
|
|
220
232
|
if (!changeFlowApplyReceiptPaidFloors || totalQuantity <= 0) return feeLineItems;
|
|
@@ -274,6 +286,9 @@ export function useAdminChangeProtectedPricing({
|
|
|
274
286
|
effectiveSubtotal,
|
|
275
287
|
adminCustomAdjustmentTotal,
|
|
276
288
|
effectiveSubtotalForCheckout,
|
|
289
|
+
promoEligibleSubtotalForCheckout,
|
|
290
|
+
addOnTotal,
|
|
291
|
+
originalAddOnSubtotal,
|
|
277
292
|
feeLineItemsForChangeFlowDisplay,
|
|
278
293
|
feeLineItemsWithAddOns,
|
|
279
294
|
};
|
|
@@ -48,6 +48,8 @@ interface UseChangeBookingPriceSummaryParams {
|
|
|
48
48
|
feeLineItems: OrderSummaryFeeLine[];
|
|
49
49
|
changeFlowBookedFeeUnitByNormalizedLabel: Map<string, number>;
|
|
50
50
|
addOnSelections: AddOnSelection[];
|
|
51
|
+
originalAddOnSelections?: AddOnSelection[] | null;
|
|
52
|
+
originalProductOptionId?: string | null;
|
|
51
53
|
addOns: AddOn[];
|
|
52
54
|
quantities: Record<string, number>;
|
|
53
55
|
pricing: PricingRate[];
|
|
@@ -88,6 +90,8 @@ export function useChangeBookingPriceSummary({
|
|
|
88
90
|
feeLineItems,
|
|
89
91
|
changeFlowBookedFeeUnitByNormalizedLabel,
|
|
90
92
|
addOnSelections,
|
|
93
|
+
originalAddOnSelections,
|
|
94
|
+
originalProductOptionId,
|
|
91
95
|
addOns,
|
|
92
96
|
quantities,
|
|
93
97
|
pricing,
|
|
@@ -113,6 +117,9 @@ export function useChangeBookingPriceSummary({
|
|
|
113
117
|
ticketLineItemsForChangeFlowDisplay,
|
|
114
118
|
effectiveSubtotal,
|
|
115
119
|
feeLineItemsWithAddOns,
|
|
120
|
+
promoEligibleSubtotal,
|
|
121
|
+
addOnTotal,
|
|
122
|
+
originalAddOnSubtotal,
|
|
116
123
|
} = useChangeBookingProtectedPricing({
|
|
117
124
|
selectedAvailability,
|
|
118
125
|
selectedReturnOption,
|
|
@@ -132,8 +139,13 @@ export function useChangeBookingPriceSummary({
|
|
|
132
139
|
feeLineItems,
|
|
133
140
|
changeFlowBookedFeeUnitByNormalizedLabel,
|
|
134
141
|
addOnSelections,
|
|
142
|
+
originalAddOnSelections,
|
|
135
143
|
addOns,
|
|
136
144
|
});
|
|
145
|
+
const promoEligibleSubtotalExcludingCancellation = Math.max(
|
|
146
|
+
0,
|
|
147
|
+
promoEligibleSubtotal - cancellationPolicyFee,
|
|
148
|
+
);
|
|
137
149
|
|
|
138
150
|
const {
|
|
139
151
|
effectivePromoDiscountAmount,
|
|
@@ -151,6 +163,10 @@ export function useChangeBookingPriceSummary({
|
|
|
151
163
|
ticketLineItemsForChangeFlowDisplay,
|
|
152
164
|
feeLineItems,
|
|
153
165
|
effectiveSubtotal,
|
|
166
|
+
promoEligibleSubtotal: promoEligibleSubtotalExcludingCancellation,
|
|
167
|
+
addOnTotal,
|
|
168
|
+
originalAddOnSubtotal,
|
|
169
|
+
originalProductOptionId,
|
|
154
170
|
lockedPromoCode,
|
|
155
171
|
originalReceipt,
|
|
156
172
|
isTaxIncludedInPrice,
|
|
@@ -21,12 +21,29 @@ interface UseChangeBookingPromoDiscountParams {
|
|
|
21
21
|
ticketLineItemsForChangeFlowDisplay: OrderSummaryTicketLine[];
|
|
22
22
|
feeLineItems: OrderSummaryFeeLine[];
|
|
23
23
|
effectiveSubtotal: number;
|
|
24
|
+
promoEligibleSubtotal?: number;
|
|
25
|
+
addOnTotal?: number;
|
|
26
|
+
originalAddOnSubtotal?: number;
|
|
27
|
+
originalProductOptionId?: string | null;
|
|
24
28
|
lockedPromoCode: string | null;
|
|
25
29
|
originalReceipt: ChangeBookingFlowProps['originalReceipt'];
|
|
26
30
|
isTaxIncludedInPrice: boolean;
|
|
27
31
|
pricingConfig: PricingConfig | null;
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
function originalReceiptGrossSubtotalBeforePromo(
|
|
35
|
+
originalReceipt: ChangeBookingFlowProps['originalReceipt'],
|
|
36
|
+
): number {
|
|
37
|
+
const chargeTypes = new Set(['TICKET', 'FEE', 'RETURN_OPTION']);
|
|
38
|
+
return (originalReceipt?.lineItems ?? []).reduce((sum, line) => {
|
|
39
|
+
const amount = Number(line.amount) || 0;
|
|
40
|
+
if (amount <= 0) return sum;
|
|
41
|
+
const type = String(line.type ?? '').trim().toUpperCase();
|
|
42
|
+
if (!chargeTypes.has(type)) return sum;
|
|
43
|
+
return sum + amount;
|
|
44
|
+
}, 0);
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
export function useChangeBookingPromoDiscount({
|
|
31
48
|
appliedPromoCode,
|
|
32
49
|
selectedAvailability,
|
|
@@ -39,6 +56,10 @@ export function useChangeBookingPromoDiscount({
|
|
|
39
56
|
ticketLineItemsForChangeFlowDisplay,
|
|
40
57
|
feeLineItems,
|
|
41
58
|
effectiveSubtotal,
|
|
59
|
+
promoEligibleSubtotal = effectiveSubtotal,
|
|
60
|
+
addOnTotal = Math.max(0, effectiveSubtotal - promoEligibleSubtotal),
|
|
61
|
+
originalAddOnSubtotal = 0,
|
|
62
|
+
originalProductOptionId,
|
|
42
63
|
lockedPromoCode,
|
|
43
64
|
originalReceipt,
|
|
44
65
|
isTaxIncludedInPrice,
|
|
@@ -62,15 +83,36 @@ export function useChangeBookingPromoDiscount({
|
|
|
62
83
|
selectedAvailability: null as Availability | null,
|
|
63
84
|
ticketLineItems: [] as Array<{ category: string; qty: number; itemTotal: number }>,
|
|
64
85
|
effectiveSubtotal: 0,
|
|
86
|
+
promoEligibleSubtotal: 0,
|
|
65
87
|
displayedProductFeeTotal: 0,
|
|
66
88
|
appliedPromoCode: null as string | null,
|
|
67
|
-
changeBookingPromo: null as {
|
|
89
|
+
changeBookingPromo: null as {
|
|
90
|
+
priorAmount?: number;
|
|
91
|
+
originalFullSubtotal?: number;
|
|
92
|
+
originalAddOnSubtotal?: number;
|
|
93
|
+
newFullSubtotal?: number;
|
|
94
|
+
newAddOnSubtotal?: number;
|
|
95
|
+
} | null,
|
|
68
96
|
});
|
|
69
97
|
|
|
98
|
+
const originalFullSubtotalBeforePromo = useMemo(
|
|
99
|
+
() => originalReceiptGrossSubtotalBeforePromo(originalReceipt),
|
|
100
|
+
[originalReceipt],
|
|
101
|
+
);
|
|
102
|
+
|
|
70
103
|
const selectedAvailabilityOptionId = useMemo(
|
|
71
104
|
() => (selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : ''),
|
|
72
105
|
[selectedAvailability],
|
|
73
106
|
);
|
|
107
|
+
const includeLegacyPromoRetentionContext = useMemo(() => {
|
|
108
|
+
const originalOptionId = originalProductOptionId?.trim();
|
|
109
|
+
return Boolean(
|
|
110
|
+
lockedPromoCode &&
|
|
111
|
+
originalOptionId &&
|
|
112
|
+
selectedAvailabilityOptionId &&
|
|
113
|
+
selectedAvailabilityOptionId === originalOptionId,
|
|
114
|
+
);
|
|
115
|
+
}, [lockedPromoCode, originalProductOptionId, selectedAvailabilityOptionId]);
|
|
74
116
|
|
|
75
117
|
const promoDiscountFetchKey = useMemo(() => {
|
|
76
118
|
if (!appliedPromoCode || !selectedAvailability || totalQuantity === 0) return '';
|
|
@@ -92,6 +134,10 @@ export function useChangeBookingPromoDiscount({
|
|
|
92
134
|
.join('|'),
|
|
93
135
|
String(Math.round(feeLineItems.reduce((sum, fee) => sum + fee.totalAmount, 0) * 100)),
|
|
94
136
|
String(Math.round(effectiveSubtotal * 100)),
|
|
137
|
+
String(Math.round(promoEligibleSubtotal * 100)),
|
|
138
|
+
String(Math.round(addOnTotal * 100)),
|
|
139
|
+
includeLegacyPromoRetentionContext ? String(Math.round(originalFullSubtotalBeforePromo * 100)) : '',
|
|
140
|
+
includeLegacyPromoRetentionContext ? String(Math.round(originalAddOnSubtotal * 100)) : '',
|
|
95
141
|
lockedPromoCode ? String(Math.round((originalReceipt?.promoAmount ?? 0) * 100)) : '',
|
|
96
142
|
].join('::');
|
|
97
143
|
}, [
|
|
@@ -107,6 +153,11 @@ export function useChangeBookingPromoDiscount({
|
|
|
107
153
|
ticketLineItemsForChangeFlowDisplay,
|
|
108
154
|
feeLineItems,
|
|
109
155
|
effectiveSubtotal,
|
|
156
|
+
promoEligibleSubtotal,
|
|
157
|
+
addOnTotal,
|
|
158
|
+
includeLegacyPromoRetentionContext,
|
|
159
|
+
originalFullSubtotalBeforePromo,
|
|
160
|
+
originalAddOnSubtotal,
|
|
110
161
|
lockedPromoCode,
|
|
111
162
|
originalReceipt?.promoAmount,
|
|
112
163
|
]);
|
|
@@ -119,10 +170,17 @@ export function useChangeBookingPromoDiscount({
|
|
|
119
170
|
itemTotal: line.itemTotal,
|
|
120
171
|
})),
|
|
121
172
|
effectiveSubtotal,
|
|
173
|
+
promoEligibleSubtotal,
|
|
122
174
|
displayedProductFeeTotal: feeLineItems.reduce((sum, fee) => sum + fee.totalAmount, 0),
|
|
123
175
|
appliedPromoCode,
|
|
124
176
|
changeBookingPromo: lockedPromoCode
|
|
125
|
-
? {
|
|
177
|
+
? {
|
|
178
|
+
priorAmount: originalReceipt?.promoAmount,
|
|
179
|
+
originalFullSubtotal: includeLegacyPromoRetentionContext ? originalFullSubtotalBeforePromo : undefined,
|
|
180
|
+
originalAddOnSubtotal: includeLegacyPromoRetentionContext ? originalAddOnSubtotal : undefined,
|
|
181
|
+
newFullSubtotal: includeLegacyPromoRetentionContext ? effectiveSubtotal : undefined,
|
|
182
|
+
newAddOnSubtotal: includeLegacyPromoRetentionContext ? addOnTotal : undefined,
|
|
183
|
+
}
|
|
126
184
|
: null,
|
|
127
185
|
};
|
|
128
186
|
|
|
@@ -143,6 +201,7 @@ export function useChangeBookingPromoDiscount({
|
|
|
143
201
|
ticketLineItems: lines,
|
|
144
202
|
effectiveSubtotal: sub,
|
|
145
203
|
displayedProductFeeTotal,
|
|
204
|
+
promoEligibleSubtotal: promoSub,
|
|
146
205
|
appliedPromoCode: code,
|
|
147
206
|
changeBookingPromo,
|
|
148
207
|
} = promoDiscountParamsRef.current;
|
|
@@ -169,10 +228,16 @@ export function useChangeBookingPromoDiscount({
|
|
|
169
228
|
priorPromoDiscountAmount: changeBookingPromo.priorAmount,
|
|
170
229
|
displayedTicketLines: lines,
|
|
171
230
|
displayedProductFeeTotal,
|
|
231
|
+
promoEligibleSubtotal: promoSub,
|
|
232
|
+
legacyPromoOriginalFullSubtotal: changeBookingPromo.originalFullSubtotal,
|
|
233
|
+
legacyPromoOriginalAddOnSubtotal: changeBookingPromo.originalAddOnSubtotal,
|
|
234
|
+
legacyPromoNewFullSubtotal: changeBookingPromo.newFullSubtotal,
|
|
235
|
+
legacyPromoNewAddOnSubtotal: changeBookingPromo.newAddOnSubtotal,
|
|
172
236
|
}
|
|
173
237
|
: {
|
|
174
238
|
displayedTicketLines: lines,
|
|
175
239
|
displayedProductFeeTotal,
|
|
240
|
+
promoEligibleSubtotal: promoSub,
|
|
176
241
|
},
|
|
177
242
|
)
|
|
178
243
|
.then((res) => {
|
|
@@ -15,6 +15,23 @@ import { normalizeLineLabelForCompare } from './change-booking-flow-helpers';
|
|
|
15
15
|
|
|
16
16
|
type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
|
|
17
17
|
|
|
18
|
+
function addOnSelectionTotal(selections: AddOnSelection[] | null | undefined, addOns: AddOn[]): number {
|
|
19
|
+
let sum = 0;
|
|
20
|
+
for (const sel of selections ?? []) {
|
|
21
|
+
const addOn = addOns.find((a) => a.addOnId === sel.addOnId);
|
|
22
|
+
if (!addOn) continue;
|
|
23
|
+
const basePrice = addOn.price ?? 0;
|
|
24
|
+
const hasVariant =
|
|
25
|
+
(addOn.variantType === 'single_choice' || addOn.variantType === 'multi_quantity') &&
|
|
26
|
+
sel.variantId;
|
|
27
|
+
const variantAdjustment = hasVariant
|
|
28
|
+
? (addOn.variants?.find((v) => v.id === sel.variantId)?.priceAdjustment ?? 0)
|
|
29
|
+
: 0;
|
|
30
|
+
sum += (basePrice + variantAdjustment) * (sel.quantity ?? 1);
|
|
31
|
+
}
|
|
32
|
+
return sum;
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
interface UseChangeBookingProtectedPricingParams {
|
|
19
36
|
selectedAvailability: Availability | null;
|
|
20
37
|
selectedReturnOption: ReturnOption | null;
|
|
@@ -34,6 +51,7 @@ interface UseChangeBookingProtectedPricingParams {
|
|
|
34
51
|
feeLineItems: OrderSummaryFeeLine[];
|
|
35
52
|
changeFlowBookedFeeUnitByNormalizedLabel: Map<string, number>;
|
|
36
53
|
addOnSelections: AddOnSelection[];
|
|
54
|
+
originalAddOnSelections?: AddOnSelection[] | null;
|
|
37
55
|
addOns: AddOn[];
|
|
38
56
|
}
|
|
39
57
|
|
|
@@ -56,6 +74,7 @@ export function useChangeBookingProtectedPricing({
|
|
|
56
74
|
feeLineItems,
|
|
57
75
|
changeFlowBookedFeeUnitByNormalizedLabel,
|
|
58
76
|
addOnSelections,
|
|
77
|
+
originalAddOnSelections,
|
|
59
78
|
addOns,
|
|
60
79
|
}: UseChangeBookingProtectedPricingParams) {
|
|
61
80
|
const changeFlowProtectedTicketSubtotal = useMemo(() => {
|
|
@@ -87,22 +106,14 @@ export function useChangeBookingProtectedPricing({
|
|
|
87
106
|
changeFlowProtectedReceiptPricing,
|
|
88
107
|
]);
|
|
89
108
|
|
|
90
|
-
const addOnTotal = useMemo(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
sel.variantId;
|
|
99
|
-
const variantAdjustment = hasVariant
|
|
100
|
-
? (addOn.variants?.find((v) => v.id === sel.variantId)?.priceAdjustment ?? 0)
|
|
101
|
-
: 0;
|
|
102
|
-
sum += (basePrice + variantAdjustment) * (sel.quantity ?? 1);
|
|
103
|
-
}
|
|
104
|
-
return sum;
|
|
105
|
-
}, [addOnSelections, addOns]);
|
|
109
|
+
const addOnTotal = useMemo(
|
|
110
|
+
() => addOnSelectionTotal(addOnSelections, addOns),
|
|
111
|
+
[addOnSelections, addOns],
|
|
112
|
+
);
|
|
113
|
+
const originalAddOnSubtotal = useMemo(
|
|
114
|
+
() => addOnSelectionTotal(originalAddOnSelections, addOns),
|
|
115
|
+
[originalAddOnSelections, addOns],
|
|
116
|
+
);
|
|
106
117
|
|
|
107
118
|
const currentTicketSubtotal = useMemo(
|
|
108
119
|
() => ticketLineItems.reduce((sum, line) => sum + Math.max(0, Number(line.itemTotal) || 0), 0),
|
|
@@ -220,6 +231,7 @@ export function useChangeBookingProtectedPricing({
|
|
|
220
231
|
changeFlowProtectedReturnAdjustment
|
|
221
232
|
: subtotal;
|
|
222
233
|
const effectiveSubtotal = effectiveSubtotalBeforeAddOns + addOnTotal;
|
|
234
|
+
const promoEligibleSubtotal = effectiveSubtotalBeforeAddOns;
|
|
223
235
|
|
|
224
236
|
const feeLineItemsWithAddOns = useMemo(() => {
|
|
225
237
|
const addOnLines = addOnSelections
|
|
@@ -251,6 +263,9 @@ export function useChangeBookingProtectedPricing({
|
|
|
251
263
|
checkoutReturnLineAmount,
|
|
252
264
|
ticketLineItemsForChangeFlowDisplay,
|
|
253
265
|
effectiveSubtotal,
|
|
266
|
+
promoEligibleSubtotal,
|
|
267
|
+
addOnTotal,
|
|
268
|
+
originalAddOnSubtotal,
|
|
254
269
|
feeLineItemsWithAddOns,
|
|
255
270
|
};
|
|
256
271
|
}
|
|
@@ -246,6 +246,7 @@ export function useStandardBookingPriceSummary({
|
|
|
246
246
|
const checkoutReturnLineAmount = returnPriceAdjustment;
|
|
247
247
|
const effectiveSubtotalBeforeAddOns = subtotal;
|
|
248
248
|
const effectiveSubtotal = effectiveSubtotalBeforeAddOns + addOnTotal;
|
|
249
|
+
const promoEligibleSubtotal = Math.max(0, effectiveSubtotalBeforeAddOns - cancellationPolicyFee);
|
|
249
250
|
|
|
250
251
|
const feeLineItemsWithAddOns = useMemo(() => {
|
|
251
252
|
const addOnLines = addOnSelections
|
|
@@ -373,6 +374,7 @@ export function useStandardBookingPriceSummary({
|
|
|
373
374
|
ticketLineItems,
|
|
374
375
|
feeLineItems,
|
|
375
376
|
effectiveSubtotal,
|
|
377
|
+
promoEligibleSubtotal,
|
|
376
378
|
isTaxIncludedInPrice,
|
|
377
379
|
pricingConfig,
|
|
378
380
|
});
|
|
@@ -25,6 +25,7 @@ interface UseStandardBookingPromoDiscountParams {
|
|
|
25
25
|
ticketLineItems: OrderSummaryTicketLine[];
|
|
26
26
|
feeLineItems: OrderSummaryFeeLine[];
|
|
27
27
|
effectiveSubtotal: number;
|
|
28
|
+
promoEligibleSubtotal?: number;
|
|
28
29
|
isTaxIncludedInPrice: boolean;
|
|
29
30
|
pricingConfig: PricingConfig | null;
|
|
30
31
|
}
|
|
@@ -41,6 +42,7 @@ export function useStandardBookingPromoDiscount({
|
|
|
41
42
|
ticketLineItems,
|
|
42
43
|
feeLineItems,
|
|
43
44
|
effectiveSubtotal,
|
|
45
|
+
promoEligibleSubtotal = effectiveSubtotal,
|
|
44
46
|
isTaxIncludedInPrice,
|
|
45
47
|
pricingConfig,
|
|
46
48
|
}: UseStandardBookingPromoDiscountParams) {
|
|
@@ -62,6 +64,7 @@ export function useStandardBookingPromoDiscount({
|
|
|
62
64
|
selectedAvailability: null as Availability | null,
|
|
63
65
|
ticketLineItems: [] as Array<{ category: string; qty: number; itemTotal: number }>,
|
|
64
66
|
effectiveSubtotal: 0,
|
|
67
|
+
promoEligibleSubtotal: 0,
|
|
65
68
|
displayedProductFeeTotal: 0,
|
|
66
69
|
appliedPromoCode: null as string | null,
|
|
67
70
|
});
|
|
@@ -91,6 +94,7 @@ export function useStandardBookingPromoDiscount({
|
|
|
91
94
|
.join('|'),
|
|
92
95
|
String(Math.round(feeLineItems.reduce((sum, fee) => sum + fee.totalAmount, 0) * 100)),
|
|
93
96
|
String(Math.round(effectiveSubtotal * 100)),
|
|
97
|
+
String(Math.round(promoEligibleSubtotal * 100)),
|
|
94
98
|
].join('::');
|
|
95
99
|
}, [
|
|
96
100
|
appliedPromoCode,
|
|
@@ -105,6 +109,7 @@ export function useStandardBookingPromoDiscount({
|
|
|
105
109
|
ticketLineItems,
|
|
106
110
|
feeLineItems,
|
|
107
111
|
effectiveSubtotal,
|
|
112
|
+
promoEligibleSubtotal,
|
|
108
113
|
]);
|
|
109
114
|
|
|
110
115
|
promoDiscountParamsRef.current = {
|
|
@@ -115,6 +120,7 @@ export function useStandardBookingPromoDiscount({
|
|
|
115
120
|
itemTotal: line.itemTotal,
|
|
116
121
|
})),
|
|
117
122
|
effectiveSubtotal,
|
|
123
|
+
promoEligibleSubtotal,
|
|
118
124
|
displayedProductFeeTotal: feeLineItems.reduce((sum, fee) => sum + fee.totalAmount, 0),
|
|
119
125
|
appliedPromoCode,
|
|
120
126
|
};
|
|
@@ -136,6 +142,7 @@ export function useStandardBookingPromoDiscount({
|
|
|
136
142
|
ticketLineItems: lines,
|
|
137
143
|
effectiveSubtotal: sub,
|
|
138
144
|
displayedProductFeeTotal,
|
|
145
|
+
promoEligibleSubtotal: promoSub,
|
|
139
146
|
appliedPromoCode: code,
|
|
140
147
|
} = promoDiscountParamsRef.current;
|
|
141
148
|
if (!code || !sel) return;
|
|
@@ -160,6 +167,7 @@ export function useStandardBookingPromoDiscount({
|
|
|
160
167
|
totalCapacity: sel.totalCapacity,
|
|
161
168
|
displayedTicketLines: lines,
|
|
162
169
|
displayedProductFeeTotal,
|
|
170
|
+
promoEligibleSubtotal: promoSub,
|
|
163
171
|
},
|
|
164
172
|
)
|
|
165
173
|
.then((res) => {
|
package/src/lib/booking-api.ts
CHANGED
|
@@ -980,6 +980,12 @@ export interface GetPromoDiscountBookingChangeOptions {
|
|
|
980
980
|
displayedTicketLines?: Array<{ category: string; qty: number; itemTotal: number }>;
|
|
981
981
|
/** Displayed product fee total from the cart, excluding add-ons, return, and cancellation lines. */
|
|
982
982
|
displayedProductFeeTotal?: number;
|
|
983
|
+
/** Promo-eligible subtotal excluding add-ons; normal promos use this, gift cards/vouchers use subtotal. */
|
|
984
|
+
promoEligibleSubtotal?: number;
|
|
985
|
+
legacyPromoOriginalFullSubtotal?: number;
|
|
986
|
+
legacyPromoOriginalAddOnSubtotal?: number;
|
|
987
|
+
legacyPromoNewFullSubtotal?: number;
|
|
988
|
+
legacyPromoNewAddOnSubtotal?: number;
|
|
983
989
|
}
|
|
984
990
|
|
|
985
991
|
export async function getPromoDiscount(
|
|
@@ -1004,6 +1010,13 @@ export async function getPromoDiscount(
|
|
|
1004
1010
|
});
|
|
1005
1011
|
if (dateTime) params.set('dateTime', dateTime);
|
|
1006
1012
|
if (subtotal != null && subtotal > 0) params.set('subtotal', String(subtotal));
|
|
1013
|
+
if (
|
|
1014
|
+
bookingChange?.promoEligibleSubtotal != null &&
|
|
1015
|
+
Number.isFinite(bookingChange.promoEligibleSubtotal) &&
|
|
1016
|
+
bookingChange.promoEligibleSubtotal >= 0
|
|
1017
|
+
) {
|
|
1018
|
+
params.set('promoEligibleSubtotal', String(bookingChange.promoEligibleSubtotal));
|
|
1019
|
+
}
|
|
1007
1020
|
if (
|
|
1008
1021
|
bookingChange?.availabilityCount != null &&
|
|
1009
1022
|
Number.isFinite(bookingChange.availabilityCount)
|
|
@@ -1039,6 +1052,34 @@ export async function getPromoDiscount(
|
|
|
1039
1052
|
) {
|
|
1040
1053
|
params.set('priorPromoDiscountAmount', String(bookingChange.priorPromoDiscountAmount));
|
|
1041
1054
|
}
|
|
1055
|
+
if (
|
|
1056
|
+
bookingChange?.legacyPromoOriginalFullSubtotal != null &&
|
|
1057
|
+
Number.isFinite(bookingChange.legacyPromoOriginalFullSubtotal) &&
|
|
1058
|
+
bookingChange.legacyPromoOriginalFullSubtotal >= 0
|
|
1059
|
+
) {
|
|
1060
|
+
params.set('legacyPromoOriginalFullSubtotal', String(bookingChange.legacyPromoOriginalFullSubtotal));
|
|
1061
|
+
}
|
|
1062
|
+
if (
|
|
1063
|
+
bookingChange?.legacyPromoOriginalAddOnSubtotal != null &&
|
|
1064
|
+
Number.isFinite(bookingChange.legacyPromoOriginalAddOnSubtotal) &&
|
|
1065
|
+
bookingChange.legacyPromoOriginalAddOnSubtotal >= 0
|
|
1066
|
+
) {
|
|
1067
|
+
params.set('legacyPromoOriginalAddOnSubtotal', String(bookingChange.legacyPromoOriginalAddOnSubtotal));
|
|
1068
|
+
}
|
|
1069
|
+
if (
|
|
1070
|
+
bookingChange?.legacyPromoNewFullSubtotal != null &&
|
|
1071
|
+
Number.isFinite(bookingChange.legacyPromoNewFullSubtotal) &&
|
|
1072
|
+
bookingChange.legacyPromoNewFullSubtotal >= 0
|
|
1073
|
+
) {
|
|
1074
|
+
params.set('legacyPromoNewFullSubtotal', String(bookingChange.legacyPromoNewFullSubtotal));
|
|
1075
|
+
}
|
|
1076
|
+
if (
|
|
1077
|
+
bookingChange?.legacyPromoNewAddOnSubtotal != null &&
|
|
1078
|
+
Number.isFinite(bookingChange.legacyPromoNewAddOnSubtotal) &&
|
|
1079
|
+
bookingChange.legacyPromoNewAddOnSubtotal >= 0
|
|
1080
|
+
) {
|
|
1081
|
+
params.set('legacyPromoNewAddOnSubtotal', String(bookingChange.legacyPromoNewAddOnSubtotal));
|
|
1082
|
+
}
|
|
1042
1083
|
const res = await fetchBookingGetWithRetry(`${API_BASE}/1/get-promo-discount?${params}`);
|
|
1043
1084
|
if (!res.ok) {
|
|
1044
1085
|
const err = await res.json();
|