Pattern · Tax
Country-driven tax code, without Apex
Vary the tax code — not just the rate — by customer country, using only configuration and a flow.
The problem
A subscription product was pinned to a single tax code. Sold to a customer in another country, no rate matched and the invoice came out with no tax at all — silently. Event-type products, which carry their tax code from the event location, were unaffected, which made the bug look random.
Context
Two product families with opposite rules: some products impose their own tax code because the taxable event happens somewhere specific, others must take the code of the customer's country.
Recommended architecture
First the negative finding, because it closes off the obvious paths: no native mechanism varies the tax code by country. TaxTreatmentItem carries product and tax code only, with no geographic field. And the standard tax decision table takes a code as input and returns a rate — it can never produce a code.
So the code has to be placed on the line before calculation, and made to survive repricing. The lever is TaxPolicy.TreatmentSelection = Manual: a treatment set on the line then commands the calculation and is not re-derived.
Implementation
- Declare the rule on the product. A picklist with two values is enough: the product imposes its code, or the code comes from the customer.
- Hold the tax zone on the account as a picklist maintained by finance, not derived from the address — finance needs to be able to override it.
- Set the treatment from a flow on quote line create and update, mapping zone to code.
- Set the policy to Manual so the treatment survives Reprice All.
- Do not write a second flow for the order. The context service carries the treatment from the quote line to the order item on its own.
Trade-offs
A flow instead of Apex means no test class and no deployment coverage problem — on one project org coverage was the single obstacle to a production release, so avoiding Apex was worth real money.
The cost is that Manual selection makes the line authoritative: if the flow is wrong, nothing downstream corrects it.
Watch out
- A flow criterion written as
Status = "Activated"never fires in a French-language org, where the value renders as “Activé”. The flow fails silently and no schedule is ever created. Any flow criterion written in English is suspect on a localised org. InvoiceLine.TaxCodeis always empty, posted or not. Read the code throughTaxTreatment.TaxCode.