Skip to content

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 (validUntil in the past) or not yet valid (validFrom in 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 subdivisions entry exempts. A bare countries entry 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 countries entry 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.