@ticketboothapp/booking 1.2.119 → 1.2.121
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 +22 -13
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +47 -20
- package/src/components/booking/provider-dashboard-change-booking.ts +11 -0
- package/src/lib/booking/pricing.ts +15 -4
- package/src/lib/booking-api.ts +2 -0
package/package.json
CHANGED
|
@@ -4208,18 +4208,12 @@ export function AdminChangeBookingFlow({
|
|
|
4208
4208
|
type: 'TAX',
|
|
4209
4209
|
});
|
|
4210
4210
|
}
|
|
4211
|
-
let subtotal = roundMoney(displayChangeFlowSubtotal + editableSummaryPreSubtotalDelta);
|
|
4212
4211
|
const tax = roundMoney(displayChangeFlowTax);
|
|
4213
4212
|
const expectedTotal = roundMoney(displayChangeFlowProposedTotalWithEditableLines);
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
);
|
|
4219
|
-
const recomputed = roundMoney(subtotal + tax + promoAdjustmentTotal);
|
|
4220
|
-
if (!unchangedReceiptAnchor && Math.abs(recomputed - expectedTotal) >= 0.005) {
|
|
4221
|
-
subtotal = roundMoney(expectedTotal - tax - promoAdjustmentTotal);
|
|
4222
|
-
}
|
|
4213
|
+
// Receipt subtotal means "subtotal before tax" after discount/payment-adjustment lines.
|
|
4214
|
+
// The visible line list already includes promo/voucher/gift-card rows, so deriving from
|
|
4215
|
+
// total minus tax keeps the footer aligned with the receipt totals we send to the API.
|
|
4216
|
+
const subtotal = roundMoney(expectedTotal - tax);
|
|
4223
4217
|
return {
|
|
4224
4218
|
subtotal,
|
|
4225
4219
|
tax,
|
|
@@ -4231,14 +4225,27 @@ export function AdminChangeBookingFlow({
|
|
|
4231
4225
|
taxIncludedReconciledCheckoutPriceSummaryLines,
|
|
4232
4226
|
quantities,
|
|
4233
4227
|
displayChangeFlowTax,
|
|
4234
|
-
displayChangeFlowSubtotal,
|
|
4235
|
-
editableSummaryPreSubtotalDelta,
|
|
4236
4228
|
displayChangeFlowProposedTotalWithEditableLines,
|
|
4237
|
-
unchangedReceiptAnchor,
|
|
4238
4229
|
currency,
|
|
4239
4230
|
t,
|
|
4240
4231
|
]);
|
|
4241
4232
|
|
|
4233
|
+
const providerApplyAuthoritativeReceipt = useMemo(() => {
|
|
4234
|
+
if (!useAdminFeAuthoritativeQuote) return undefined;
|
|
4235
|
+
return {
|
|
4236
|
+
subtotalBeforeTax: adminFeAuthoritativeReceipt.subtotal,
|
|
4237
|
+
taxAmount: adminFeAuthoritativeReceipt.tax,
|
|
4238
|
+
totalAmount: adminFeAuthoritativeReceipt.total,
|
|
4239
|
+
currency: adminFeAuthoritativeReceipt.currency ?? currency,
|
|
4240
|
+
lineItems: adminFeAuthoritativeReceipt.lineItems.map((line) => ({
|
|
4241
|
+
type: String(line.type || 'FEE'),
|
|
4242
|
+
label: String(line.label || 'Adjustment'),
|
|
4243
|
+
amount: Number(line.amount) || 0,
|
|
4244
|
+
...(line.quantity != null ? { quantity: Number(line.quantity) || 0 } : {}),
|
|
4245
|
+
})),
|
|
4246
|
+
};
|
|
4247
|
+
}, [adminFeAuthoritativeReceipt, currency, useAdminFeAuthoritativeQuote]);
|
|
4248
|
+
|
|
4242
4249
|
const changeFlowClientEstimateDueBase = (() => {
|
|
4243
4250
|
if (!originalReceipt) return totalPrice;
|
|
4244
4251
|
if (unchangedReceiptAnchor) return 0;
|
|
@@ -5401,6 +5408,7 @@ export function AdminChangeBookingFlow({
|
|
|
5401
5408
|
: {}),
|
|
5402
5409
|
}
|
|
5403
5410
|
: undefined,
|
|
5411
|
+
authoritativeReceipt: providerApplyAuthoritativeReceipt,
|
|
5404
5412
|
capacitySeatCredit: {
|
|
5405
5413
|
enabled: true,
|
|
5406
5414
|
previousPassengerCount: changeFlowInitialTicketCount,
|
|
@@ -5796,6 +5804,7 @@ export function AdminChangeBookingFlow({
|
|
|
5796
5804
|
: {}),
|
|
5797
5805
|
}
|
|
5798
5806
|
: undefined,
|
|
5807
|
+
authoritativeReceipt: providerApplyAuthoritativeReceipt,
|
|
5799
5808
|
capacitySeatCredit: {
|
|
5800
5809
|
enabled: true,
|
|
5801
5810
|
previousPassengerCount: changeFlowInitialTicketCount,
|
|
@@ -352,7 +352,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
352
352
|
itineraryDisplay?: ItineraryDisplayStep[] | null;
|
|
353
353
|
clientSecret: string;
|
|
354
354
|
ticketLinesForModal: CheckoutModalLineItem[];
|
|
355
|
-
feeLineItems: { name: string; totalAmount: number }[];
|
|
355
|
+
feeLineItems: { name: string; totalAmount: number; description?: string }[];
|
|
356
356
|
cancellationPolicyFee: number;
|
|
357
357
|
cancellationPolicyLabel?: string;
|
|
358
358
|
subtotal: number;
|
|
@@ -1095,6 +1095,17 @@ export function PrivateShuttleBookingFlow({
|
|
|
1095
1095
|
const cancellationPolicyFee = selectedCancellationPolicy
|
|
1096
1096
|
? (selectedCancellationPolicy.feeByCurrency[currency] ?? 0)
|
|
1097
1097
|
: 0;
|
|
1098
|
+
const perBookingFeeLineItems = useMemo(() => {
|
|
1099
|
+
if (isTaxIncludedInPrice) return [];
|
|
1100
|
+
const amountsByName = pricingConfig?.perBookingFeesByCurrency?.[currency];
|
|
1101
|
+
if (!amountsByName) return [];
|
|
1102
|
+
const feeMetadata = pricingConfig?.perBookingFees ?? {};
|
|
1103
|
+
return Object.entries(amountsByName).map(([name, amount]) => ({
|
|
1104
|
+
name,
|
|
1105
|
+
totalAmount: amount,
|
|
1106
|
+
description: feeMetadata[name]?.description,
|
|
1107
|
+
}));
|
|
1108
|
+
}, [currency, isTaxIncludedInPrice, pricingConfig]);
|
|
1098
1109
|
|
|
1099
1110
|
const addOnTotal = useMemo(() => {
|
|
1100
1111
|
let sum = 0;
|
|
@@ -1114,7 +1125,8 @@ export function PrivateShuttleBookingFlow({
|
|
|
1114
1125
|
}, [addOnSelections, addOns]);
|
|
1115
1126
|
|
|
1116
1127
|
const additionalHoursAmount = (isAdmin ? additionalHoursCount : 0) * ADDITIONAL_HOUR_PRICE;
|
|
1117
|
-
const
|
|
1128
|
+
const perBookingFeeTotal = perBookingFeeLineItems.reduce((sum, fee) => sum + fee.totalAmount, 0);
|
|
1129
|
+
const subtotal = basePrice + addOnTotal + additionalHoursAmount + perBookingFeeTotal;
|
|
1118
1130
|
const effectivePromoDiscountAmount = promoDiscountAmount > 0 ? promoDiscountAmount : 0;
|
|
1119
1131
|
const taxAmount = isTaxIncludedInPrice ? 0 : subtotal * taxRate;
|
|
1120
1132
|
const effectiveTaxAmount =
|
|
@@ -1792,6 +1804,11 @@ export function PrivateShuttleBookingFlow({
|
|
|
1792
1804
|
},
|
|
1793
1805
|
]
|
|
1794
1806
|
: []),
|
|
1807
|
+
...perBookingFeeLineItems.map((fee) => ({
|
|
1808
|
+
label: fee.name,
|
|
1809
|
+
amount: fee.totalAmount,
|
|
1810
|
+
type: 'FEE' as const,
|
|
1811
|
+
})),
|
|
1795
1812
|
...(cancellationPolicyFee > 0 && selectedCancellationPolicy
|
|
1796
1813
|
? [
|
|
1797
1814
|
{
|
|
@@ -1929,23 +1946,26 @@ export function PrivateShuttleBookingFlow({
|
|
|
1929
1946
|
breakdown: null,
|
|
1930
1947
|
},
|
|
1931
1948
|
],
|
|
1932
|
-
feeLineItems:
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
+
feeLineItems: [
|
|
1950
|
+
...addOnSelections
|
|
1951
|
+
.map((sel) => {
|
|
1952
|
+
const addOn = addOns.find((a) => a.addOnId === sel.addOnId);
|
|
1953
|
+
if (!addOn) return null;
|
|
1954
|
+
const base = addOn.price ?? 0;
|
|
1955
|
+
const hasVariant =
|
|
1956
|
+
(addOn.variantType === 'single_choice' || addOn.variantType === 'multi_quantity') &&
|
|
1957
|
+
sel.variantId;
|
|
1958
|
+
const adj = hasVariant
|
|
1959
|
+
? (addOn.variants?.find((v) => v.id === sel.variantId)?.priceAdjustment ?? 0)
|
|
1960
|
+
: 0;
|
|
1961
|
+
return {
|
|
1962
|
+
name: addOn.name,
|
|
1963
|
+
totalAmount: (base + adj) * (sel.quantity ?? 1),
|
|
1964
|
+
};
|
|
1965
|
+
})
|
|
1966
|
+
.filter(Boolean) as { name: string; totalAmount: number }[],
|
|
1967
|
+
...perBookingFeeLineItems,
|
|
1968
|
+
],
|
|
1949
1969
|
cancellationPolicyFee,
|
|
1950
1970
|
cancellationPolicyLabel: selectedCancellationPolicy?.label,
|
|
1951
1971
|
subtotal,
|
|
@@ -2036,7 +2056,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
2036
2056
|
breakdown: null,
|
|
2037
2057
|
},
|
|
2038
2058
|
];
|
|
2039
|
-
const
|
|
2059
|
+
const addOnFeeLineItemsForModal = addOnSelections
|
|
2040
2060
|
.map((sel) => {
|
|
2041
2061
|
const addOn = addOns.find((a) => a.addOnId === sel.addOnId);
|
|
2042
2062
|
if (!addOn) return null;
|
|
@@ -2053,6 +2073,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
2053
2073
|
};
|
|
2054
2074
|
})
|
|
2055
2075
|
.filter(Boolean) as { name: string; totalAmount: number }[];
|
|
2076
|
+
const feeLineItemsForModal = [...addOnFeeLineItemsForModal, ...perBookingFeeLineItems];
|
|
2056
2077
|
|
|
2057
2078
|
setCheckoutClientSecret(paymentIntent.clientSecret ?? '');
|
|
2058
2079
|
setCheckoutModalData({
|
|
@@ -2913,6 +2934,12 @@ export function PrivateShuttleBookingFlow({
|
|
|
2913
2934
|
},
|
|
2914
2935
|
]
|
|
2915
2936
|
: []),
|
|
2937
|
+
...perBookingFeeLineItems.map((fee) => ({
|
|
2938
|
+
kind: 'line' as const,
|
|
2939
|
+
label: fee.name,
|
|
2940
|
+
amount: fee.totalAmount,
|
|
2941
|
+
type: 'fee' as const,
|
|
2942
|
+
})),
|
|
2916
2943
|
...(cancellationPolicyFee > 0
|
|
2917
2944
|
? [
|
|
2918
2945
|
{
|
|
@@ -26,6 +26,17 @@ export type ProviderDashboardChangeBookingPayload = {
|
|
|
26
26
|
/** Signed major-unit rows; may include admin custom receipt lines merged with provider inline adjustments. */
|
|
27
27
|
additionalAdjustments?: Array<{ label: string; amount: number }>;
|
|
28
28
|
} | null;
|
|
29
|
+
/**
|
|
30
|
+
* Admin/provider change flow: when FE-authoritative quoting is used, apply must persist
|
|
31
|
+
* the exact receipt that was quoted instead of rebuilding from catalog pricing.
|
|
32
|
+
*/
|
|
33
|
+
authoritativeReceipt?: {
|
|
34
|
+
currency?: string | null;
|
|
35
|
+
lineItems: Array<{ type: string; label: string; amount: number; quantity?: number; reference?: string }>;
|
|
36
|
+
subtotalBeforeTax: number;
|
|
37
|
+
taxAmount: number;
|
|
38
|
+
totalAmount: number;
|
|
39
|
+
} | null;
|
|
29
40
|
capacitySeatCredit?: {
|
|
30
41
|
enabled: boolean;
|
|
31
42
|
previousPassengerCount?: number | null;
|
|
@@ -450,15 +450,26 @@ export function computeOrderSummary(
|
|
|
450
450
|
: 0;
|
|
451
451
|
|
|
452
452
|
const fees = pricingConfig.fees ?? {};
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
453
|
+
const perBookingFees = pricingConfig.perBookingFees ?? {};
|
|
454
|
+
const perPersonByCurrency = hasFees ? pricingConfig.feesByCurrency?.[currency] : undefined;
|
|
455
|
+
const perBookingByCurrency = pricingConfig.perBookingFeesByCurrency?.[currency];
|
|
456
|
+
const perPersonFeeLineItems: OrderSummaryFeeLine[] =
|
|
457
|
+
!hasFees || totalQuantity === 0 || isTaxIncludedInPrice || perPersonByCurrency == null
|
|
456
458
|
? []
|
|
457
|
-
: Object.entries(
|
|
459
|
+
: Object.entries(perPersonByCurrency).map(([name, amountPerPerson]) => ({
|
|
458
460
|
name,
|
|
459
461
|
totalAmount: round2(totalQuantity * amountPerPerson),
|
|
460
462
|
description: fees[name]?.description,
|
|
461
463
|
}));
|
|
464
|
+
const perBookingFeeLineItems: OrderSummaryFeeLine[] =
|
|
465
|
+
totalQuantity === 0 || isTaxIncludedInPrice || perBookingByCurrency == null
|
|
466
|
+
? []
|
|
467
|
+
: Object.entries(perBookingByCurrency).map(([name, amountPerBooking]) => ({
|
|
468
|
+
name,
|
|
469
|
+
totalAmount: round2(amountPerBooking),
|
|
470
|
+
description: perBookingFees[name]?.description,
|
|
471
|
+
}));
|
|
472
|
+
const feeLineItems = [...perPersonFeeLineItems, ...perBookingFeeLineItems];
|
|
462
473
|
|
|
463
474
|
const feesTotal = feeLineItems.reduce((s, f) => s + f.totalAmount, 0);
|
|
464
475
|
const subtotal = round2(basePrice + returnPriceAdjustment + cancellationPolicyFee + feesTotal);
|
package/src/lib/booking-api.ts
CHANGED
|
@@ -850,6 +850,8 @@ export interface PricingConfig {
|
|
|
850
850
|
currenciesWithTaxIncluded: string[];
|
|
851
851
|
fees?: Record<string, { feePerPerson: number; description?: string }>;
|
|
852
852
|
feesByCurrency?: Record<string, Record<string, number>>;
|
|
853
|
+
perBookingFees?: Record<string, { feePerBooking: number; description?: string }>;
|
|
854
|
+
perBookingFeesByCurrency?: Record<string, Record<string, number>>;
|
|
853
855
|
exchangeRates?: Record<string, number>;
|
|
854
856
|
cancellationPolicies?: CancellationPolicyOption[];
|
|
855
857
|
}
|