Skip to content

PRD: Pricing simulation & order pricing preview

ModulePricing (CORE-14)PRD IDPRD-SIM-001
StatusShippedOwnerPricing squad
Date2026-06-15Versionv1.0
Packages@nx/pricing · @nx/sale · @nx/coreURDSIM · FARE · TAX

TL;DR

Prices a basket without ever persisting an order. The engine exposes two stateless simulation endpoints: v1 returns a flat, per-line + order-totals breakdown (selected fare, applied taxes, line and order totals); v2 returns an immutable, auditable pricing snapshot - one order snapshot plus one line snapshot per line - where every applied decision (price, tax, discount, fee) is copied in-time and a per-party ledger says exactly who bears what. The sale flow calls a read-only order-pricing preview to show a customer their price before checkout, and at checkout it runs v1 (authoritative) alongside v2 (additive, best-effort) so the order can be priced and, later, the snapshot persisted - all sharing the same core fare and tax logic.

1. Context & Problem

The fare and tax engine (PRD-FARE-001) decides the winning fare per variant and computes compound taxes. It answers "what is this one variant's price right now" - but the sale flow needs to price a whole basket at once, show that price to a customer before any order is committed, and later keep an auditable record of exactly how each line was priced.

Without a simulation surface the sale flow would have to call the fare and tax primitives line by line, re-assemble order totals itself, and re-implement the inclusive/exclusive tax rules - and there would be no immutable, self-contained record of the pricing decision to attach to the order for tax declaration, ledger, and dispute resolution. A merchant also cannot show a trustworthy "this is what you will pay" figure if pricing is only computed at the moment of commit.

This increment closes that gap with a stateless pricing engine in two shapes - a flat v1 breakdown for direct display and an immutable v2 snapshot for persistence and audit - and the read-only preview the sale flow consumes, with no order persistence, no status change, and no stock side effects.

2. Goals & Non-Goals

Goals

  • A stateless basket-pricing engine (1-100 lines) that never persists an order or touches stock.
  • A v1 flat breakdown - per-line selected fare, applied taxes, and totals, plus order totals and a compute timestamp.
  • A v2 immutable snapshot - an order snapshot plus one line snapshot each, carrying every applied decision self-contained so historical rendering survives later rule edits.
  • A per-party ledger (buyer / seller / platform / supplier / government) on v2 so readers consume totals directly instead of re-aggregating.
  • A read-only order-pricing preview the sale flow calls to show a price before checkout.
  • v1 (authoritative) and v2 (additive, best-effort) computed together at checkout, sharing one core fare/tax implementation.

Non-Goals

  • Order persistence, status changes, invoice issuance, or stock mutation - the preview is read-only; persistence stays in the sale callers.
  • Applying promotions / discounts in the pipeline - the discount calculator is not yet wired (URD-PROMO-006); v2 reserves discount and fee decision slots but does not yet populate them.
  • Multi-currency conversion - a line carries a currency code, but the engine does not convert (URD-CON).
  • The fare-selection and tax-computation rules themselves - specified in PRD-FARE-001.

3. Success Metrics

MetricTarget / signal
No side effectsA preview or simulation never persists an order, changes status, or mutates stock
Total integrityorder total = Σ line totals holds at decimal precision; inclusive taxes never increase the total
Snapshot auditabilityEvery v2 applied decision renders correctly even after its source rule is renamed or deactivated
Line traceabilityEvery priced line maps back to its request line by the caller-supplied id
Checkout resilienceA v2 failure never blocks checkout; the v1 result still returns

4. Personas & Use Cases

PersonaGoal in this feature
CashierSee a trustworthy basket price before committing the order
CustomerBe shown what they will pay, line by line, ahead of checkout
Owner / ManagerTrust that each order keeps an auditable record of how it was priced
Sale flow (system)Price an order read-only for preview, and price + snapshot it at checkout

Core scenario: a cashier builds a basket of three lines; the sale flow calls the order-pricing preview, which re-prices the existing order read-only and returns the v1 breakdown - per-line fare, taxes, and totals plus the order total - with nothing persisted. At checkout the sale flow prices the same order with v1 (authoritative) and v2 (best-effort); the v2 snapshot - order plus per-line, each line carrying its price, taxes, and a per-party ledger - is available to persist on the order. If a line's variant has no active fare set, the real upstream rejection is surfaced rather than a generic failure.

5. User Stories

  • As a cashier, I price the whole basket in one call and get per-line and order totals, so I never assemble totals by hand.
  • As a customer, I am shown what I will pay before I commit, so there are no surprises at checkout.
  • As an owner, I want every order to keep a self-contained record of how each line was priced, so I can defend a figure long after the rule changed.
  • As an owner, I want a per-party breakdown of who bears each amount, so tax and ledger reads need no re-computation.
  • As the sale flow, I want the preview to be read-only, so showing a price never mutates the order.
  • As the sale flow, I want a v2 failure to never block checkout, so a snapshot gap degrades gracefully to the v1 figure.

6. Functional Requirements

#RequirementURD ref
FR-1Stateless basket pricing of 1-100 lines, with no order persistence and no stock mutationURD-SIM-001
FR-2v1 flat breakdown: per-line base/unit price, selected fare + applied rules, applied taxes, subtotal/discount/tax/total; plus order totals and a compute timestamp; inclusive vs exclusive taxes treated correctlyURD-SIM-002 · URD-SIM-004
FR-3A caller-supplied line id is echoed back and used as the result key for unambiguous matchingURD-SIM-003
FR-4v2 immutable snapshot - one order snapshot plus one line snapshot each, carrying every applied decision (price, tax, discount, fee) copied in-timeURD-SIM-005 · URD-SIM-006
FR-5v2 totals: per-line buyer-payable + per-party ledger; order rollup of subtotal, buyer-payable, per-party ledger, and seller liabilityURD-SIM-007
FR-6A v2 simulation declares a transaction direction (SALE / PURCHASE) and an order currency (ISO 4217, default VND)URD-SIM-008
FR-7A read-only order-pricing preview re-prices an existing order and returns the v1 breakdown; pricing an empty cart is refusedURD-SIM-009 · URD-SIM-010
FR-8At checkout the order is priced with v1 (authoritative) + v2 (additive, best-effort, never blocking); persisted per-line taxes merge v1 VAT with v2 PIT entriesURD-SIM-011 · URD-SIM-012
FR-9Each line's pricing context carries quantity, compute time, day/time/date, the basket's variant ids (co-occurrence rules), and a derived service window, so fare rules can evaluate themURD-SIM-013
FR-10An upstream rejection (e.g. no active fare set) surfaces with its real status/reason; both endpoints are authenticated + merchant-scoped + decimal-precise; v1 and v2 share core fare/tax logicURD-SIM-014 · URD-SIM-015 · URD-SIM-016

Full requirement text and acceptance criteria live in the Pricing URD - SIM. This PRD references them rather than restating them. The fare-selection and tax-computation rules are specified in PRD-FARE-001.

7. Non-Functional Requirements

AreaRequirement
StatelessnessSimulation and preview are pure reads - no order persistence, status change, or stock side effect
ImmutabilityA v2 snapshot is immutable once computed (URD-CON-004); corrections re-compute a new snapshot, never edit one
Self-containmentEach v2 applied decision copies its label, base, value, and amount at compute time, so it renders correctly even after the source rule changes
Resiliencev2 is additive at checkout - its failure is logged and skipped, never blocking the authoritative v1 result
Tenancy & authzBoth endpoints require authentication and a merchant scope (x-merchant-id); gated by a pricing simulate permission
PrecisionAll pricing math uses decimal precision (float(value, 4)); inclusive taxes never increase the total
Shared corev1 (/simulation) and v2 (/simulation-v2) share the same core fare/tax logic, so a fix lands in both (C-06)
i18nUser-facing fare and tax labels are bilingual ({ en, vi })

8. UX & Flows

Key surfaces: the customer-facing price preview in the sale client (per-line and order totals), and - behind checkout - the v2 snapshot attached to the order and its items for audit, tax declaration, and ledger reads.

9. Data & Domain

ConceptRole
OrderPricingSnapshotOrder-level snapshot: direction, currency, order-scope decisions, and rolled-up totals (subtotal, buyer-payable, per-party ledger, seller liability)
LineItemPricingSnapshotOne line's snapshot: quantity, merged context, its applied decisions, and totals (buyer-payable + per-party ledger)
AppliedDecisionA self-contained PRICE / TAX / DISCOUNT / FEE entry - label, base, value, amount, bearer, inclusivity, priority, compounding - copied at compute time
v1 breakdownThe flat per-line (fare, taxes, totals) + order-totals result, keyed by the caller-supplied line id

Conceptual only - the full snapshot schema and invariants live in the pricing domain model. The snapshot is designed to be persisted on the order and its items by the sale callers.

10. Dependencies & Assumptions

Depends on

  • Fare & tax engine (PRD-FARE-001, URD-FARE · URD-TAX) - simulation drives fare selection and tax computation; both versions share that core.
  • Sale orders (Orders) - the preview re-prices an existing order; checkout persists the snapshot.
  • @nx/core - shared money precision, tax-snapshot shape, and merchant scoping.

Assumptions

  • Each priced variant has an activated fare set (auto-seeded on variant creation); a missing fare set is an upstream rejection, surfaced as such.
  • The discount/promotion calculator is not yet wired (URD-PROMO-006); v2 reserves but does not populate discount and fee decisions.
  • The sale callers own all persistence and status changes; the pricing engine stays stateless.

11. Risks & Open Questions

Risk / questionMitigation / status
A snapshot could drift from the rule it cited if the rule later changesEach applied decision copies label/base/value/amount in-time - the snapshot is self-contained
v2 failure could block checkoutv2 is additive and best-effort - failures are logged and skipped; the authoritative v1 result still returns
Inclusive taxes mis-counted into the totalInclusive taxes are back-calculated and never increase the total (C-03)
Lines confused when matching the response backThe caller-supplied line id is echoed and used as the result key
A variant without an active fare set fails opaquelyThe real upstream status/reason is propagated instead of a generic 500

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - SIM in the URD feature catalog, alongside COST
RolloutAll merchants; no feature flag
MigrationNone - simulation and preview are stateless reads over the existing fare/tax engine
Launch criteriav1 returns per-line + order totals with correct inclusive/exclusive tax; v2 returns an immutable order + per-line snapshot with a per-party ledger; the preview is read-only and refuses an empty cart; checkout prices v1 + v2 with v2 never blocking; upstream rejections surface their real status
MonitoringSimulation latency and error rate by version, v2 best-effort failure rate at checkout, upstream-rejection counts (e.g. missing fare set)

13. FAQ

Does previewing a price change the order? No - the preview is a pure read. It re-prices an existing order and returns the v1 breakdown without persisting anything, changing status, or touching stock.

What is the difference between v1 and v2? v1 is a flat breakdown for direct display - per-line fare, taxes, and totals plus order totals. v2 is an immutable, auditable snapshot - an order snapshot plus one line snapshot each, with every decision self-contained and a per-party ledger - meant to be persisted on the order.

Why run both at checkout? v1 is authoritative and always required; v2 is additive and best-effort. Computing them together lets the order be priced from v1 while the v2 snapshot is captured for audit - and a v2 failure never blocks the sale.

Will a snapshot still read correctly if I rename a tax later? Yes - each applied decision copies its label, base, value, and amount at compute time, so historical rendering is unaffected by later rule edits.

Are promotions applied here? Not yet - the discount calculator is not wired into the pipeline (URD-PROMO-006). v2 reserves discount and fee decision slots for when it is.

References

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