PRD: Pricing simulation & order pricing preview
| Module | Pricing | PRD ID | PRD-SIM-001 |
| Status | Ready to dev | FEAT | SIM · BANA-1434 |
| Epic | — | Plane | BANA-1550 |
| Date | 2026-06-15 | Version | v1.0 |
| Packages | @nx/pricing · @nx/sale | URD | SIM |
| Surface | Backend | ||
| Owner | Phát Nguyễn | ||
What pricing simulation & order pricing preview are
Once the cashier has built the basket, the sale flow prices the whole basket in one call, up to 100 lines, and no order is persisted and no stock is touched. What comes back is a pricing snapshot: one snapshot for the order, plus one snapshot for each line. Each snapshot carries every decision applied to that line - which price, which taxes, which discounts, which fees - and each decision is copied at compute time, so renaming or deactivating a rule later leaves an old receipt reading exactly as it did on the day of sale.
The snapshot also records who bears what: how much the buyer pays, how much the seller absorbs, and which portions belong to the government, the platform, or a supplier. Readers take the figure straight from it instead of re-adding it. Before checkout, the customer is shown the price through a read-only action: the order is re-priced and the snapshot is returned, and the order's status does not change. At checkout the order is priced by that same engine, and the snapshot is persisted alongside the order for later audit, tax declaration, and dispute resolution.
Why a simulation surface is needed
The fare and tax engine 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. Worse, 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 that returns a pricing snapshot, plus the read-only preview the sale flow consumes - with no order persistence, no status change, and no stock side effects.
One example, start to finish
| Step | What happens | Result |
|---|---|---|
| A cashier builds a basket of three lines | The sale flow calls the order-pricing preview | The order is re-priced read-only, returning a pricing snapshot - each line with price, taxes, per-party ledger - plus the order total, with nothing persisted |
| The customer sees the price before paying | Checkout happens | The order is re-priced by that same engine and the snapshot is persisted alongside it |
| A line's variant has no active fare set | The line is priced | The real upstream rejection reason is surfaced, rather than a generic failure |
The easiest thing to get wrong
A preview persists nothing, even when it returns exactly what checkout would
The order-pricing preview and the pricing simulation are pure reads. They always price FROM SCRATCH and return a fresh snapshot - never reading back a previously persisted snapshot, and never writing anything to the order. Only checkout actually persists a snapshot alongside the order.