Skip to main content

ADR-0020 — BC tax rules in code

  • Status: Accepted
  • Date: 2026-05-09
  • Decision-makers: Tom Anderson

Context

British Columbia has specific tax rules:

  • GST 5% (federal) on most goods
  • PST 7% (provincial) on most goods, with notable exemptions
  • Bicycles are PST-exempt
  • Components bundled with a new bike are PST-exempt when sold together
  • Components sold standalone are PST-applicable
  • Service labour is PST-applicable
  • Used bikes have different rules than new bikes

The shop's POS needs to apply these correctly per line item, per transaction. Errors here mean either the shop overpays tax (and the shop loses money) or underpays tax (and the shop owes back-taxes + penalties).

Two approaches:

  • Configurable shop settings: each shop sets up their own tax rules. Maximum flexibility; maximum risk that the shop mis-configures.
  • Coded rules per province: BC's rules are encoded in the Worker; the shop chooses "BC" and the rules apply. Less flexible but safer.

Helm currently only serves BC shops. Future provinces will need additional logic; the question is whether the first-pass is generic or BC-specific.

Decision

Encode BC tax rules in code (src/lib/tax-bc.js). The shop selects tax_jurisdiction = 'BC' in shop_config; the tax helper applies the encoded rules per line item.

Per-line tax computation:

function taxForLine({ category, is_bundled_with_bike, is_used }) {
if (category === 'bike' && !is_used) return { gst: 0.05, pst: 0 };
if (category === 'bike' && is_used) return { gst: 0.05, pst: 0 }; // BC used-bike rule
if (category === 'component' && is_bundled_with_bike) return { gst: 0.05, pst: 0 };
if (category === 'service_labour') return { gst: 0.05, pst: 0.07 };
// default
return { gst: 0.05, pst: 0.07 };
}

The shop's tax registration numbers (GST/PST/HST IDs) are in shop_config. The tax engine uses the jurisdiction's coded rules; the shop just provides their IDs.

Future provinces (Alberta — GST only; Ontario — HST 13%) will get their own tax-ab.js, tax-on.js, selected by shop_config.tax_jurisdiction.

Consequences

Positive:

  • Tax errors caught at the code review level, not in audit penalties at year-end
  • The rules are testable (unit tests cover known scenarios from BC's tax guidance)
  • Shop owners don't have to navigate complex tax-rule configurations
  • New shops in BC pick up correct defaults instantly

Negative:

  • BC's rules occasionally change; code update required (mitigation: the rules don't change often, and the version history of tax-bc.js is the audit trail of changes)
  • Each new province adds a code file; not "configurable in admin UI"
  • Edge cases (mixed bundles, demo bikes, warranty parts) require code, not config

Mitigations:

  • A tax_overrides table allows per-line manual overrides (audit-logged) for the edge cases code doesn't yet handle
  • The shop's accountant gets a CSV export tagged by tax category for verification

Notes

The bundled-with-bike exemption is the most error-prone rule; the AIM data showed Swicked applied it inconsistently. Helm's UI explicitly flags bundles at sale time so the operator confirms whether the components are part of a bike purchase. See slice 5 — Transactions & Payments.

See also