tax-service is a Quarkus service that owns tax zone configuration, rate management, and tax calculation for ShopSTAR3. It is stateless at calculation time — it does not store applied taxes. The final tax breakdown is persisted by order-service on the order record. External provider credentials are stored in Vault.
Provider Abstraction#
All tax calculation — whether built-in or delegated to an external engine — goes through a single TaxProvider interface:
interface TaxProvider {
TaxCalculationResult calculate(TaxCalculationRequest request);
}Built-in Adapters#
| Adapter | Notes |
|---|---|
BuiltInTaxProvider | Evaluates zone and rate configuration stored in the database |
AvalaraTaxProvider | Delegates to Avalara AvaTax REST API |
TaxJarProvider | Delegates to TaxJar SmartCalcs REST API |
The active provider per store is set in tax_provider_configs. A store using an external provider still stores its zone and rate config in the database as a fallback reference, but the calculation result comes from the external engine.
Product Tax Classes#
Each catalog product carries a taxClass field that tax-service uses to select the correct rate within a matched zone.
| Tax Class | Typical Use |
|---|---|
STANDARD | Default — most physical goods |
REDUCED | Reduced rate goods — food, books (jurisdiction-dependent) |
ZERO_RATED | Exports and specific exempt categories; tax line shows 0% |
EXEMPT | No tax applied — medical goods, charity items |
Shipping can be assigned a tax class by the store admin (STANDARD or EXEMPT depending on the jurisdiction).
Data Model#
tax_zones — zone_id UUID PK, store_id, name, country_code CHAR(2), state_code (nullable), postal_pattern (nullable, supports wildcards), priority INT (higher wins when multiple zones match), is_active.
tax_rates — rate_id UUID PK, zone_id FK, store_id, tax_class VARCHAR (STANDARD / REDUCED / ZERO_RATED / EXEMPT), rate NUMERIC (e.g. 0.1000 = 10%), name (e.g. “GST”, “VAT”), is_compound BOOLEAN (stacked jurisdictions), is_inclusive BOOLEAN (price already includes tax), is_active.
tax_provider_configs — config_id UUID PK, store_id, provider_type (BUILTIN / AVALARA / TAXJAR), is_active, settings JSONB (non-sensitive: company code, environment, commit mode).
Compound rates#
A zone can carry multiple rates with is_compound = true to model stacked tax jurisdictions (e.g. federal tax applied first, then state tax applied on the tax-inclusive subtotal). Rates within a zone are evaluated in priority order.
Inclusive tax#
When is_inclusive = true, the displayed price already contains tax. The service extracts the tax portion as: tax = price − (price ÷ (1 + rate)). The line item amount returned to checkout-service remains unchanged; only the tax breakdown is updated.
Customer Exemptions#
Customer-level tax exemptions (e.g. reseller certificates) are stored in customer-service. checkout-service enriches the TaxCalculationRequest with an is_tax_exempt flag. When the flag is true, tax-service returns zero tax for all line items and shipping regardless of zone/rate configuration.
Calculation Flow#
checkout-service calls CalculateTax during the order summary step. The sequence for the built-in provider:
- Resolve the matching zone for the shipping address (most specific postal pattern wins).
- If
is_tax_exempt, return all-zero response immediately. - For each line item, look up the rate for
(zone_id, taxClass). Apply inclusive or exclusive calculation. Stack compound rates sequentially. - Apply the same logic to the shipping line using the store’s configured shipping tax class.
- Return the full breakdown: per-line tax amounts, shipping tax, total tax, and a rate breakdown array (name, rate, amount) for display.
gRPC Interface#
| Method | Caller | Purpose |
|---|---|---|
CalculateTax(TaxRequest) | checkout-service | Return line-level and order-level tax breakdown |
message TaxRequest {
string store_id = 1;
Address shipping_addr = 2;
Address billing_addr = 3;
bool is_tax_exempt = 4;
repeated TaxLineItem items = 5;
string shipping_amount = 6;
string shipping_tax_class = 7;
}Kafka Topics#
tax-service does not produce or consume Kafka events. It is a synchronous, stateless calculation service.
Vault Configuration#
| Key path | Purpose |
|---|---|
ss3/kv/tax-service/db.url | PostgreSQL JDBC URL |
ss3/kv/tax-service/db.username | Database username |
ss3/kv/tax-service/db.password | Database password |
ss3/kv/tax-service/avalara.account-id | Avalara account ID |
ss3/kv/tax-service/avalara.license-key | Avalara license key |
ss3/kv/tax-service/taxjar.token | TaxJar API token |
ss3/kv/shared/ | Shared OTel config |