Exemptions
Exemptions
A buyer may hold a certificate that removes the tax it would otherwise be charged —
a US resale permit, a nonprofit or government exemption, or another
jurisdiction-specific basis. The engine accepts this as a native input on the query
and, when it is valid and covers the taxed jurisdiction, returns a native Exempt
assessment.
The boundary: the engine computes; the consumer captures
The engine deliberately does not capture or verify the underlying certificate.
Storing the certificate, checking its expiry, and verifying it against a tax
authority are the consumer's concern (a certificate store in your app). The
consumer expresses the result of that verification as a TaxExemption value
object, and the engine applies it. This keeps TaxQuery free of buyer identity: it
carries the place of supply and an asserted, verified exemption, not a customer
record.
The input
use Cbox\Tax\ValueObjects\TaxExemption;
use Cbox\Tax\Enums\ExemptionType;
use Cbox\Geo\ValueObjects\{CountryCode, SubdivisionCode};
$exemption = new TaxExemption(
type: ExemptionType::Resale, // Resale | Nonprofit | Government | Other
reference: 'CA-RESALE-42', // opaque certificate id, kept on the assessment
countries: [], // country-level coverage (EU/national VAT)
subdivisions: [new SubdivisionCode('US-CA')], // sub-federal coverage (US states, CA provinces)
validFrom: null, // optional validity window (DateTimeImmutable)
validUntil: null,
);
$assessment = app(TaxCalculator::class)->assess(new TaxQuery(
// …amount, pricing, place, customer, seller…
exemption: $exemption,
));
TaxQuery::$exemption is optional and defaults to null — every existing query is
unaffected.
Precedence — deny-by-default, overrides only Standard
The exemption is applied last, as an override of the regime's verdict. It only
rewrites a would-be Standard-taxed line to Exempt; every other treatment is
left exactly as the regime decided:
| Regime verdict | With a valid, covering exemption |
|---|---|
Standard (tax would be charged) |
→ Exempt (net kept, tax 0, gross = net) |
ReverseCharge (cross-border B2B, buyer self-accounts) |
unchanged |
NotRegistered (no seller nexus) |
unchanged |
ZeroRated (a real 0% rate) |
unchanged |
Exempt (already out of scope, e.g. a non-taxable product) |
unchanged |
This is the same precedence an app-layer decorator over the calculator would implement — an exemption never manufactures tax where the regime charged none, and never competes with reverse-charge or nexus rules. It only relieves a supply the seller would otherwise have to tax.
An exemption is ignored (the standard tax stands) when:
- it does not cover the place of supply (see matching, below);
- it is expired (
validUntilin the past) or not yet valid (validFromin the future); - the supply was not going to be standard-taxed in the first place.
Jurisdiction matching
Coverage is matched at the granularity of the taxing jurisdiction — the
placeOfSupply on the assessment, which for EU micro-business origin sourcing is
the seller's country, not the buyer's:
- Sub-federal place (a US state, a Canadian province): only a matching
subdivisionsentry exempts. A barecountriesentry does not — exemption certificates there are issued per state/province, so a country-wide claim is refused. - National place (an EU Member State, the UK, …): a matching
countriesentry exempts.
On the assessment
A certificate-driven exemption records the driving TaxExemption on the result, so
the reference and basis survive into your audit trail:
$assessment->treatment; // TaxTreatment::Exempt
$assessment->tax->isZero(); // true — net kept, gross = net
$assessment->exemption?->reference;// 'CA-RESALE-42'
$assessment->exemption?->type; // ExemptionType::Resale
$assessment->reason; // 'Exempt: resale exemption (ref: CA-RESALE-42) covers US-CA; standard tax overridden.'
$assessment->isExempt(); // true
TaxAssessment::$exemption is null for every non-certificate outcome, including
an Exempt treatment that is out of scope rather than certificate-driven (e.g. a
product that is simply not taxable in the state) — so a reader can tell the two
apart.
See testing for the dogfooded
InteractsWithTax::taxExemption() builder and assertExempt() helper.
Exemptions by purchaser
TaxExemption applies a certificate you have already decided covers the sale. When
you know who is buying but not whether the place relieves them, state the purchaser
and let the place's own rule decide. A charity is exempt in Texas and taxable in
Alabama; a direct pay permit holder in Arkansas pays the tax itself; a diplomat in a
member state is relieved under Art. 151 within that state's limits.
use Cbox\Tax\Enums\PurchaserType;
use Cbox\Tax\ValueObjects\DecisionFacts;
new TaxQuery(
// …
purchaser: PurchaserType::CharitableOrganization,
facts: new DecisionFacts([
'recipient.federalIncomeTaxExemptUnderIrc501c' => '3',
'use.relatedToThePurposeOfTheOrganization' => true,
'evidence.holdsExemptionCertificate' => true,
]),
);
On a TaxOrder, pass purchaser and the document's facts once; every line gets
them, and a line's own fact wins.
The rule comes from the register's purchaser_exemption rules: the US state's own,
or the member state's, then the regime's (eu) for what binds every member. Only a
supply the seller would otherwise charge is asked about.
| The place's rule | The answer |
|---|---|
| applies, certificate held or not required | Exempt, or ZeroRated where the input tax stays deductible (Art. 151), with the citation on the invoice |
| applies, and the purchaser accounts for the tax (a direct pay permit) | ReverseCharge: the seller charges nothing, and the tax is still due |
| applies at a reduced rate | taxed at that rate |
| applies by refund | taxed at the till; the purchaser reclaims |
| a condition is false, or the place grants this purchaser nothing | taxed, no flag |
a certificate is required and evidence.holdsExemptionCertificate is not true |
taxed, flagged ExemptionCertificateMissing |
| a condition's facts are missing, or the place has not said whether it relieves at the till or by refund | taxed, flagged PurchaserExemptionUnsettled, naming the facts |
| the register states nothing for this purchaser here | taxed, flagged PurchaserExemptionNotPublished |
The engine never assumes an exemption. Where a rule cannot be read, the sale is taxed
and flagged. Bind your own Cbox\Tax\Contracts\PurchaserExemptions to answer for
places the register does not cover yet.