ExciseEngine¶
ExciseEngine connects Vectis to the separate ExciseEngine excise-tax service for per-jurisdiction excise tax (vape, tobacco, and similar regulated industries). It calculates API-first against the Engine's GraphQL API, falls back to a locally-synced rate engine when the API is unavailable, supports seven rate-type formulas plus three additional compatibility rate types, and rounds with ROUND_HALF_EVEN banker's rounding.
At a glance
| Extension id | excise_engine |
| Category | tax |
| Version | 1.0.0 |
| Provides | Tax calculation strategy (stage 200), Temporal sync workflow + 24 h schedule, admin GraphQL queries/mutations, admin page + nav + quick actions, Cmd-K search indexes, extension-owned ee_* tables |
| External account | ExciseEngine account — API URL, API key + secret, industry ID, ship-from location ID |
What It Provides¶
- Excise calculation strategy — a
TaxCalculationStrategyregistered under the nameexcise_engineat stage200, so it runs after sales tax (stage100, e.g. TaxJar) in the stage-sorted tax engine. Only lines whose product traits carryexcise_engine_tax_class_slug(a legacy tax-class trait,exciseiq_tax_class_slug, is also honored) are taxed. Emits oneexcise-type tax line per jurisdiction component so the audit trail shows e.g. state and federal components separately. - API path — calls the Engine's
calculateTaxGraphQL mutation to get a quote; the quote id is cached in Valkey (ee:quote:{channel_id}:{account_id}, 30-minute TTL) so it can be finalized when the order is placed. - Local fallback engine — when the API is unavailable and Fallback on API Failure is on, tax is computed from locally-synced tables. Rate type formulas:
ad_valorem,per_unit,minimum,compound,tiered,flat_cost_per_order,percentage_of_price_x_milliliters, plus the additional compatibility typesad_valorem_cost,flat_per_ml, andflat_per_mfg_weight_oz. - Order lifecycle handlers — event-bus subscriptions (priority 70):
order.placedfinalizes the cached quote into an Engine transaction (finalizeQuote),order.status_changedtocancelledcancels it (cancelTransaction), andorder.modifiedrebuilds and resubmits the cart (amendTransaction). The Engine transaction id is stored on the order's metadata underee_transaction_id. - Temporal workflow + schedule —
SyncExciseEngineDataWorkflowruns thesync_excise_engine_dataactivity on the registered schedulevectis-sync-excise-engine-data(every 24 hours), pulling locations, tax classes, tax regions, and tax rates from the Engine into the local cache tables. - Admin GraphQL — queries
exciseEngineTaxClasses,exciseEngineLocations,exciseEngineTaxRegions,exciseEngineSyncLogs; mutationsexciseEngineTestConnection,exciseEngineTriggerSync, andexciseEngineCalculateTax(an ad-hoc spot-check calculator). The mutations require thesettings.editpermission. - Admin UI — a nav item ExciseEngine in the Tax section linking to
/extensions/excise_engine, with tabs Configuration, Overview, Tax Classes, Locations, Tax Regions, Sync Log, and Calculator. Six Cmd-K quick actions (Sync Now, Calculator, Tax Classes, Tax Regions, Sync Log, Settings) link to the page and its tabs. Products get an Excise Taxes tab panel for assigning tax classes and unit attributes. - Cmd-K search indexes — federated search over synced tax classes, tax regions, and locations (handy for verifying state coverage).
- Extension-owned tables —
ee_tax_classes,ee_locations,ee_tax_regions,ee_tax_rates, andee_sync_log, managed by the extension's own migration chain (theee_*names are grandfathered — new extensions must use theext_<name>_prefix).
Setup¶
- Enable the extension for your channel under Settings > Extensions.
- Open Tax > ExciseEngine in the admin nav and switch to the Configuration tab. Enter the API URL, API Key, API Secret, Industry ID, and Ship-From Location ID (plus Store ID for multi-store Engine accounts).
- Test the connection —
exciseEngineTestConnectionverifies the credentials by fetching your industry's tax classes. - Run an initial sync — the Sync Now button on the extension page (the Sync ExciseEngine Now quick action jumps there), or the
exciseEngineTriggerSyncmutation — to populate locations, tax classes, regions, and rates. Check progress on the Sync Log tab. - Assign an excise tax class to each taxable product on its Excise Taxes tab — the strategy only taxes lines whose product traits carry
excise_engine_tax_class_slug.
Configuration Reference¶
| Key | Type | Required | Secret | Description |
|---|---|---|---|---|
api_url |
string | Yes | No | ExciseEngine GraphQL API base URL (e.g. https://api.eiq.example.com) |
api_key |
string | Yes | Yes | ExciseEngine API key for Bearer authentication |
api_secret |
string | Yes | Yes | ExciseEngine API secret for Bearer authentication |
industry_id |
string | Yes | No | UUID of your industry in ExciseEngine (e.g. vape, tobacco) |
ship_from_location_id |
string | Yes | No | ExciseEngine UUID of your warehouse / origin location |
store_id |
string | No | No | Optional ExciseEngine Store UUID (for multi-store accounts) |
calculate_tax_by |
enum (shipping, billing) |
No | No | Address to use for jurisdiction resolution; default shipping. billing requires the tax engine to pass billing address (pending core support) |
fallback_on_api_failure |
boolean | No | No | Use locally-synced rates when the API is unavailable (default true) |
sync_schedule |
enum (hourly, daily, weekly) |
No | No | How often to sync tax data from ExciseEngine (default daily) |
debug_logging |
boolean | No | No | Enable verbose logging for troubleshooting (default false) |
tax_scope |
enum (product, variant) |
No | No | Whether excise tax fields are configured per product (all variants share one set) or per variant (default product) |
use_core_cost_field |
boolean | No | No | Use the product's built-in cost field for cost-based tax calculations; disable to show a separate cost field on each product's Excise Taxes tab (default true) |
Secret fields are stored Fernet-encrypted and read back through the typed config accessor that decrypts them; they are masked in the admin.
Permissions¶
tax.excise_engine.manage— also gates all six Cmd-K quick actions.
Permissions are upserted at boot from the manifest; assigning them to roles is currently a manual step.
Operational Notes¶
- Auth format — the client authenticates with
Authorization: Bearer {api_key}:{api_secret}against{api_url}/graphql, with a 15 s request timeout. - Rate limiting — on HTTP 429 the client records the
Retry-Afterwindow (default 60 s) and short-circuits further calls until it elapses. - Fail-soft calculation — API errors are logged and the strategy falls back to the local engine when Fallback on API Failure is enabled; with it disabled, no excise lines are emitted for that calculation.
- Rounding — both the API and fallback paths round amounts to 4 decimal places with
ROUND_HALF_EVEN. - Quote TTL — the cached quote expires after 30 minutes. If the order is placed after expiry, the
order.placedhandler finds no quote to finalize and no Engine transaction is created for that order. - Sync interval is fixed — the Temporal schedule registered by the extension runs every 24 hours; the
sync_schedulesetting does not change the registered interval. The manual sync mutation falls back to running the sync activity in-process when Temporal is unreachable. - Fallback region matching — supports exclusion zones (
is_excluderegions remove matching locations from the positive set), postal-code-range regions for jurisdictions finer than the Engine's location tree, and per-customer-group rates: a region with acustomer_grouponly matches accounts in that group (matched by CustomerGroup slug); regions with no group match everyone. - Location resolution — the shipping address resolves to an Engine location by county name first, then by state ISO code (
US-<state>); the fallback engine walks the location's parent chain to collect all applicable regions.
For the admin spot-check tool, see the Excise Tax Calculator; for the usage-level overview, Tax; for the developer view, Building Extensions.