TaxRate computes net, tax and gross with one exact rational and one rounding per call — no float divisor, no int conversion:
use RoundlyConsulting\Money\Tax\TaxRate;
$vat = TaxRate::fromBasisPoints(2000, 'VAT'); // 20 %
$net = Money::ofMajor('10', 'EUR');
$gross = Money::ofMajor('12', 'EUR');
$vat->taxOnNet($net); // 2.00 EUR
$vat->grossFromNet($net); // 12.00 EUR
$vat->netFromGross($gross); // 10.00 EUR
$vat->taxInGross($gross); // 2.00 EUR
$line = TaxRate::of('19')->breakdownFromGross(Money::ofMajor('10', 'EUR'));
$line->net; // 8.40 EUR
$line->tax; // 1.60 EUR
$line->gross; // 10.00 EUR — net + tax === gross, always
TaxRate::of('8.875', 'Sales tax'); // precise to a thousandth of a percent
TaxRate::zero();- TaxRate::of($percent, ?$label), TaxRate::fromBasisPoints($bp, ?$label), TaxRate::zero().
- taxOnNet() and grossFromNet() — for prices without tax.
- netFromGross() and taxInGross() — for prices that include tax.
- breakdownFromNet() rounds the tax and derives the gross; breakdownFromGross() rounds the net and derives the tax.
In both directions net + tax === gross exactly: one side rounds, the other is derived by subtraction. The package property-tests this on 10 000 seeded amounts across all eight rounding modes.
Invoice summaries
use RoundlyConsulting\Money\Tax\TaxSummary;
$vat = TaxRate::of(20, 'VAT');
$reduced = TaxRate::of(10, 'Reduced');
$summary = TaxSummary::of(
$vat->breakdownFromNet(Money::ofMajor('10', 'EUR')),
$vat->breakdownFromNet(Money::ofMajor('25', 'EUR')),
$reduced->breakdownFromNet(Money::ofMajor('8', 'EUR')),
);
$summary->perRate(); // list<TaxBreakdown>, one per rate, sorted by rate:
// 10 %: 8.00 + 0.80 = 8.80 · 20 %: 35.00 + 7.00 = 42.00
$summary->net(); // 43.00 EUR
$summary->tax(); // 7.80 EUR
$summary->gross(); // 50.80 EURTaxSummary re-aggregates lines without re-rounding. Whether tax is rounded per line or per total is your policy — which is why both primitives exist. Mixed currencies throw CurrencyMismatch.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.