Tax Providers
A family of apps that calculate transaction tax on Hantera orders using an external tax engine, and — where the provider supports it — record the resulting documents for filing and audit.
Tax providers plug into the order pipeline rather than replacing it. Hantera's native tax behaviour (a taxFactor percentage per order line and delivery) keeps working for everything a tax app doesn't cover: destinations outside its enabled countries, channels it isn't enabled for, and any period where the app is switched off.
Available tax apps
- Avalara AvaTax — US sales tax and Canadian GST/HST/PST/QST via Avalara's AvaTax service. Calculates tax on carts and orders, records
SalesInvoiceandReturnInvoicedocuments from Hantera invoices, voids them on cancellation, and ships portal tooling for tax-code lookup, entity/use codes, and address validation.
How a tax app participates in an order
Every tax app in this family follows the same three conventions. They are deliberately vendor-neutral — nothing in Hantera core or in the Commerce app knows about a specific tax provider.
1. Absolute tax amounts, not rates
A tax app writes the exact amounts the tax engine returned:
setOrderLineTax { orderLineId, salesTax }per order linesetShippingTax { deliveryId, tax }per delivery
These are absolute currency amounts. Setting salesTax clears any taxFactor on the line, so the engine's figure is what appears on invoices — the app never re-derives tax from a percentage.
This matters for jurisdictions where tax isn't a clean percentage of the line: US sales tax combines state, county, city and special-district rates, and rounding is per jurisdiction.
2. The taxChecksum order field
External tax engines charge per call and rate-limit aggressively, so a tax app must not call out on every order render.
The convention is a single order dynamic field:
| Field | Type | Meaning |
|---|---|---|
taxChecksum | text | An opaque digest of the tax-relevant inputs as they stood when tax was last calculated. |
On each order calculation the app recomputes the digest from the current inputs (lines, quantities, amounts, tax codes, addresses, customer, origin) and compares:
- Same — the tax already on the order is up to date. No call.
- Different — something tax-relevant changed. One call, then store the new digest.
Only the tax app computes or interprets the value. Everything else treats it as opaque and simply copies it along.
3. The egress response cache
External tax engines charge per call and rate-limit aggressively, so a tax app must not call out on every order render. Cart renders are especially tricky: every render runs the full order pipeline inside a preview that is rolled back, so a rule cannot persist anything to share between renders.
The platform's egress response cache solves this below the app layer. The tax app declares its calculation egress with a positive cacheTtlSeconds and passes its own checksum as the cache key:
- Unchanged state — the
taxChecksumstill on the order matches the freshly computed one, so the rule stops before any egress at all. This is the first tier, and it has no expiry: a months-old order edited without tax-relevant changes never triggers a call. - Changed but recently seen state — the egress dispatch carries
cacheKey = checksum, so the dispatcher serves an identical calculation from memory within the TTL. Repeat renders, validation passes, and even different carts with identical tax-relevant inputs cost zero network calls.
The net effect is one real call per distinct tax-relevant state per cache window, and zero per render — with no cart-side persistence and no Commerce dependency. A forced recalculation (the portal's Recalculate tax action) clears the checksum and writes a one-time nonce into the cache key, so each press costs exactly one real call.
Failure behaviour
Tax apps in this family never block an order. If the tax engine is unreachable or rejects a request:
- The order keeps whatever tax it already had (or falls back to native
taxFactorbehaviour). - The order is tagged so operators can find affected orders.
- The provider's exact error code and message are written to the order timeline.
- The checksum is not updated, so the next mutation retries.
An order that can't be taxed is still an order. Reconciling it is an operations problem, not a reason to fail a customer's checkout.
Related
- Egresses — the response cache that makes cart renders free
- Commerce app — carts and the checkout flow
- Order actor —
setOrderLineTaxandsetShippingTaxcommands - Official Apps — the full app catalogue