@ticketboothapp/booking 1.2.129 → 1.2.131
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/pricing-v2-checkout-summary.ts +28 -18
- package/src/components/booking/useStandardBookingPriceSummary.ts +16 -5
- package/src/components/booking/useStandardBookingPromoDiscount.ts +5 -3
- package/src/lib/booking-api.ts +2 -0
- package/test/change-booking-helpers.test.ts +53 -0
package/package.json
CHANGED
|
@@ -48,27 +48,37 @@ function isPaymentCreditType(type?: string | null): boolean {
|
|
|
48
48
|
return normalized === "GIFT_CARD" || normalized === "PAYMENT_CREDIT";
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function
|
|
52
|
-
return
|
|
51
|
+
function isPaymentCreditQuoteLine(line: PricingV2QuoteLineSnapshot): boolean {
|
|
52
|
+
return isPaymentCreditType(line.type);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function
|
|
56
|
-
const
|
|
57
|
-
|
|
55
|
+
function isPostTaxDiscountQuoteLine(line: PricingV2QuoteLineSnapshot): boolean {
|
|
56
|
+
const discountBehavior = String(line.discountBehavior ?? "").trim().toUpperCase();
|
|
57
|
+
const source = String(line.source ?? "").trim().toUpperCase();
|
|
58
|
+
return discountBehavior === "POST_TAX_DISCOUNT" || source === "VOUCHER";
|
|
59
|
+
}
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
function moveSettlementLinesAfterTax(
|
|
62
|
+
lines: PricingV2QuoteLineSnapshot[],
|
|
63
|
+
): PricingV2QuoteLineSnapshot[] {
|
|
64
|
+
const settlementLines = lines.filter(
|
|
65
|
+
(line) => isPaymentCreditQuoteLine(line) || isPostTaxDiscountQuoteLine(line),
|
|
66
|
+
);
|
|
67
|
+
if (settlementLines.length === 0) return lines;
|
|
68
|
+
|
|
69
|
+
const nonSettlementLines = lines.filter(
|
|
70
|
+
(line) => !isPaymentCreditQuoteLine(line) && !isPostTaxDiscountQuoteLine(line),
|
|
62
71
|
);
|
|
72
|
+
const taxIndex = nonSettlementLines.findIndex((line) => normalizedLineType(line.type) === "TAX");
|
|
63
73
|
|
|
64
74
|
if (taxIndex < 0) {
|
|
65
|
-
return [...
|
|
75
|
+
return [...nonSettlementLines, ...settlementLines];
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
return [
|
|
69
|
-
...
|
|
70
|
-
...
|
|
71
|
-
...
|
|
79
|
+
...nonSettlementLines.slice(0, taxIndex + 1),
|
|
80
|
+
...settlementLines,
|
|
81
|
+
...nonSettlementLines.slice(taxIndex + 1),
|
|
72
82
|
];
|
|
73
83
|
}
|
|
74
84
|
|
|
@@ -112,11 +122,10 @@ export function buildCheckoutModalSummaryFromPricingV2Quote(
|
|
|
112
122
|
const total = pricingV2QuoteTotal(quote);
|
|
113
123
|
if (total == null) return undefined;
|
|
114
124
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const lines = movePaymentCreditLinesAfterTax(mappedLines);
|
|
125
|
+
const quoteLines = quote?.lines ?? [];
|
|
126
|
+
const lines = moveSettlementLinesAfterTax(quoteLines)
|
|
127
|
+
.map((line, index) => priceSummaryLineFromQuoteLine(line, index))
|
|
128
|
+
.filter((line): line is PriceSummaryLine => line != null);
|
|
120
129
|
|
|
121
130
|
if (lines.length === 0) return undefined;
|
|
122
131
|
|
|
@@ -156,8 +165,9 @@ export function buildCheckoutModalSummaryFromPricingV2Quote(
|
|
|
156
165
|
0,
|
|
157
166
|
finiteNumber(quote?.paymentCreditTotal) ?? 0,
|
|
158
167
|
);
|
|
168
|
+
const hasPostTaxDiscount = quoteLines.some(isPostTaxDiscountQuoteLine);
|
|
159
169
|
const subtotal = finiteNumber(
|
|
160
|
-
paymentCreditTotal > 0
|
|
170
|
+
paymentCreditTotal > 0 || hasPostTaxDiscount
|
|
161
171
|
? quote?.taxableSubtotal ??
|
|
162
172
|
quote?.grossSubtotal ??
|
|
163
173
|
quote?.netSubtotalBeforeTax ??
|
|
@@ -112,10 +112,21 @@ export function useStandardBookingPriceSummary({
|
|
|
112
112
|
return Math.max(0, selectedReturnOption.vacancies ?? 0);
|
|
113
113
|
}, [selectedReturnOption]);
|
|
114
114
|
|
|
115
|
+
const pricingAvailability = useMemo(() => {
|
|
116
|
+
if (!selectedAvailability || !selectedReturnOption?.rates?.length) return selectedAvailability;
|
|
117
|
+
const outboundVacancies = Math.max(0, selectedAvailability.vacancies ?? 0);
|
|
118
|
+
const returnVacancies = Math.max(0, selectedReturnOption.vacancies ?? 0);
|
|
119
|
+
return {
|
|
120
|
+
...selectedAvailability,
|
|
121
|
+
vacancies: Math.min(outboundVacancies, returnVacancies),
|
|
122
|
+
rates: selectedReturnOption.rates,
|
|
123
|
+
};
|
|
124
|
+
}, [selectedAvailability, selectedReturnOption?.rates, selectedReturnOption?.vacancies]);
|
|
125
|
+
|
|
115
126
|
const pricing = useMemo(
|
|
116
127
|
() =>
|
|
117
128
|
buildPricingFromAvailability(
|
|
118
|
-
|
|
129
|
+
pricingAvailability,
|
|
119
130
|
activeOptions,
|
|
120
131
|
precomputedPricesByOption,
|
|
121
132
|
currency,
|
|
@@ -124,7 +135,7 @@ export function useStandardBookingPriceSummary({
|
|
|
124
135
|
isSimplifiedPricingView,
|
|
125
136
|
),
|
|
126
137
|
[
|
|
127
|
-
|
|
138
|
+
pricingAvailability,
|
|
128
139
|
activeOptions,
|
|
129
140
|
precomputedPricesByOption,
|
|
130
141
|
currency,
|
|
@@ -145,7 +156,7 @@ export function useStandardBookingPriceSummary({
|
|
|
145
156
|
const selectedOption = activeOptions.find(
|
|
146
157
|
(option) =>
|
|
147
158
|
option.optionId ===
|
|
148
|
-
(
|
|
159
|
+
(pricingAvailability ? getAvailabilityOptionId(pricingAvailability) : ''),
|
|
149
160
|
);
|
|
150
161
|
const basePriceCAD = selectedOption?.pricing?.[category.toUpperCase()] ?? 0;
|
|
151
162
|
return computePriceBreakdown(
|
|
@@ -160,7 +171,7 @@ export function useStandardBookingPriceSummary({
|
|
|
160
171
|
isSimplifiedPricingView,
|
|
161
172
|
);
|
|
162
173
|
},
|
|
163
|
-
[pricingConfig, activeOptions,
|
|
174
|
+
[pricingConfig, activeOptions, pricingAvailability, currency, hasFees, isSimplifiedPricingView],
|
|
164
175
|
);
|
|
165
176
|
|
|
166
177
|
const orderSummary = useMemo(
|
|
@@ -352,7 +363,7 @@ export function useStandardBookingPriceSummary({
|
|
|
352
363
|
totalPrice,
|
|
353
364
|
} = useStandardBookingPromoDiscount({
|
|
354
365
|
appliedPromoCode,
|
|
355
|
-
selectedAvailability,
|
|
366
|
+
selectedAvailability: pricingAvailability,
|
|
356
367
|
totalQuantity,
|
|
357
368
|
productCompanyId,
|
|
358
369
|
envCompanyId,
|
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
import type { Currency } from './CurrencySwitcher';
|
|
12
12
|
import { getAvailabilityOptionId } from './standard-booking-availability';
|
|
13
13
|
|
|
14
|
+
const normalizeMoneyZero = (value: number) => (Math.abs(value) < 0.005 ? 0 : value);
|
|
15
|
+
|
|
14
16
|
interface UseStandardBookingPromoDiscountParams {
|
|
15
17
|
appliedPromoCode: string | null;
|
|
16
18
|
selectedAvailability: Availability | null;
|
|
@@ -188,9 +190,9 @@ export function useStandardBookingPromoDiscount({
|
|
|
188
190
|
const taxOnSubtotal = isTaxIncludedInPrice ? 0 : effectiveSubtotal * (pricingConfig?.taxRate ?? 0);
|
|
189
191
|
const effectiveTax =
|
|
190
192
|
effectivePromoDiscountAmount > 0 && !isGiftCard && !isVoucher
|
|
191
|
-
? (effectiveSubtotal - effectivePromoDiscountAmount) * (pricingConfig?.taxRate ?? 0)
|
|
192
|
-
: taxOnSubtotal;
|
|
193
|
-
const totalPrice = effectiveSubtotal + effectiveTax - effectivePromoDiscountAmount;
|
|
193
|
+
? normalizeMoneyZero((effectiveSubtotal - effectivePromoDiscountAmount) * (pricingConfig?.taxRate ?? 0))
|
|
194
|
+
: normalizeMoneyZero(taxOnSubtotal);
|
|
195
|
+
const totalPrice = normalizeMoneyZero(effectiveSubtotal + effectiveTax - effectivePromoDiscountAmount);
|
|
194
196
|
|
|
195
197
|
return {
|
|
196
198
|
promoDiscountAmount,
|
package/src/lib/booking-api.ts
CHANGED
|
@@ -1669,6 +1669,8 @@ export interface ReturnOption {
|
|
|
1669
1669
|
bookedCapacity?: number;
|
|
1670
1670
|
pricesByCategory?: { retailPrices: Array<{ category: string; price: number }> };
|
|
1671
1671
|
priceAdjustmentByCurrency?: Record<string, number>;
|
|
1672
|
+
/** Ticket rates priced using this specific return option's capacity floor. */
|
|
1673
|
+
rates?: AvailabilityRate[];
|
|
1672
1674
|
returnLocation: string;
|
|
1673
1675
|
mostPopular?: boolean;
|
|
1674
1676
|
}
|
|
@@ -218,6 +218,59 @@ test('maps Pricing V2 gift cards as payment credits after tax', () => {
|
|
|
218
218
|
);
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
+
test('maps Pricing V2 post-tax vouchers after tax with full taxable subtotal', () => {
|
|
222
|
+
const summary = buildCheckoutModalSummaryFromPricingV2Quote(
|
|
223
|
+
{
|
|
224
|
+
currency: 'CAD',
|
|
225
|
+
grossSubtotal: 629.18,
|
|
226
|
+
discountTotal: -455.11,
|
|
227
|
+
paymentCreditTotal: 0,
|
|
228
|
+
netSubtotalBeforeTax: 174.07,
|
|
229
|
+
taxableSubtotal: 629.18,
|
|
230
|
+
taxAmount: 53.48,
|
|
231
|
+
payableTotal: 227.55,
|
|
232
|
+
amountToCharge: 227.55,
|
|
233
|
+
lines: [
|
|
234
|
+
{
|
|
235
|
+
lineId: 'ticket_1',
|
|
236
|
+
type: 'TICKET',
|
|
237
|
+
label: 'ADULT',
|
|
238
|
+
amount: 581.21,
|
|
239
|
+
quantity: 3,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
lineId: 'fee_1',
|
|
243
|
+
type: 'FEE',
|
|
244
|
+
label: 'Moraine Lake Road Access Fee',
|
|
245
|
+
amount: 47.97,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
lineId: 'voucher_1',
|
|
249
|
+
type: 'DISCOUNT',
|
|
250
|
+
label: "Les Clefs d'Or",
|
|
251
|
+
amount: -455.11,
|
|
252
|
+
discountBehavior: 'POST_TAX_DISCOUNT',
|
|
253
|
+
source: 'VOUCHER',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
lineId: 'tax_1',
|
|
257
|
+
type: 'TAX',
|
|
258
|
+
label: 'Taxes and fees',
|
|
259
|
+
amount: 53.48,
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
'Rounding',
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
assert.equal(summary?.subtotal, 629.18);
|
|
267
|
+
assert.equal(summary?.fullTotalAmount, 227.55);
|
|
268
|
+
assert.deepEqual(
|
|
269
|
+
summary?.lines.map((line) => (line.kind === 'line' ? line.type : 'TICKET')),
|
|
270
|
+
['TICKET', 'FEE', 'TAX', 'DISCOUNT'],
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
221
274
|
test('builds paid checkout modal data with trimmed customer and change totals', () => {
|
|
222
275
|
const modalData = buildChangeBookingPaidCheckoutModalData({
|
|
223
276
|
bookingReference: 'bookRef_ABC-123',
|