Skip to content

PRD: Fare & Tax Pricing Engine

ModulePricing (CORE-14)PRD IDPRD-FARE-001
StatusShippedOwnerPricing squad
Date2026-06-05Versionv1.0
Packages@nx/pricingURDFARE · TAX

TL;DR

Gives a merchant one engine that turns any product variant into money: it picks the winning fare per line (base, override, or rule-driven discount), layers on the right taxes (inclusive/exclusive, compound, item vs order level), and returns a single immutable pricing snapshot. The sale flow asks for that result at checkout instead of re-implementing pricing, so every line and order is priced consistently and auditably.

1. Context & Problem

Prices were previously hard-coded on the product or computed ad hoc at checkout, with no consistent place for taxes, rule-based prices, or an auditable breakdown. Merchants need a single engine that selects the right fare for each variant, computes taxes correctly (inclusive/exclusive, compound, item vs order level), and returns one consistent priced result. The sale flow must ask for that result at checkout without re-implementing pricing logic - a hard requirement for reliable cost, tax, and margin reporting in the HKD/SME bookkeeping KICKO targets.

This engine builds fare selection and tax computation on top of the product-variant catalog and the per-merchant commerce scope, and exposes a simulation endpoint the sale flow consumes.

2. Goals & Non-Goals

Goals

  • Select the winning fare per variant: default, OVERRIDE (first valid child), or DISCOUNT (cheapest valid child), with rule evaluation (attribute, operator, value; AND logic).
  • Compute taxes in priority order with percentage/fixed/combined modes, inclusive/exclusive treatment, compound tax-on-tax, and item vs order scope.
  • Expose a simulation endpoint that returns an immutable pricing snapshot - per-line applied fares and taxes plus order totals.
  • Auto-seed a fare set + default fare when a product variant is created (CDC), so every variant is immediately priceable.
  • Apply a default tax rate fallback when no tax set is configured.

Non-Goals

  • Applying promotion discounts during checkout - the calculator is not wired this increment (owned by the Promotions feature).
  • Multi-currency pricing.
  • Persisting orders, issuing invoices, or mutating stock.

3. Success Metrics

MetricTarget / signal
Pricing correctnessCheckout prices match configured fares + taxes for sampled baskets; zero discrepancies vs manual calculation
Fare coverage100% of product variants are immediately priceable (auto-seeded fare set + default fare)
Snapshot integrityEvery priced order produces an immutable v2 snapshot with a per-line fare/tax breakdown
PrecisionMonetary math agrees to 4 decimal places end-to-end

4. Personas & Use Cases

PersonaGoal in this feature
Owner / ManagerSet base and conditional prices per variant, attach taxes, control how prices are selected
CashierGet a correct, consistent priced result at checkout without configuring anything
SystemAuto-seed a fare set + default fare when a variant appears, so nothing is unpriceable

Core scenarios: an owner sets a base fare and optional rule-driven child fares + taxes per variant → at checkout the sale flow calls the simulation endpoint → the engine selects the winning fare per line, computes item and order taxes in priority order → returns an immutable pricing snapshot with per-line and order totals.

5. User Stories

  • As an owner, I want to set a base price per variant and optional conditional child prices driven by rules, so the right price applies automatically.
  • As an owner, I want to choose whether the first matching child fare wins (OVERRIDE) or the cheapest valid child wins (DISCOUNT), so I control my pricing strategy.
  • As an owner, I want to attach percentage/fixed/compound taxes at item or order level, inclusive or exclusive, so totals reflect the correct tax treatment.
  • As a cashier, I want checkout to return a consistent priced result for the basket, so I never re-key or re-calculate prices.
  • As a cashier, I want an auditable breakdown of every applied fare and tax, so a disputed price can be explained.
  • As the system, I want a fare set + default fare created automatically when a variant appears, so every variant is immediately priceable.

6. Functional Requirements

#RequirementURD ref
FR-1Each product variant has exactly one activated fare set; an owner can set a default (base) fareURD-FARE-001 · URD-FARE-003
FR-2Auto-seed a fare set + default fare when a product variant is created (CDC)URD-FARE-002
FR-3Parent fares carry an OVERRIDE or DISCOUNT strategy; child fares carry conditional prices and rules (AND logic)URD-FARE-004..006
FR-4OVERRIDE selects the first valid child; DISCOUNT selects the cheapest valid child; default fare is the fallbackURD-FARE-007..009
FR-5Owner can create tax types and a tax set, adding taxes via aggregate updateURD-TAX-001..002
FR-6A tax can be percentage, fixed, or combined; taxes apply in ascending priority orderURD-TAX-003..004
FR-7A tax can be exclusive (added on top) or inclusive (back-calculated); compound taxes compute on the running totalURD-TAX-005..006
FR-8Item-level taxes apply per line; order-level taxes apply to merchant-scoped setsURD-TAX-008
FR-9A default tax rate is applied when no tax set is configuredURD-TAX-009
FR-10The simulation endpoint returns an immutable v2 pricing snapshot (per-line applied fares + taxes, plus order totals); v1 returns a flat resultURD-CON-004
FR-11Monetary values use 4-decimal precision throughout calculationURD-CON-003

Full requirement text and acceptance criteria live in the Pricing URD. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Data integrityA computed v2 pricing snapshot is immutable once produced; the breakdown is not edited after the fact
Tenancy & authzAll fares, taxes, and records are scoped per merchant (merchant header) and use soft-delete; endpoints require authentication
PrecisionMonetary math uses float(value, 4); inclusive taxes never increase the total
Performance / scaleSimulation prices a full basket in one call; v1 and v2 share core fare/tax logic so fixes land in both
ConsistencyExactly one activated fare set per variant; auto-seeding keeps every variant priceable
i18nUser-facing labels are bilingual ({ en, vi })

8. UX & Flows

Configuration screens (fares, fare sets, taxes, tax sets) live in the owner-facing back office; the priced result is consumed headlessly by the sale flow at checkout. Both /simulation (v1, flat) and /simulation-v2 (snapshot) are live, with v2 canonical.

9. Data & Domain

EntityRole
FareSetContainer of all fares for one product variant; exactly one activated per variant
FareA price entry - default (base), or parent/child forming a selection group
RuleA condition (attribute, operator, value) on a child fare; all rules must pass (AND)
TaxSetContainer of taxes for a variant (item-level) or a merchant (order-level)
TaxA percentage/fixed/combined charge with priority, temporal window, inclusive/exclusive/compound flags
TaxTypeA category (e.g. VAT) - system-wide or merchant-scoped
Pricing snapshotThe immutable v2 result - applied fares and taxes per line, plus order totals

Conceptual only - full schema and invariants in the pricing domain model.

10. Dependencies & Assumptions

Depends on

  • Product (variants) - fares, tax sets, and costs are keyed to product variants; variant CDC drives auto-seeding.
  • Commerce / Merchant - fares, taxes, and records are scoped per merchant.
  • Product-variant CDC stream - the trigger that auto-seeds a fare set + default fare.

Assumptions

  • Every product variant emits a CDC event on creation so its fare set can be seeded.
  • A merchant default tax rate exists to apply when no tax set is configured.
  • The sale flow calls the simulation endpoint at checkout rather than computing prices itself.

11. Risks & Open Questions

Risk / questionMitigation / status
Promotion discount ordering vs order-level taxes in the v2 pipelineOpen: define whether the discount calculator runs before or after order-level taxes when it lands
Two live simulation versions (v1 + v2) could driftv1 and v2 share core fare/tax logic; fixes land in both. Open: deprecate v1 once all consumers move to v2
Missing tax configuration would leave a line untaxedA default tax rate is applied when no tax set is configured (FR-9)
Variant created without a fare would be unpriceableAuto-seed a fare set + default fare on variant CDC (FR-2)

12. Release Plan & Launch Criteria

AspectPlan
PhaseP1 (Fares + Taxes) - see URD feature catalog
RolloutAll merchants; no feature flag. Both /simulation (v1) and /simulation-v2 live, v2 canonical
MigrationNone (new entities; fare sets auto-seeded from product-variant CDC)
Launch criteriaCheckout prices match configured fares + taxes for sampled baskets; v2 snapshot produced per order; tax inclusive/exclusive/compound math verified
MonitoringPricing discrepancy rate vs manual calculation, fare-seeding coverage per merchant, snapshot generation errors

13. FAQ

How does the engine pick which fare wins? It evaluates child fares against their rules. For an OVERRIDE parent the first valid child wins; for a DISCOUNT parent the cheapest valid child wins; if no child is valid, the default fare is used.

What happens if a variant has no taxes configured? A default tax rate is applied so the line is never left untaxed.

Inclusive vs exclusive tax - what's the difference? Exclusive taxes are added on top of the price; inclusive taxes are back-calculated out of the price, so an inclusive tax never increases the total.

What is the difference between v1 and v2 simulation? v1 (/simulation) returns a flat priced result; v2 (/simulation-v2) returns an immutable snapshot with a per-line applied-fare and tax breakdown plus order totals. v2 is canonical.

Does this engine apply promotions at checkout? Not yet - promotion entities and CRUD exist, but the discount calculator is not wired into the pricing pipeline this increment.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.