@ticketboothapp/booking 1.2.81 → 1.2.82
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
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
changeFlowTicketLineTotalWithReceiptFloor,
|
|
62
62
|
changeFlowFeeLineTotalWithReceiptFloor,
|
|
63
63
|
changeFlowReturnLineTotalWithReceiptFloor,
|
|
64
|
+
reconcileTaxIncludedChangeFlowTicketLines,
|
|
64
65
|
type ChangeFlowProtectedReceiptPricing,
|
|
65
66
|
roundMoney,
|
|
66
67
|
} from '../../lib/booking/change-flow-pricing';
|
|
@@ -3827,11 +3828,30 @@ export function AdminChangeBookingFlow({
|
|
|
3827
3828
|
const displayChangeFlowProposedTotalWithEditableLines = roundMoney(
|
|
3828
3829
|
displayChangeFlowProposedTotal + editableSummaryPreSubtotalDelta
|
|
3829
3830
|
);
|
|
3831
|
+
/** Tax-included currencies: ticket row amounts match authoritative new-booking total (quote/display), not raw catalog/floor rollup. */
|
|
3832
|
+
const taxIncludedReconciledCheckoutPriceSummaryLines = useMemo(() => {
|
|
3833
|
+
if (!isTaxIncludedInPrice || !originalReceipt || showProviderPricingInlineEditor) {
|
|
3834
|
+
return editableCheckoutPriceSummaryLines;
|
|
3835
|
+
}
|
|
3836
|
+
return reconcileTaxIncludedChangeFlowTicketLines(
|
|
3837
|
+
editableCheckoutPriceSummaryLines,
|
|
3838
|
+
displayChangeFlowProposedTotalWithEditableLines,
|
|
3839
|
+
effectivePromoDiscountAmount,
|
|
3840
|
+
);
|
|
3841
|
+
}, [
|
|
3842
|
+
isTaxIncludedInPrice,
|
|
3843
|
+
originalReceipt,
|
|
3844
|
+
showProviderPricingInlineEditor,
|
|
3845
|
+
editableCheckoutPriceSummaryLines,
|
|
3846
|
+
displayChangeFlowProposedTotalWithEditableLines,
|
|
3847
|
+
effectivePromoDiscountAmount,
|
|
3848
|
+
]);
|
|
3849
|
+
|
|
3830
3850
|
const adminFeAuthoritativeReceipt = useMemo((): AdminFeAuthoritativeReceipt => {
|
|
3831
|
-
const hasTaxLine =
|
|
3851
|
+
const hasTaxLine = taxIncludedReconciledCheckoutPriceSummaryLines.some(
|
|
3832
3852
|
(line) => line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'
|
|
3833
3853
|
);
|
|
3834
|
-
const lineItems = mapSummaryLinesToFeReceiptLineItems(
|
|
3854
|
+
const lineItems = mapSummaryLinesToFeReceiptLineItems(taxIncludedReconciledCheckoutPriceSummaryLines);
|
|
3835
3855
|
if (!hasTaxLine && Math.abs(displayChangeFlowTax) >= 0.0005) {
|
|
3836
3856
|
lineItems.push({
|
|
3837
3857
|
label: t('booking.tax') !== 'booking.tax' ? t('booking.tax') : 'Taxes and fees',
|
|
@@ -3854,7 +3874,7 @@ export function AdminChangeBookingFlow({
|
|
|
3854
3874
|
lineItems,
|
|
3855
3875
|
};
|
|
3856
3876
|
}, [
|
|
3857
|
-
|
|
3877
|
+
taxIncludedReconciledCheckoutPriceSummaryLines,
|
|
3858
3878
|
displayChangeFlowTax,
|
|
3859
3879
|
displayChangeFlowSubtotal,
|
|
3860
3880
|
editableSummaryPreSubtotalDelta,
|
|
@@ -5014,8 +5034,11 @@ export function AdminChangeBookingFlow({
|
|
|
5014
5034
|
}),
|
|
5015
5035
|
)
|
|
5016
5036
|
: totalPrice;
|
|
5037
|
+
const ticketRowsForCheckoutBreakdown = taxIncludedReconciledCheckoutPriceSummaryLines.filter(
|
|
5038
|
+
(l): l is Extract<PriceSummaryLine, { kind: 'ticket' }> => l.kind === 'ticket',
|
|
5039
|
+
);
|
|
5017
5040
|
const lines = [
|
|
5018
|
-
...
|
|
5041
|
+
...ticketRowsForCheckoutBreakdown.map((line) => ({
|
|
5019
5042
|
label: line.category,
|
|
5020
5043
|
amount: line.itemTotal,
|
|
5021
5044
|
type: 'TICKET' as const,
|
|
@@ -5158,7 +5181,7 @@ export function AdminChangeBookingFlow({
|
|
|
5158
5181
|
availabilityProductOptionId,
|
|
5159
5182
|
itineraryDisplay: itineraryDisplay ?? undefined,
|
|
5160
5183
|
clientSecret: paymentIntent.clientSecret ?? '',
|
|
5161
|
-
ticketLinesForModal:
|
|
5184
|
+
ticketLinesForModal: ticketRowsForCheckoutBreakdown.map((line) => {
|
|
5162
5185
|
const rate = pricing.find((r) => r.category === line.category);
|
|
5163
5186
|
const breakdown = getPriceBreakdown(
|
|
5164
5187
|
line.category,
|
|
@@ -5185,7 +5208,7 @@ export function AdminChangeBookingFlow({
|
|
|
5185
5208
|
return;
|
|
5186
5209
|
}
|
|
5187
5210
|
|
|
5188
|
-
const ticketLinesForModal: CheckoutModalLineItem[] =
|
|
5211
|
+
const ticketLinesForModal: CheckoutModalLineItem[] = ticketRowsForCheckoutBreakdown.map((line) => {
|
|
5189
5212
|
const rate = pricing.find((r) => r.category === line.category);
|
|
5190
5213
|
const breakdown = getPriceBreakdown(
|
|
5191
5214
|
line.category,
|
|
@@ -5724,7 +5747,7 @@ export function AdminChangeBookingFlow({
|
|
|
5724
5747
|
{selectedAvailability && (
|
|
5725
5748
|
<>
|
|
5726
5749
|
<CheckoutForm
|
|
5727
|
-
priceSummaryLines={
|
|
5750
|
+
priceSummaryLines={taxIncludedReconciledCheckoutPriceSummaryLines}
|
|
5728
5751
|
replacePriceSummary={hasChangeSelection ? selfServeCheckoutPlaceholder : undefined}
|
|
5729
5752
|
totalPrice={changeFlowAmountDue}
|
|
5730
5753
|
totalSummaryLabel={
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
changeFlowTicketLineTotalWithReceiptFloor,
|
|
57
57
|
changeFlowFeeLineTotalWithReceiptFloor,
|
|
58
58
|
changeFlowReturnLineTotalWithReceiptFloor,
|
|
59
|
+
reconcileTaxIncludedChangeFlowTicketLines,
|
|
59
60
|
type ChangeFlowProtectedReceiptPricing,
|
|
60
61
|
roundMoney,
|
|
61
62
|
} from '../../lib/booking/change-flow-pricing';
|
|
@@ -3535,6 +3536,34 @@ export function ChangeBookingFlow({
|
|
|
3535
3536
|
const displayChangeFlowSubtotal = displayedChangeAmounts.subtotal;
|
|
3536
3537
|
const displayChangeFlowTax = displayedChangeAmounts.tax;
|
|
3537
3538
|
|
|
3539
|
+
/** When quote is confirmed but BE sends no `priceSummaryLines`, we still show FE-built lines while total is quote-driven — reconcile ticket amounts for tax-included currencies. */
|
|
3540
|
+
const selfServeSummaryUsesServerLines =
|
|
3541
|
+
suppressSelfServeCurrencyUi &&
|
|
3542
|
+
selfServePricingConfirmed &&
|
|
3543
|
+
(latestChangeQuote?.serverPreview?.priceSummaryLines?.length ?? 0) > 0;
|
|
3544
|
+
|
|
3545
|
+
const taxIncludedSelfServeReconciledPriceSummaryLines = useMemo(() => {
|
|
3546
|
+
if (!isTaxIncludedInPrice || !originalReceipt || !isCustomerSelfServeChange) {
|
|
3547
|
+
return checkoutPriceSummaryLinesForCheckout;
|
|
3548
|
+
}
|
|
3549
|
+
if (selfServeSummaryUsesServerLines) {
|
|
3550
|
+
return checkoutPriceSummaryLinesForCheckout;
|
|
3551
|
+
}
|
|
3552
|
+
return reconcileTaxIncludedChangeFlowTicketLines(
|
|
3553
|
+
checkoutPriceSummaryLinesForCheckout,
|
|
3554
|
+
displayChangeFlowProposedTotal,
|
|
3555
|
+
effectivePromoDiscountAmount,
|
|
3556
|
+
);
|
|
3557
|
+
}, [
|
|
3558
|
+
isTaxIncludedInPrice,
|
|
3559
|
+
originalReceipt,
|
|
3560
|
+
isCustomerSelfServeChange,
|
|
3561
|
+
selfServeSummaryUsesServerLines,
|
|
3562
|
+
checkoutPriceSummaryLinesForCheckout,
|
|
3563
|
+
displayChangeFlowProposedTotal,
|
|
3564
|
+
effectivePromoDiscountAmount,
|
|
3565
|
+
]);
|
|
3566
|
+
|
|
3538
3567
|
const changeFlowClientEstimateDue = (() => {
|
|
3539
3568
|
if (!originalReceipt) return totalPrice;
|
|
3540
3569
|
// Customer self-serve: amount due comes from POST .../change/quote (`amountDueCents` / priceDiff), not FE delta math.
|
|
@@ -4437,8 +4466,11 @@ export function ChangeBookingFlow({
|
|
|
4437
4466
|
originalReceiptTotal: originalReceipt?.total ?? 0,
|
|
4438
4467
|
audience: 'customer',
|
|
4439
4468
|
});
|
|
4469
|
+
const ticketRowsForCheckoutBreakdown = taxIncludedSelfServeReconciledPriceSummaryLines.filter(
|
|
4470
|
+
(l): l is Extract<PriceSummaryLine, { kind: 'ticket' }> => l.kind === 'ticket',
|
|
4471
|
+
);
|
|
4440
4472
|
const lines = [
|
|
4441
|
-
...
|
|
4473
|
+
...ticketRowsForCheckoutBreakdown.map((line) => ({
|
|
4442
4474
|
label: line.category,
|
|
4443
4475
|
amount: line.itemTotal,
|
|
4444
4476
|
type: 'TICKET' as const,
|
|
@@ -4523,7 +4555,7 @@ export function ChangeBookingFlow({
|
|
|
4523
4555
|
})()
|
|
4524
4556
|
);
|
|
4525
4557
|
|
|
4526
|
-
const ticketLinesForModal: CheckoutModalLineItem[] =
|
|
4558
|
+
const ticketLinesForModal: CheckoutModalLineItem[] = ticketRowsForCheckoutBreakdown.map((line) => {
|
|
4527
4559
|
const rate = pricing.find((r) => r.category === line.category);
|
|
4528
4560
|
const breakdown = getPriceBreakdown(
|
|
4529
4561
|
line.category,
|
|
@@ -4883,7 +4915,7 @@ export function ChangeBookingFlow({
|
|
|
4883
4915
|
{selectedAvailability && (
|
|
4884
4916
|
<>
|
|
4885
4917
|
<CheckoutForm
|
|
4886
|
-
priceSummaryLines={
|
|
4918
|
+
priceSummaryLines={taxIncludedSelfServeReconciledPriceSummaryLines}
|
|
4887
4919
|
replacePriceSummary={selfServeCheckoutPlaceholder}
|
|
4888
4920
|
totalPrice={changeFlowAmountDue}
|
|
4889
4921
|
totalSummaryLabel={
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
* **Provider** inline pricing may show a **signed** delta from cart or manual overrides.
|
|
47
47
|
*/
|
|
48
48
|
import type { ChangeBookingQuoteResponse } from '../booking-api';
|
|
49
|
+
import type { PriceSummaryLine } from '../../components/booking/PriceSummary';
|
|
49
50
|
import { reconcileChangeBookingProposedTotal } from '../currency';
|
|
50
51
|
|
|
51
52
|
/** Money in major units, rounded to cents (half-up). */
|
|
@@ -301,6 +302,77 @@ export function resolveChangeFlowDisplayedAmounts(input: {
|
|
|
301
302
|
return { ...input.fromCart };
|
|
302
303
|
}
|
|
303
304
|
|
|
305
|
+
function isPromoSummaryLine(row: PriceSummaryLine): boolean {
|
|
306
|
+
if (row.kind !== 'line') return false;
|
|
307
|
+
const t = String(row.type ?? '').toUpperCase();
|
|
308
|
+
if (t === 'PROMO_CODE' || t === 'GIFT_CARD' || t === 'DISCOUNT' || t === 'VOUCHER') return true;
|
|
309
|
+
return row.amount < -0.005 && /promo|discount|voucher|gift/i.test(row.label);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Tax-included currencies (e.g. EUR): ticket rows from catalog + receipt floors can disagree with the
|
|
314
|
+
* authoritative **new booking total** (quote / display layer). Scale ticket `itemTotal` values so
|
|
315
|
+
* tickets + non-ticket lines + promo = `targetNewBookingTotal` without changing quantities or non-ticket rows.
|
|
316
|
+
*/
|
|
317
|
+
export function reconcileTaxIncludedChangeFlowTicketLines(
|
|
318
|
+
lines: PriceSummaryLine[],
|
|
319
|
+
targetNewBookingTotal: number,
|
|
320
|
+
promoDiscountAmountIfNotInLines: number,
|
|
321
|
+
): PriceSummaryLine[] {
|
|
322
|
+
if (!Number.isFinite(targetNewBookingTotal) || lines.length === 0) return lines;
|
|
323
|
+
|
|
324
|
+
const ticketIndices: number[] = [];
|
|
325
|
+
let currentTicketTotal = 0;
|
|
326
|
+
for (let i = 0; i < lines.length; i++) {
|
|
327
|
+
const row = lines[i];
|
|
328
|
+
if (row.kind === 'ticket') {
|
|
329
|
+
ticketIndices.push(i);
|
|
330
|
+
currentTicketTotal += row.itemTotal;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (ticketIndices.length === 0) return lines;
|
|
334
|
+
|
|
335
|
+
let nonTicketSum = 0;
|
|
336
|
+
let promoLineSum = 0;
|
|
337
|
+
for (const row of lines) {
|
|
338
|
+
if (row.kind === 'line') {
|
|
339
|
+
const t = String(row.type ?? '').toUpperCase();
|
|
340
|
+
if (t === 'TAX') continue;
|
|
341
|
+
if (isPromoSummaryLine(row)) {
|
|
342
|
+
promoLineSum += row.amount;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
nonTicketSum += row.amount;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const desiredTicketTotal =
|
|
350
|
+
Math.abs(promoLineSum) >= 0.005
|
|
351
|
+
? roundMoney(targetNewBookingTotal - nonTicketSum - promoLineSum)
|
|
352
|
+
: roundMoney(
|
|
353
|
+
targetNewBookingTotal - nonTicketSum + Math.max(0, promoDiscountAmountIfNotInLines),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (currentTicketTotal <= 0 || Math.abs(desiredTicketTotal - currentTicketTotal) < 0.02) {
|
|
357
|
+
return lines;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const scale = desiredTicketTotal / currentTicketTotal;
|
|
361
|
+
const next = lines.slice() as PriceSummaryLine[];
|
|
362
|
+
let running = 0;
|
|
363
|
+
for (let k = 0; k < ticketIndices.length; k++) {
|
|
364
|
+
const i = ticketIndices[k];
|
|
365
|
+
const row = next[i] as Extract<PriceSummaryLine, { kind: 'ticket' }>;
|
|
366
|
+
const isLast = k === ticketIndices.length - 1;
|
|
367
|
+
const newTotal = isLast
|
|
368
|
+
? roundMoney(desiredTicketTotal - running)
|
|
369
|
+
: roundMoney(row.itemTotal * scale);
|
|
370
|
+
running += newTotal;
|
|
371
|
+
next[i] = { ...row, itemTotal: newTotal };
|
|
372
|
+
}
|
|
373
|
+
return next;
|
|
374
|
+
}
|
|
375
|
+
|
|
304
376
|
/**
|
|
305
377
|
* Product: **Main “total owed” number** should not show -0.00 or noise when the balance is effectively zero.
|
|
306
378
|
*/
|