@ticketboothapp/booking 1.2.128 → 1.2.130
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 +100 -14
- package/src/components/booking/StandardBookingCheckoutSection.tsx +3 -0
- package/src/components/booking/pricing-v2-checkout-summary.ts +54 -5
- package/src/components/booking/standard-booking-checkout-controller.ts +5 -0
- package/test/change-booking-helpers.test.ts +104 -0
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import { parseISO } from 'date-fns';
|
|
|
5
5
|
import { formatInTimeZone, fromZonedTime } from 'date-fns-tz';
|
|
6
6
|
import {
|
|
7
7
|
type Availability,
|
|
8
|
+
type PricingV2QuoteSnapshot,
|
|
8
9
|
} from '../../lib/booking-api';
|
|
9
10
|
import {
|
|
10
11
|
EARLIEST_AVAILABILITY_DATE,
|
|
@@ -42,6 +43,7 @@ import { StandardBookingCheckoutDialogs } from './StandardBookingCheckoutDialogs
|
|
|
42
43
|
import { StandardBookingItineraryPanel } from './StandardBookingItineraryPanel';
|
|
43
44
|
import { StandardBookingMediaIntro } from './StandardBookingMediaIntro';
|
|
44
45
|
import { StandardBookingSelectionControlsPanel } from './StandardBookingSelectionControlsPanel';
|
|
46
|
+
import { buildCheckoutModalSummaryFromPricingV2Quote } from './pricing-v2-checkout-summary';
|
|
45
47
|
|
|
46
48
|
export function NewBookingFlow({
|
|
47
49
|
product,
|
|
@@ -99,6 +101,8 @@ export function NewBookingFlow({
|
|
|
99
101
|
const [phoneNumber, setPhoneNumber] = useState('');
|
|
100
102
|
const [promoCodeInput, setPromoCodeInput] = useState('');
|
|
101
103
|
const [appliedPromoCode, setAppliedPromoCode] = useState<string | null>(null);
|
|
104
|
+
const [checkoutPricingV2QuoteForDisplay, setCheckoutPricingV2QuoteForDisplay] =
|
|
105
|
+
useState<PricingV2QuoteSnapshot | null>(null);
|
|
102
106
|
const [pickupLocationId, setPickupLocationId] = useState<string | null>(null);
|
|
103
107
|
const [pickupLocationSkipped, setPickupLocationSkipped] = useState(false);
|
|
104
108
|
// Cancellation: change flow seeds from the booking so totals match the quote (server uses booking.cancellationPolicyId).
|
|
@@ -488,6 +492,75 @@ export function NewBookingFlow({
|
|
|
488
492
|
augmentPriceSummary,
|
|
489
493
|
});
|
|
490
494
|
|
|
495
|
+
const bookingItemsDisplayKey = useMemo(
|
|
496
|
+
() =>
|
|
497
|
+
Object.entries(quantities)
|
|
498
|
+
.filter(([, count]) => count > 0)
|
|
499
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
500
|
+
.map(([category, count]) => `${category}:${count}`)
|
|
501
|
+
.join('|'),
|
|
502
|
+
[quantities],
|
|
503
|
+
);
|
|
504
|
+
const addOnSelectionsDisplayKey = useMemo(
|
|
505
|
+
() =>
|
|
506
|
+
normalizeAddOnSelections(addOnSelections)
|
|
507
|
+
.map((selection) =>
|
|
508
|
+
[
|
|
509
|
+
selection.addOnId,
|
|
510
|
+
selection.variantId ?? '',
|
|
511
|
+
selection.quantity ?? 1,
|
|
512
|
+
].join(':'),
|
|
513
|
+
)
|
|
514
|
+
.join('|'),
|
|
515
|
+
[addOnSelections],
|
|
516
|
+
);
|
|
517
|
+
const dependentAddOnSelectionDisplayKey = useMemo(
|
|
518
|
+
() => JSON.stringify(dependentAddOnSelection ?? null),
|
|
519
|
+
[dependentAddOnSelection],
|
|
520
|
+
);
|
|
521
|
+
const checkoutPricingV2DisplayKey = useMemo(
|
|
522
|
+
() =>
|
|
523
|
+
[
|
|
524
|
+
product.productId,
|
|
525
|
+
selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
526
|
+
selectedAvailability?.availabilityId ?? '',
|
|
527
|
+
selectedAvailability?.dateTime ?? '',
|
|
528
|
+
selectedReturnOption?.returnAvailabilityId ?? '',
|
|
529
|
+
bookingItemsDisplayKey,
|
|
530
|
+
currency,
|
|
531
|
+
appliedPromoCode ?? '',
|
|
532
|
+
cancellationPolicyId ?? '',
|
|
533
|
+
addOnSelectionsDisplayKey,
|
|
534
|
+
dependentAddOnSelectionDisplayKey,
|
|
535
|
+
pricingProfileIdForAvailabilities ?? '',
|
|
536
|
+
isAdmin ? 'admin' : 'public',
|
|
537
|
+
].join('::'),
|
|
538
|
+
[
|
|
539
|
+
addOnSelectionsDisplayKey,
|
|
540
|
+
appliedPromoCode,
|
|
541
|
+
bookingItemsDisplayKey,
|
|
542
|
+
cancellationPolicyId,
|
|
543
|
+
currency,
|
|
544
|
+
dependentAddOnSelectionDisplayKey,
|
|
545
|
+
isAdmin,
|
|
546
|
+
pricingProfileIdForAvailabilities,
|
|
547
|
+
product.productId,
|
|
548
|
+
selectedAvailability,
|
|
549
|
+
selectedReturnOption?.returnAvailabilityId,
|
|
550
|
+
],
|
|
551
|
+
);
|
|
552
|
+
useEffect(() => {
|
|
553
|
+
setCheckoutPricingV2QuoteForDisplay(null);
|
|
554
|
+
}, [checkoutPricingV2DisplayKey]);
|
|
555
|
+
const checkoutPricingV2DisplaySummary = useMemo(
|
|
556
|
+
() =>
|
|
557
|
+
buildCheckoutModalSummaryFromPricingV2Quote(
|
|
558
|
+
checkoutPricingV2QuoteForDisplay,
|
|
559
|
+
t('booking.rounding') || 'Rounding',
|
|
560
|
+
),
|
|
561
|
+
[checkoutPricingV2QuoteForDisplay, t],
|
|
562
|
+
);
|
|
563
|
+
|
|
491
564
|
useBookingQuantityCapTrim({
|
|
492
565
|
isAdmin,
|
|
493
566
|
selectedAvailability,
|
|
@@ -502,18 +575,29 @@ export function NewBookingFlow({
|
|
|
502
575
|
: undefined;
|
|
503
576
|
|
|
504
577
|
const checkoutFormError = error || '';
|
|
578
|
+
const hasServerBackedCheckoutDisplay = checkoutPricingV2DisplaySummary != null;
|
|
579
|
+
const checkoutDisplayPriceSummaryLines =
|
|
580
|
+
checkoutPricingV2DisplaySummary?.lines ?? displayCheckoutPriceSummaryLines;
|
|
581
|
+
const checkoutDisplaySubtotal =
|
|
582
|
+
checkoutPricingV2DisplaySummary?.subtotal ?? displaySubtotal;
|
|
583
|
+
const checkoutDisplayTotalPrice =
|
|
584
|
+
checkoutPricingV2DisplaySummary?.fullTotalAmount ?? displayTotalPrice;
|
|
505
585
|
const checkoutFormSubtotal =
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
586
|
+
checkoutPricingV2DisplaySummary != null
|
|
587
|
+
? checkoutPricingV2DisplaySummary.subtotal
|
|
588
|
+
: subtotal !== totalFromSummary ||
|
|
589
|
+
effectivePromoDiscountAmount > 0 ||
|
|
590
|
+
addOnTotal > 0 ||
|
|
591
|
+
(priceSummaryAugmentation?.subtotalAdjustment ?? 0) !== 0
|
|
592
|
+
? displaySubtotal
|
|
593
|
+
: undefined;
|
|
512
594
|
const checkoutFormTaxAmount =
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
595
|
+
checkoutPricingV2DisplaySummary != null
|
|
596
|
+
? checkoutPricingV2DisplaySummary.taxAmount ?? 0
|
|
597
|
+
: !isTaxIncludedInPrice &&
|
|
598
|
+
(effectivePromoDiscountAmount > 0 ? effectiveTax : tax) > 0
|
|
599
|
+
? (effectivePromoDiscountAmount > 0 ? effectiveTax : tax)
|
|
600
|
+
: 0;
|
|
517
601
|
|
|
518
602
|
const handleCheckoutPickupLocationSelect = useCallback((locationId: string | null) => {
|
|
519
603
|
setPickupLocationId(locationId);
|
|
@@ -644,8 +728,8 @@ export function NewBookingFlow({
|
|
|
644
728
|
isAdmin,
|
|
645
729
|
pricingConfig,
|
|
646
730
|
refreshSelectedDateDetailsForCheckout,
|
|
647
|
-
displayTotalPrice,
|
|
648
|
-
displaySubtotal,
|
|
731
|
+
displayTotalPrice: checkoutDisplayTotalPrice,
|
|
732
|
+
displaySubtotal: checkoutDisplaySubtotal,
|
|
649
733
|
totalPrice,
|
|
650
734
|
currency,
|
|
651
735
|
appliedPromoCode,
|
|
@@ -678,6 +762,7 @@ export function NewBookingFlow({
|
|
|
678
762
|
setError,
|
|
679
763
|
onSuccess,
|
|
680
764
|
onShowManage,
|
|
765
|
+
onPricingV2QuoteForDisplay: setCheckoutPricingV2QuoteForDisplay,
|
|
681
766
|
});
|
|
682
767
|
|
|
683
768
|
if (activeOptions.length === 0) {
|
|
@@ -863,8 +948,8 @@ export function NewBookingFlow({
|
|
|
863
948
|
{/* Total and Checkout — shared PriceSummary component */}
|
|
864
949
|
{!selectedBookingOptionsHydrating && selectedAvailability && (
|
|
865
950
|
<StandardBookingCheckoutSection
|
|
866
|
-
priceSummaryLines={
|
|
867
|
-
totalPrice={
|
|
951
|
+
priceSummaryLines={checkoutDisplayPriceSummaryLines}
|
|
952
|
+
totalPrice={checkoutDisplayTotalPrice}
|
|
868
953
|
subtotal={checkoutFormSubtotal}
|
|
869
954
|
taxAmount={checkoutFormTaxAmount}
|
|
870
955
|
taxRate={pricingConfig?.taxRate}
|
|
@@ -876,6 +961,7 @@ export function NewBookingFlow({
|
|
|
876
961
|
promoCodeError={promoCodeError}
|
|
877
962
|
promoCodeValidating={promoCodeValidating}
|
|
878
963
|
promoDiscountAmount={promoDiscountAmount}
|
|
964
|
+
hidePromoDiscountAmount={hasServerBackedCheckoutDisplay}
|
|
879
965
|
onPromoInputChange={handlePromoInputChange}
|
|
880
966
|
onPromoApply={handleApplyPromo}
|
|
881
967
|
onPromoRemove={handleRemovePromo}
|
|
@@ -21,6 +21,7 @@ export interface StandardBookingCheckoutSectionProps {
|
|
|
21
21
|
promoCodeError: string;
|
|
22
22
|
promoCodeValidating: boolean;
|
|
23
23
|
promoDiscountAmount: number;
|
|
24
|
+
hidePromoDiscountAmount?: boolean;
|
|
24
25
|
onPromoInputChange: (value: string) => void;
|
|
25
26
|
onPromoApply: () => void;
|
|
26
27
|
onPromoRemove: () => void;
|
|
@@ -76,6 +77,7 @@ export function StandardBookingCheckoutSection({
|
|
|
76
77
|
promoCodeError,
|
|
77
78
|
promoCodeValidating,
|
|
78
79
|
promoDiscountAmount,
|
|
80
|
+
hidePromoDiscountAmount = false,
|
|
79
81
|
onPromoInputChange,
|
|
80
82
|
onPromoApply,
|
|
81
83
|
onPromoRemove,
|
|
@@ -134,6 +136,7 @@ export function StandardBookingCheckoutSection({
|
|
|
134
136
|
promoCodeError={promoCodeError}
|
|
135
137
|
promoCodeValidating={promoCodeValidating}
|
|
136
138
|
promoDiscountAmount={promoDiscountAmount}
|
|
139
|
+
hideDiscountAmount={hidePromoDiscountAmount}
|
|
137
140
|
currency={currency}
|
|
138
141
|
locale={locale}
|
|
139
142
|
t={t}
|
|
@@ -43,6 +43,45 @@ function fallbackLabel(type: string): string {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function isPaymentCreditType(type?: string | null): boolean {
|
|
47
|
+
const normalized = normalizedLineType(type);
|
|
48
|
+
return normalized === "GIFT_CARD" || normalized === "PAYMENT_CREDIT";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isPaymentCreditQuoteLine(line: PricingV2QuoteLineSnapshot): boolean {
|
|
52
|
+
return isPaymentCreditType(line.type);
|
|
53
|
+
}
|
|
54
|
+
|
|
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
|
+
}
|
|
60
|
+
|
|
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),
|
|
71
|
+
);
|
|
72
|
+
const taxIndex = nonSettlementLines.findIndex((line) => normalizedLineType(line.type) === "TAX");
|
|
73
|
+
|
|
74
|
+
if (taxIndex < 0) {
|
|
75
|
+
return [...nonSettlementLines, ...settlementLines];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return [
|
|
79
|
+
...nonSettlementLines.slice(0, taxIndex + 1),
|
|
80
|
+
...settlementLines,
|
|
81
|
+
...nonSettlementLines.slice(taxIndex + 1),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
46
85
|
function priceSummaryLineFromQuoteLine(
|
|
47
86
|
line: PricingV2QuoteLineSnapshot,
|
|
48
87
|
index: number,
|
|
@@ -83,10 +122,10 @@ export function buildCheckoutModalSummaryFromPricingV2Quote(
|
|
|
83
122
|
const total = pricingV2QuoteTotal(quote);
|
|
84
123
|
if (total == null) return undefined;
|
|
85
124
|
|
|
86
|
-
const lines
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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);
|
|
90
129
|
|
|
91
130
|
if (lines.length === 0) return undefined;
|
|
92
131
|
|
|
@@ -122,8 +161,18 @@ export function buildCheckoutModalSummaryFromPricingV2Quote(
|
|
|
122
161
|
type === "PAYMENT_CREDIT"
|
|
123
162
|
);
|
|
124
163
|
});
|
|
164
|
+
const paymentCreditTotal = Math.max(
|
|
165
|
+
0,
|
|
166
|
+
finiteNumber(quote?.paymentCreditTotal) ?? 0,
|
|
167
|
+
);
|
|
168
|
+
const hasPostTaxDiscount = quoteLines.some(isPostTaxDiscountQuoteLine);
|
|
125
169
|
const subtotal = finiteNumber(
|
|
126
|
-
|
|
170
|
+
paymentCreditTotal > 0 || hasPostTaxDiscount
|
|
171
|
+
? quote?.taxableSubtotal ??
|
|
172
|
+
quote?.grossSubtotal ??
|
|
173
|
+
quote?.netSubtotalBeforeTax ??
|
|
174
|
+
quote?.subtotal
|
|
175
|
+
: quote?.netSubtotalBeforeTax ?? quote?.subtotal ?? quote?.grossSubtotal,
|
|
127
176
|
);
|
|
128
177
|
const taxAmount = hasTaxLine
|
|
129
178
|
? 0
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type Availability,
|
|
13
13
|
type ItineraryDisplayStep,
|
|
14
14
|
type PricingConfig,
|
|
15
|
+
type PricingV2QuoteSnapshot,
|
|
15
16
|
type Product,
|
|
16
17
|
type ReturnOption,
|
|
17
18
|
} from '../../lib/booking-api';
|
|
@@ -107,6 +108,7 @@ export interface UseStandardBookingCheckoutControllerParams {
|
|
|
107
108
|
setError: (error: string) => void;
|
|
108
109
|
onSuccess?: NewBookingFlowProps['onSuccess'];
|
|
109
110
|
onShowManage?: (params: { ref: string; lastName: string }) => void;
|
|
111
|
+
onPricingV2QuoteForDisplay?: (quote: PricingV2QuoteSnapshot | null) => void;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
export function useStandardBookingCheckoutController({
|
|
@@ -170,6 +172,7 @@ export function useStandardBookingCheckoutController({
|
|
|
170
172
|
setError,
|
|
171
173
|
onSuccess,
|
|
172
174
|
onShowManage,
|
|
175
|
+
onPricingV2QuoteForDisplay,
|
|
173
176
|
}: UseStandardBookingCheckoutControllerParams) {
|
|
174
177
|
const [showCheckoutModal, setShowCheckoutModal] = useState(false);
|
|
175
178
|
const [checkoutClientSecret, setCheckoutClientSecret] = useState('');
|
|
@@ -269,6 +272,7 @@ export function useStandardBookingCheckoutController({
|
|
|
269
272
|
setLoading(true);
|
|
270
273
|
setError('');
|
|
271
274
|
paymentSubmitInFlightRef.current = false;
|
|
275
|
+
onPricingV2QuoteForDisplay?.(null);
|
|
272
276
|
|
|
273
277
|
try {
|
|
274
278
|
const reserveResult = await reserveStandardBookingForCheckout({
|
|
@@ -309,6 +313,7 @@ export function useStandardBookingCheckoutController({
|
|
|
309
313
|
timePart,
|
|
310
314
|
itineraryDisplay,
|
|
311
315
|
} = reserveResult;
|
|
316
|
+
onPricingV2QuoteForDisplay?.(pricingV2Quote?.quote ?? null);
|
|
312
317
|
pendingReservationRef.current = { reservationReference: reservation.reservationReference };
|
|
313
318
|
|
|
314
319
|
const taxForBreakdown = effectivePromoDiscountAmount > 0 ? effectiveTax : tax;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
buildChangeBookingPaymentSuccessUrl,
|
|
5
5
|
} from '../src/components/booking/change-booking-payment-modal-builders';
|
|
6
6
|
import { evaluateChangeBookingQuoteForCheckout } from '../src/components/booking/change-booking-quote-guards';
|
|
7
|
+
import { buildCheckoutModalSummaryFromPricingV2Quote } from '../src/components/booking/pricing-v2-checkout-summary';
|
|
7
8
|
import type { ChangeQuoteUiSlice } from '../src/lib/booking/change-flow-pricing';
|
|
8
9
|
import type { ChangeBookingQuoteResponse } from '../src/lib/booking-api';
|
|
9
10
|
|
|
@@ -167,6 +168,109 @@ test('delegates success URL construction to host when provider dashboard asks fo
|
|
|
167
168
|
assert.equal(url, 'provider://ABC-123/Smith/2026-07-08');
|
|
168
169
|
});
|
|
169
170
|
|
|
171
|
+
test('maps Pricing V2 gift cards as payment credits after tax', () => {
|
|
172
|
+
const summary = buildCheckoutModalSummaryFromPricingV2Quote(
|
|
173
|
+
{
|
|
174
|
+
currency: 'CAD',
|
|
175
|
+
grossSubtotal: 449.4,
|
|
176
|
+
netSubtotalBeforeTax: 350.46,
|
|
177
|
+
taxableSubtotal: 449.4,
|
|
178
|
+
taxAmount: 38.2,
|
|
179
|
+
paymentCreditTotal: 98.94,
|
|
180
|
+
payableTotal: 388.66,
|
|
181
|
+
amountToCharge: 388.66,
|
|
182
|
+
lines: [
|
|
183
|
+
{
|
|
184
|
+
lineId: 'ticket_1',
|
|
185
|
+
type: 'TICKET',
|
|
186
|
+
label: 'ADULT',
|
|
187
|
+
amount: 417.42,
|
|
188
|
+
quantity: 2,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
lineId: 'fee_1',
|
|
192
|
+
type: 'FEE',
|
|
193
|
+
label: 'Moraine Lake Road Access Fee',
|
|
194
|
+
amount: 31.98,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
lineId: 'gift_1',
|
|
198
|
+
type: 'GIFT_CARD',
|
|
199
|
+
label: 'Gift card comp',
|
|
200
|
+
amount: -98.94,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
lineId: 'tax_1',
|
|
204
|
+
type: 'TAX',
|
|
205
|
+
label: 'Taxes and fees',
|
|
206
|
+
amount: 38.2,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
'Rounding',
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
assert.equal(summary?.subtotal, 449.4);
|
|
214
|
+
assert.equal(summary?.fullTotalAmount, 388.66);
|
|
215
|
+
assert.deepEqual(
|
|
216
|
+
summary?.lines.map((line) => (line.kind === 'line' ? line.type : 'TICKET')),
|
|
217
|
+
['TICKET', 'FEE', 'TAX', 'GIFT_CARD'],
|
|
218
|
+
);
|
|
219
|
+
});
|
|
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
|
+
|
|
170
274
|
test('builds paid checkout modal data with trimmed customer and change totals', () => {
|
|
171
275
|
const modalData = buildChangeBookingPaidCheckoutModalData({
|
|
172
276
|
bookingReference: 'bookRef_ABC-123',
|