AstroBaaS

Tax & compliance

Destination-Based Tax

Free — GPL coresize Mplanned, not built

Generated from docs/plan/core/destination-based-tax/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.

Merged from duplicate proposals: “Destination-Based Tax”, “Destination-Based Tax Engine”

A tax rate engine for multi-jurisdiction selling that stores VAT, GST, and sales tax rates per destination and product category, applies them at checkout, and audits all calculations. Core feature—no licence gating—so merchants can always comply with the law.

The problem

A German merchant selling to 12 countries has no way to store different VAT rates per destination; they’re manually checking rates per order and risk charging 7% to Austria (15% super-reduced) or 19% to Spain (4% reduced). Incorrect tax collection exposes them to back-taxes and fines.

What it does

  • Store tax rates in a category × country matrix (rate, effective date, rule name)
  • Apply rates at line-item level during order creation, not at checkout
  • Support multiple rule types: standard, reduced, super-reduced, zero-rate, reverse-charge
  • UI to upload/manage rate tables per country (bulk import from CSV)
  • Audit log: which rate version was applied to each order, when it changed
  • Tax-inclusive vs tax-exclusive toggle per shop setting
  • Integration with product-category assignment: tax lookup by category + destination
  • Checkout JSON API returns tax per line item, total tax, effective rate
  • Admin dashboard: rate coverage map (dark = missing rates, light = complete)
  • Export: tax report grouped by country, category, effective date

What it deliberately does NOT do

Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.

  • Real-time rate updates or third-party rate feeds (Avalara, TaxJar)—that’s the paid pack’s job; core is hand-maintained
  • Cross-border B2B reverse-charge logic (handled by reverse-charge slug separately, as it requires VIES validation)
  • Tax nexus calculation or threshold tracking (e.g., ‘when do I owe tax in France?’)—merchant responsibility
  • Invoice generation or line-item tax reporting to external tax authorities—paid pack owns filing
  • Tax calculation for digital goods, subscriptions, or downloads with delivery rules—scope stays to tangible goods, expand in future

Data model

New: TaxRate(id, shopId, countryCode, category, rate_bps, effective_from, rule_type, archived_at); TaxLine(orderId, lineId, applied_rate_id, tax_amount_minor)—both require schema migration for any live install

API

  • GET /tax-rates — list all rates for this shop
  • POST /tax-rates — create a new rate
  • PUT /tax-rates/:rateId — update effective date or rate value
  • DELETE /tax-rates/:rateId — mark archived (soft delete for audit)
  • POST /tax-rates/bulk-import — CSV upload for category × country matrix
  • GET /tax-rates/coverage — returns map of country × category coverage (% complete)
  • POST /orders/:orderId/recalculate-tax — reapply current rates (manual audit tool)
  • GET /tax-audit — filtered list of TaxLine records, grouped by country/date

Every route added here must also appear in src/pages/openapi.json.ts — a test fails the build if it does not.

Admin

Tax manager panel: world map showing rate coverage, drill-down to each country; upload/edit matrix; preview tax on sample product before checkout goes live; audit report (which orders used which rate version); archive old rates without deleting

The seam — why this is core

Core owns: tax rate storage, line-item calculation, audit trail, admin UI, CSV import. Paid pack owns: third-party feed sync (Avalara, TaxJar), VIES reverse-charge validation, per-country rate maintenance & quarterly updates, OSS filing integration.

GPL-3.0 core, paid modules in a separate repo — the arithmetic engine (lookup rate by country + category) and data structure (rate table) belong in core. NO licence gating: a merchant can input rates manually to comply.

Dependencies

  • product-categories (must exist before tax rates can reference them)
  • order line-item schema (tax_amount_minor field must be available)
  • core settings (shop.defaultTaxInclusive boolean)

Acceptance checks

Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.

  • An order for Germany with a standard 19% product shows 19% VAT on the line item
  • An order for Spain with a 4% super-reduced product shows 4% VAT on the line item
  • An order with mixed rates (one 19%, one 4%) shows both on separate lines, totals correctly
  • A product with no category assigned does NOT crash checkout; tax defaults to zero or shop default
  • Archiving a rate does not recalculate past orders; future orders use new rate
  • An old rate version from 2025-06 can be retrieved for an order placed in June 2025
  • CSV import with 27 countries × 4 categories loads in <5s, all rows validated
  • Coverage report shows 100% green when all combinations are filled, red when any are missing

Risks

Schema migration on live shops—data loss if rollback is not tested first. Tax miscalculation at scale—even 0.1% error on VAT compounds across thousands of orders. Archive soft-delete confusion—if not carefully implemented, archived rates leak into checkout. Order recalculation bug—must not re-charge or refund customers if rates change.

Commercial context

Suggested priceFree (core)
Rival anchorShopify Tax (€0.01/txn ~€10-100/mo for SMBs); Magento (free, no UI); Avalara (€50-500/mo)

The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.


Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full catalogue and ../../AI-GUIDE.md for how to work on this repository.