Skip to main content

Overview

Tax is the part of commerce most likely to differ from what you assumed. A US shopper expects $17.99 on the shelf and a little more at the till. A German shopper expects €17.99 to be the whole story, VAT already inside it. Both are correct, and a store selling to both has to do both. Spree handles this by keeping three things separate:
  • What is being sold — a tax category groups products taxed the same way
  • Where the customer is — their market and shipping address
  • Who works out the number — Spree’s own rates, or an external tax service
The result of all this is a set of tax lines on the order — one per charge, recording what was taxed and at what rate. This page is about how they get their numbers.

Tax categories

A tax category is how you say “these things are taxed alike”. Children’s clothing, books and hot food are treated differently from general goods in many countries, and a tax category is where that distinction lives. Every product carries one. If you don’t choose, the default is used.
tax_code matters once you connect an external service — it is how you tell that service what kind of thing this is, in the vocabulary it already uses.

Tax rates

A tax rate is the number itself, plus where it applies and whether it’s already in the price.
Rates name their country and state by ISO code, not by an ID — DE, US + CA. There’s no separate region record to create first.

Included or added on

included_in_price is the single most consequential setting here, so it’s worth being precise about what it does.
The shelf price is what the product costs. Tax is worked out at checkout, once the address is known, and added.A $17.99 item at 5%:Set included_in_price: false.
When building an order summary, add additional_tax_total only. included_tax_total is already inside the item prices — adding it counts the tax twice. This is the most common bug in a European storefront.
Because prices are shown before anyone knows where the shopper lives, each Market declares whether its prices are quoted with tax included. That’s what lets a catalogue page show honest prices to a German visitor and a US visitor at the same time.

Tax lines

Whatever works out the tax, the record is the same: a tax line per charge, saying what was taxed and how.
Tax lines attach to a line item, a fulfillment, or a fee — so tax on goods and tax on delivery stay separately visible, which is what a tax return needs. Each line also keeps its own copy of the rate and label. If someone edits that tax rate next year, the order still says what the customer was actually charged. You never create tax lines yourself; they’re written whenever the total is worked out.

Tax given back

When a customer returns something, the tax they paid on it goes back with the money. That tax is recorded as tax lines too, marked credit, on the return, claim or exchange line rather than the order line. Each one names the sale’s tax line it gives back (original_tax_line_id, Admin API), keeps that line’s rate and label, and covers only the units that came back. They never change the order’s own tax totals, and the order’s tax lines list above shows only what was charged.

When tax is worked out

Tax is recalculated whenever the answer could have changed — an item added, an address entered, a delivery option chosen. Every one of those requests returns the updated cart, so a storefront never needs a separate “refresh the tax” call.
1

Before an address is known

Prices are shown using the market’s tax treatment. For an inclusive market the displayed price is the real one; for an added-on market tax simply hasn’t been worked out yet.
2

Once the shipping address is entered

The address decides the real rate. Tax lines are written against each item, the delivery charge, and any taxable fee.
3

At completion

Whatever the tax is at that moment is what’s charged, and the order keeps it. A rate change next month does not rewrite an order from last month.

Connecting a tax service

Spree’s built-in rate tables are fine when the rules are simple — one country, or a handful of clearly-defined rates. They become a liability where the rules are not: US sales tax varies by city and changes constantly, and cross-border EU VAT has thresholds that shift with your turnover. For those, connect a real tax service. The provider is chosen per Market, which means one store can use its own rates in a simple market and an external service in a complicated one.
A provider declares up front what it cannot do — no US local tax, no reverse charge — so the dashboard can warn a merchant who pairs it with a market that needs it, rather than quietly under-collecting. Whichever provider is in use, the result is the same: tax lines on the order, in the same shape. Your storefront code doesn’t change. The provider also decides the tax given back on returns, claims and exchanges, and the refund pays exactly what it answers. Spree’s own rates give back each returned unit’s share of the tax the sale actually charged, so the customer gets back what they paid even if a rate has been edited since. An external service may calculate it itself instead, for example at the rates in force on the day of the sale. If it cannot be reached, the return is refused until it can, rather than refunding tax nobody worked out. If you are writing a provider, these are the calls Spree makes after the sale: estimate_refund and estimate_replacement have no default — a provider without them cannot open a return, claim or exchange. To give back exactly what the sale charged, as Spree’s own rates do, answer estimate_refund with Spree::TaxProvider::RecordedShare:
server/app/models/my_app/tax_provider.rb
refund and commit_replacement are optional: by default they do nothing, which suits a service that keeps no record of sales.

Tax-exempt customers

Business customers are often exempt, and the paperwork is real. Spree records a customer’s or company’s tax identifier — a VAT number, an ABN — and can validate it. Companies can also hold exemption certificates, scoped to the country or state that issued them. Exemption is decided when tax is worked out, not stored as a flag on the customer, so an expired certificate stops applying by itself. See Companies.