From b59140e1c43997953f66da1f6c1d3c83b31ad76c Mon Sep 17 00:00:00 2001 From: Paulo Henriques Date: Mon, 25 May 2026 11:47:32 +0100 Subject: [PATCH 1/2] fix: include tax in cumulative value breakdown when tax is not inclusive When isTaxInclusive=false and a tax rate is provided, both tierAmountDecimal (unit price per tier) and totalAmountDecimal (total for that tier) now correctly include the tax amount. This makes the breakdown transparent and shows how the final price was calculated from the base price plus tax. - Calculate unitAmountWithTax for unit price display - Apply tax multiplier to tier amounts in breakdown when isTaxInclusive=false - Update test expectations to reflect gross amounts in breakdown Co-Authored-By: Claude Haiku 4.5 --- src/tiers/compute-cumulative-value.test.ts | 4 ++-- src/tiers/compute-cumulative-value.ts | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tiers/compute-cumulative-value.test.ts b/src/tiers/compute-cumulative-value.test.ts index 3b83a680..fb7f684d 100644 --- a/src/tiers/compute-cumulative-value.test.ts +++ b/src/tiers/compute-cumulative-value.test.ts @@ -57,11 +57,11 @@ describe('computeCumulativeValue', () => { tiers | quantityToSelectTier | unit | locale | currency | isTaxInclusive | showOnRequest | showStartsAt | tax | expected ${baseTiersUnitAmount} | ${1} | ${'kWh'} | ${undefined} | ${'EUR'} | ${true} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '10,00\xa0€', totalWithPrecision: '10,00\xa0€', average: '10,00\xa0€/kWh', subtotal: '8,40\xa0€', subtotalWithPrecision: '8,403361344538\xa0€', subAverage: '8,40\xa0€/kWh', breakdown: [{ quantityUsed: '1 kWh', tierAmountDecimal: '10,00\xa0€/kWh', totalAmountDecimal: '10,00\xa0€' }] }} ${baseTiersUnitAmount} | ${1} | ${'kWh'} | ${undefined} | ${'EUR'} | ${undefined} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '10,00\xa0€', totalWithPrecision: '10,00\xa0€', average: '10,00\xa0€/kWh', subtotal: '8,40\xa0€', subtotalWithPrecision: '8,403361344538\xa0€', subAverage: '8,40\xa0€/kWh', breakdown: [{ quantityUsed: '1 kWh', tierAmountDecimal: '10,00\xa0€/kWh', totalAmountDecimal: '10,00\xa0€' }] }} - ${baseTiersUnitAmount} | ${1} | ${'kWh'} | ${undefined} | ${'EUR'} | ${false} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '11,90\xa0€', totalWithPrecision: '11,90\xa0€', average: '11,90\xa0€/kWh', subtotal: '10,00\xa0€', subtotalWithPrecision: '10,00\xa0€', subAverage: '10,00\xa0€/kWh', breakdown: [{ quantityUsed: '1 kWh', tierAmountDecimal: '10,00\xa0€/kWh', totalAmountDecimal: '10,00\xa0€' }] }} + ${baseTiersUnitAmount} | ${1} | ${'kWh'} | ${undefined} | ${'EUR'} | ${false} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '11,90\xa0€', totalWithPrecision: '11,90\xa0€', average: '11,90\xa0€/kWh', subtotal: '10,00\xa0€', subtotalWithPrecision: '10,00\xa0€', subAverage: '10,00\xa0€/kWh', breakdown: [{ quantityUsed: '1 kWh', tierAmountDecimal: '11,90\xa0€/kWh', totalAmountDecimal: '11,90\xa0€' }] }} ${baseTiersUnitAmount} | ${1} | ${'kWh'} | ${undefined} | ${'EUR'} | ${false} | ${undefined} | ${undefined} | ${undefined} | ${{ total: '10,00\xa0€', totalWithPrecision: '10,00\xa0€', average: '10,00\xa0€/kWh', subtotal: '10,00\xa0€', subtotalWithPrecision: '10,00\xa0€', subAverage: '10,00\xa0€/kWh', breakdown: [{ quantityUsed: '1 kWh', tierAmountDecimal: '10,00\xa0€/kWh', totalAmountDecimal: '10,00\xa0€' }] }} ${baseTiersUnitAmount} | ${2} | ${'kWh'} | ${undefined} | ${'EUR'} | ${true} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '20,00\xa0€', totalWithPrecision: '20,00\xa0€', average: '10,00\xa0€/kWh', subtotal: '16,81\xa0€', subtotalWithPrecision: '16,806722689076\xa0€', subAverage: '8,40\xa0€/kWh', breakdown: [{ quantityUsed: '2 kWh', tierAmountDecimal: '10,00\xa0€/kWh', totalAmountDecimal: '20,00\xa0€' }] }} ${baseTiersUnitAmount} | ${2} | ${'kWh'} | ${undefined} | ${'EUR'} | ${undefined} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '20,00\xa0€', totalWithPrecision: '20,00\xa0€', average: '10,00\xa0€/kWh', subtotal: '16,81\xa0€', subtotalWithPrecision: '16,806722689076\xa0€', subAverage: '8,40\xa0€/kWh', breakdown: [{ quantityUsed: '2 kWh', tierAmountDecimal: '10,00\xa0€/kWh', totalAmountDecimal: '20,00\xa0€' }] }} - ${baseTiersUnitAmount} | ${2} | ${'kWh'} | ${undefined} | ${'EUR'} | ${false} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '23,80\xa0€', totalWithPrecision: '23,80\xa0€', average: '11,90\xa0€/kWh', subtotal: '20,00\xa0€', subtotalWithPrecision: '20,00\xa0€', subAverage: '10,00\xa0€/kWh', breakdown: [{ quantityUsed: '2 kWh', tierAmountDecimal: '10,00\xa0€/kWh', totalAmountDecimal: '20,00\xa0€' }] }} + ${baseTiersUnitAmount} | ${2} | ${'kWh'} | ${undefined} | ${'EUR'} | ${false} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '23,80\xa0€', totalWithPrecision: '23,80\xa0€', average: '11,90\xa0€/kWh', subtotal: '20,00\xa0€', subtotalWithPrecision: '20,00\xa0€', subAverage: '10,00\xa0€/kWh', breakdown: [{ quantityUsed: '2 kWh', tierAmountDecimal: '11,90\xa0€/kWh', totalAmountDecimal: '23,80\xa0€' }] }} ${baseTiersUnitAmount} | ${2} | ${'kWh'} | ${undefined} | ${'EUR'} | ${false} | ${undefined} | ${undefined} | ${undefined} | ${{ total: '20,00\xa0€', totalWithPrecision: '20,00\xa0€', average: '10,00\xa0€/kWh', subtotal: '20,00\xa0€', subtotalWithPrecision: '20,00\xa0€', subAverage: '10,00\xa0€/kWh', breakdown: [{ quantityUsed: '2 kWh', tierAmountDecimal: '10,00\xa0€/kWh', totalAmountDecimal: '20,00\xa0€' }] }} ${baseTiersUnitAmount} | ${5} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '€50.00', totalWithPrecision: '€50.00', average: '€10.00/kWh', subAverage: '€8.40/kWh', subtotal: '€42.02', subtotalWithPrecision: '€42.016806722689', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '€10.00/kWh', totalAmountDecimal: '€50.00' }] }} ${baseTiersUnitAmount} | ${15} | ${'banana'} | ${'en'} | ${'EUR'} | ${undefined} | ${undefined} | ${undefined} | ${{ rate: 19 }} | ${{ total: '€145.00', totalWithPrecision: '€145.00', average: '€9.67/banana', subAverage: '€8.12/banana', subtotal: '€121.85', subtotalWithPrecision: '€121.848739495798', breakdown: [{ quantityUsed: '10 banana', tierAmountDecimal: '€10.00/banana', totalAmountDecimal: '€100.00' }, { quantityUsed: '5 banana', tierAmountDecimal: '€9.00/banana', totalAmountDecimal: '€45.00' }] }} diff --git a/src/tiers/compute-cumulative-value.ts b/src/tiers/compute-cumulative-value.ts index f852041e..ae37b5a0 100644 --- a/src/tiers/compute-cumulative-value.ts +++ b/src/tiers/compute-cumulative-value.ts @@ -73,6 +73,7 @@ export const computeCumulativeValue = ( }; const breakdown: CumulativePriceBreakdownItem[] = []; + const taxRate = getTaxValue(tax); const total = priceTiersForQuantity.reduce( (total: Dinero, tier: PriceTier, index: number) => { @@ -84,17 +85,20 @@ export const computeCumulativeValue = ( quantity: quantityToSelectTier, }); const tierAmount = toDinero(tier.unit_amount_decimal!, formatOptions.currency).multiply(graduatedQuantity); + const tierAmountWithTax = isTaxInclusive ? tierAmount : tierAmount.multiply(1 + taxRate); + const unitAmount = toDinero(tier.unit_amount_decimal!, formatOptions.currency); + const unitAmountWithTax = isTaxInclusive ? unitAmount : unitAmount.multiply(1 + taxRate); breakdown.push({ quantityUsed: `${graduatedQuantity.toLocaleString(formatOptions.locale, { maximumFractionDigits: 6, })} ${formattedUnit}`, tierAmountDecimal: `${formatAmountFromString({ - decimalAmount: tier.unit_amount_decimal!, + decimalAmount: addSeparatorToDineroString(unitAmountWithTax.getAmount().toString()), ...formatOptions, })}${formattedUnit ? `/${formattedUnit}` : ''}`, totalAmountDecimal: formatAmountFromString({ - decimalAmount: addSeparatorToDineroString(tierAmount.getAmount().toString()), + decimalAmount: addSeparatorToDineroString(tierAmountWithTax.getAmount().toString()), ...formatOptions, }), }); @@ -111,7 +115,6 @@ export const computeCumulativeValue = ( defaultValue: 'Starts at', }); - const taxRate = getTaxValue(tax); const amountTotal = isTaxInclusive ? total : total.multiply(1 + taxRate); const amountSubtotal = isTaxInclusive ? total.divide(1 + taxRate) : total; From 9e380f5d4d519ea64bdca3ed4230ed2a12cc6f6f Mon Sep 17 00:00:00 2001 From: Paulo Henriques Date: Mon, 25 May 2026 12:53:57 +0100 Subject: [PATCH 2/2] Add changeset --- .changeset/chatty-taxes-hammer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chatty-taxes-hammer.md diff --git a/.changeset/chatty-taxes-hammer.md b/.changeset/chatty-taxes-hammer.md new file mode 100644 index 00000000..64fea798 --- /dev/null +++ b/.changeset/chatty-taxes-hammer.md @@ -0,0 +1,5 @@ +--- +'@epilot/pricing': patch +--- + +Fix computeCumulativeValue to handle tax computation correctly