Skip to content

URD: Pricing

ModuleCORE-14Versionv0.1
StatusBuiltDate2026-06-05

Business documentation. This URD is Pricing's feature list - each feature below is one Functional Area (<AREA>). The same <AREA> keys the feature's PRDs (PRD-<AREA>-NNN) and tests (TC-<AREA>-NNN), and each feature is listed in the Delivery feature catalog. See the Feature Spine convention.

1. Purpose

Define user-facing requirements for the Pricing engine - how a merchant sets prices, how taxes are computed, how variant costs are tracked, and how promotions are configured. The outcome is that every product line and every order is priced consistently and auditably, both when an owner configures it and when the sale flow asks for a price at checkout.

2. Scope

IncludedExcluded
Fare sets, fares, parent/child fare hierarchyOrder persistence
Rule-based fare selection (quantity, time, channel)Invoice / e-invoice issuance
Tax sets, taxes, tax typesStock mutation
Compound, inclusive/exclusive, priority-ordered tax computationPayment processing
Item-level and order-level taxesMulti-currency pricing
Variant cost tracking with effective date rangesDiscount calculation in checkout (calculator pending)
Pricing simulation (v1 flat + v2 snapshot)Technical API specifications
Promotions, promotion methods, eligibility rules

3. Definitions

TermDefinition
Fare SetThe container of all fares for one product variant. One activated fare set per variant.
FareA price entry. A default fare is the base price; parent/child fares form a selection group.
Default FareThe fallback base price used when no rule-bearing child fare matches.
Parent / Child FareA parent fare defines a selection strategy (OVERRIDE or DISCOUNT); child fares carry the conditional prices.
RuleA condition (attribute, operator, value) attached to a child fare or promotion; all rules must pass (AND logic).
Tax SetThe container of all taxes for a variant or for a merchant.
TaxA percentage, fixed, or combined charge with priority, temporal window, and inclusive/exclusive/compound flags.
Tax TypeA category (e.g. VAT, GST) - system-wide or merchant-scoped.
CostWhat a product variant costs the merchant over an effective date range; one is the current cost.
Pricing SnapshotThe immutable, auditable v2 result: every applied fare and tax per line, plus order totals.
PromotionA merchant-configured discount campaign with one method and eligibility rules.
Promotion MethodThe mechanic of a promotion (how the discount is applied).

4. Conceptual Model

Conceptual only - the full schema lives in the developer domain model.

5. Feature Catalog

The feature list of this module. Each row is one feature (a Functional Area). Detail in §6. Mirrored in the Delivery feature catalog.

Feature IDFeaturePhaseStatusPriority
FAREFares & Fare SetsP1BuiltHigh
TAXTax ComputationP1BuiltHigh
COSTCost TrackingP2BuiltHigh
SIMPricing Simulation & PreviewP2BuiltHigh
PROMOPromotions & RulesP3In-progressMedium

Status: live from Plane where mapped, otherwise registry-declared. Vocabulary mirrors Plane (state-group / phase).

6. Features

One sub-section per feature, in catalog order. Each feature keeps its description, requirements, and acceptance together. Priority = MoSCoW (Must / Should / Could / Won't).

FARE - Fares & Fare Sets Built

Feature ID: pricing/FARE · Phase: P1 · PRDs: Fare & Tax Pricing Engine · Dev: @nx/pricing · Fare System

What it does for users: owners set a base price per variant and, optionally, conditional child prices driven by rules (quantity, time window, channel). The engine picks the winning fare automatically - the first valid child for an OVERRIDE group, or the cheapest valid child for a DISCOUNT group, falling back to the default fare.

Requirements

IDPRequirement
URD-FARE-001MEach product variant has exactly one activated fare set.
URD-FARE-002MA fare set + default fare is auto-created when a product variant is created (CDC).
URD-FARE-003MOwner can set a default (base) fare per variant.
URD-FARE-004MOwner can create a parent fare with one of two strategies: OVERRIDE or DISCOUNT.
URD-FARE-005MOwner can add child fares under a parent, each with its own price.
URD-FARE-006MChild fares can carry rules (attribute, operator, value) evaluated with AND logic.
URD-FARE-007MOVERRIDE: the first valid child fare is selected immediately.
URD-FARE-008MDISCOUNT: the lowest-priced valid child fare is selected.
URD-FARE-009MWhen no child fare is valid, the default fare is selected.
URD-FARE-010SFares support an effective-from / effective-to window and quantity bounds.
URD-FARE-011MFares and fare sets are isolated per merchant and use soft-delete.

Acceptance

AC-FARE-01: Rule-based selection
GivenWhenThen
Variant with default 100 and a DISCOUNT child of 80 (rule: qty ≥ 10)Price 12 unitsChild fare 80 wins (lowest valid)
Same variantPrice 5 unitsDefault fare 100 wins (no rule matches)
AC-FARE-02: Auto-seed on variant
GivenWhenThen
A new product variantVariant CDC event arrivesA fare set + default fare are auto-created

TAX - Tax Computation Built

Feature ID: pricing/TAX · Phase: P1 · PRDs: Fare & Tax Pricing Engine · Dev: @nx/pricing · Tax System

What it does for users: owners attach taxes to a variant (item-level) or a merchant (order-level), choosing percentage/fixed amounts, inclusive or exclusive treatment, and compound tax-on-tax. The engine applies them in priority order and returns a clear breakdown.

Requirements

IDPRequirement
URD-TAX-001MOwner can create tax types (e.g. VAT), system-wide or merchant-scoped.
URD-TAX-002MOwner can create a tax set and add taxes to it (aggregate update).
URD-TAX-003MA tax can be percentage, fixed, or combined (percentage + fixed).
URD-TAX-004MTaxes apply in priority order (lower number = higher priority).
URD-TAX-005MA tax can be exclusive (added on top) or inclusive (back-calculated from the price).
URD-TAX-006MA tax can be compound (computed on the running total including prior taxes).
URD-TAX-007SA tax can carry an effective window and min/max quantity conditions.
URD-TAX-008MItem-level taxes apply per line; order-level taxes apply to merchant-scoped sets.
URD-TAX-009MA default tax rate is applied when no tax set is configured.
URD-TAX-010MTax sets and taxes are isolated per merchant and use soft-delete.

Acceptance

AC-TAX-01: Inclusive vs exclusive
GivenWhenThen
Price 110, one 10% exclusive taxComputeTax 11; total 121
Price 110, one 10% inclusive taxComputeTax ≈ 10; total stays 110 (back-calculated)
AC-TAX-02: Compound order
GivenWhenThen
Two taxes (10% then 5% compound) on base 100ComputeFirst 10 → base 110 → second 5.5; total tax 15.5

COST - Cost Tracking Built

Feature ID: pricing/COST · Phase: P2 · PRDs: - · Dev: @nx/pricing · Cost Tracking

What it does for users: owners record what each variant costs them over time. Updating the current cost ends the prior cost record and opens a new one, so a full cost history is preserved for margin and reporting.

Requirements

IDPRequirement
URD-COST-001MOwner can record a cost for a product variant with an effective date range.
URD-COST-002MOwner can read the current cost for a variant.
URD-COST-003MUpdating the current cost ends the prior record and creates a new one.
URD-COST-004MAt most one current (open-ended) cost exists per variant at any time.
URD-COST-005SCost history is retained and retrievable.
URD-COST-006MCosts are isolated per merchant and use soft-delete.

Acceptance

AC-COST-01: Update current cost
GivenWhenThen
Variant with current cost 50Owner sets current cost to 60Prior 50 record is ended; 60 becomes the current cost
Same variantRead current costReturns 60

SIM - Pricing Simulation & Preview Built

Feature ID: pricing/SIM · Phase: P2 · PRDs: Pricing simulation & order pricing preview · Dev: @nx/pricing · Simulation

What it does for users: the sale flow prices a whole basket without committing an order. Two stateless endpoints serve it - a v1 flat breakdown (per-line fare, taxes, totals + order totals) for direct display, and a v2 immutable, auditable snapshot (an order snapshot plus one line snapshot each, with a per-party ledger) meant to be persisted on the order. A read-only preview shows a customer their price before checkout; at checkout v1 (authoritative) and v2 (additive, best-effort) run together so the order is priced and its snapshot captured.

Requirements

IDPRequirement
URD-SIM-001MA stateless simulation prices a basket of 1-100 lines and returns per-line and order totals without persisting an order or mutating stock.
URD-SIM-002MThe v1 simulation (/simulation) returns a flat breakdown - per line: base price, unit price, selected fare + applied fare rules, applied taxes, quantity, subtotal, discount, tax, total; plus order subtotal/discount/tax/total and a compute timestamp.
URD-SIM-003MA caller-supplied line id is echoed back and used as the result key, so each priced line maps unambiguously to its request line.
URD-SIM-004MInclusive taxes are embedded in the price and never increase the total; exclusive taxes are added on top - consistent with the tax engine.
URD-SIM-005MThe v2 simulation (/simulation-v2) returns an immutable, auditable snapshot - an order snapshot plus one line snapshot per line - carrying every applied decision (price, tax, discount, fee).
URD-SIM-006MEach v2 applied decision copies its label, base, value, and amount at compute time, so historical rendering stays correct even if the source rule is later renamed or deactivated.
URD-SIM-007MA v2 line snapshot exposes ready-to-read totals - buyer-payable plus a per-party (buyer/seller/platform/supplier/government) ledger - and the order snapshot rolls these into subtotal, buyer-payable, per-party ledger, and seller liability.
URD-SIM-008MA v2 simulation declares a transaction direction - SALE or PURCHASE - and an order currency (ISO 4217, default VND).
URD-SIM-009MThe order-pricing preview (POST /sale-orders/{id}/pricing/preview) re-prices an existing order read-only - no persistence, no status change - and returns the v1 breakdown.
URD-SIM-010MPricing an empty cart is refused.
URD-SIM-011SAt checkout the order is priced with v1 (authoritative) plus v2 (additive, best-effort); a v2 failure is logged and skipped and never blocks checkout.
URD-SIM-012SThe applied taxes persisted per line merge v1 VAT entries with v2 PIT entries.
URD-SIM-013MEach line's pricing context carries quantity, compute time, day-of-week/time/date, the basket's variant ids (for co-occurrence rules), and a derived service window (service time, date, day-of-week, duration), so fare rules can evaluate them.
URD-SIM-014MWhen the upstream pricing service rejects a line (e.g. no active fare set), the sale flow surfaces the real status and reason rather than a generic failure.
URD-SIM-015MBoth simulation endpoints require authentication and a merchant scope, and all pricing math uses decimal precision.
URD-SIM-016Mv1 and v2 share the same core fare and tax logic, so a fix lands in both.

Acceptance

AC-SIM-01: v1 breakdown & totals
GivenWhenThen
A basket of 2 priceable linesPOST /simulation/calculateEach line returns base/unit price, selected fare, applied taxes, and line total; the order returns subtotal/discount/tax/total and a compute timestamp
One line carries a 10% inclusive taxSame callThat tax is embedded in the price and does not increase the order total
AC-SIM-02: Line-id echo & keying
GivenWhenThen
Items each carrying a caller-supplied line idPriced via v1The result map is keyed by that id and echoes it on each line
AC-SIM-03: v2 immutable snapshot
GivenWhenThen
A basket of N linesPOST /simulation-v2/calculateReturns one order snapshot plus N line snapshots; each line carries a PRICE plus its tax entries, buyer-payable, and a per-party ledger
The order snapshotSame callRolls up subtotal, buyer-payable, the per-party ledger, and seller liability
AC-SIM-04: Preview is read-only
GivenWhenThen
An existing order with itemsPOST /sale-orders/{id}/pricing/previewThe v1 breakdown returns and the order's status and stored data are unchanged
AC-SIM-05: Empty cart refused
GivenWhenThen
An order with no itemsPriced (preview)Refused - an empty cart cannot be priced
AC-SIM-06: v2 best-effort at checkout
GivenWhenThen
Checkout pricing where v2 calculation failsOrder is pricedThe v1 result still returns and checkout proceeds; the v2 failure is logged and skipped
AC-SIM-07: Upstream rejection surfaced
GivenWhenThen
A line whose variant has no active fare setPricedThe real upstream status and reason are surfaced (not a generic 500)

PROMO - Promotions & Rules In-progress

Feature ID: pricing/PROMO · Phase: P3 · PRDs: Promotions, methods & segment rules · Dev: @nx/pricing · Promotion System

What it does for users: owners define promotion campaigns - a promotion carries exactly one method (fixed or percentage; targeting items, the order, or shipping; with an allocation strategy), a standard or buy-get scheme, and three rule sets that segment who qualifies (eligibility), what must be bought (source), and what gets discounted (target). CRUD and the aggregate (promotion + method + all rule sets) are available today; the discount calculator that applies promotions at checkout is not yet wired into the pricing pipeline.

Requirements

IDPRequirement
URD-PROMO-001MOwner can create a promotion with a method and rules in one aggregate operation.
URD-PROMO-002MOwner can update a promotion aggregate (method + rules: add/update/remove).
URD-PROMO-003MA promotion has at most one promotion method.
URD-PROMO-004SEligibility rules use the same rule model as fares (attribute, operator, value, AND logic).
URD-PROMO-005MPromotions, methods, and rules are isolated per merchant and use soft-delete.
URD-PROMO-006WPromotion discounts are applied automatically during checkout pricing (calculator pending - not yet wired).
URD-PROMO-007MA promotion declares a scheme - STANDARD (a straight discount) or BUY_GET (buy-X-get-Y) - defaulting to STANDARD.
URD-PROMO-008MThe single method declares how the discount is computed: FIXED (an amount off) or PERCENTAGE (a percent off), carrying the discount value.
URD-PROMO-009MThe method declares what the discount lands on via a target type: ITEMS, ORDER, or SHIPPING.
URD-PROMO-010SThe method declares an allocation strategy - EACH (per item), ACROSS (spread over items), or ONCE (a single item).
URD-PROMO-011SThe method can cap the number of discounted units via a maximum-quantity limit.
URD-PROMO-012MA BUY_GET method declares a source minimum quantity (what must be bought) and a target quantity (what becomes discounted).
URD-PROMO-013MMethod-level rules are split into source rules (what must be bought) and target rules (what gets discounted), tagged by context, alongside the promotion's eligibility rules.
URD-PROMO-014MEligibility, source, and target rule counts are denormalized on the promotion and method and kept accurate as rules are added or removed.
URD-PROMO-015SA promotion carries an optional code; a promotion with no code is automatic (auto-apply) only, and a code is unique per merchant among live promotions.
URD-PROMO-016SA promotion carries a stacking flag controlling whether it may combine with other promotions, and a tax-inclusive flag.
URD-PROMO-017SA promotion carries an effective-from / effective-to window, a usage limit, and a running usage count.
URD-PROMO-018MA promotion moves through a lifecycle status - DRAFT, ACTIVATED, DEACTIVATED, EXPIRED, or ARCHIVED - defaulting to DRAFT.
URD-PROMO-019MIn an aggregate update each rule entry is interpreted by shape: no id creates, id-plus-fields updates, id-only deletes; a created rule must carry attribute, operator, and data type.
URD-PROMO-020MDeleting a promotion cascades to its method and every attached rule (eligibility, source, target) in one transaction.

Acceptance

AC-PROMO-01: Promotion aggregate
GivenWhenThen
Owner submits a promotion + method + 2 rulesCreatedPromotion, its method, and both rules persist together
Owner removes one rule via aggregate updateSavedThe rule is soft-deleted; the rest are preserved
AC-PROMO-02: Buy-get with source/target rules
GivenWhenThen
A BUY_GET promotion: one PERCENTAGE method (value 100, source-min 2, target 1) + 1 source rule + 1 target ruleCreated via aggregatePromotion, method, source rule, and target rule persist together; source and target rule counts each read 1
AC-PROMO-03: Aggregate rule merge by shape
GivenWhenThen
An existing promotion with two eligibility rulesAggregate update sends one rule with id+fields, one with id only, one with no idThe first is updated, the second deleted, the third created - in one transaction; the rule count stays accurate
A created rule missing attribute / operator / data typeAggregate update savedThe whole aggregate is refused and rolled back
AC-PROMO-04: Code uniqueness & automatic
GivenWhenThen
A live promotion with code SUMMERCreating another live promotion with code SUMMERRefused - code is unique per merchant among non-deleted promotions
A promotion with no codeSavedIt is automatic (auto-apply) only

7. Constraints & Non-Goals

Cross-cutting requirements (CON) apply to all features above and are tested as TC-CON-*.

Cross-cutting requirements (CON)

IDPRequirement
URD-CON-001MAll pricing endpoints require authentication (JWT or basic auth).
URD-CON-002MAll records are scoped per merchant via the merchant header and use soft-delete.
URD-CON-003MMonetary values use decimal precision (4 places) throughout calculation.
URD-CON-004MPricing snapshots (v2) are immutable once computed.

Constraints

IDConstraint
C-01Exactly one activated fare set per product variant.
C-02At most one current (open-ended) cost per variant at a time.
C-03Taxes apply in ascending priority order; inclusive taxes never increase the total.
C-04Money math uses float(value, 4) precision.
C-05All records are merchant-isolated and soft-deleted.
C-06v1 (/simulation) and v2 (/simulation-v2) share core fare/tax logic; fixes land in both.

Non-Goals

  • Multi-currency pricing
  • A discount calculator wired into checkout (entities exist; not yet applied)
  • Order persistence, invoice issuance, stock or payment side effects

8. Version History

DateAuthorDescriptionVer
2026-06-05Claude (AI pair)Initial URD from @nx/pricing code analysis; feature spine FARE / TAX / COST / PROMOv0.1
2026-06-15Pricing squadAdded SIM feature - pricing simulation (v1 flat + v2 snapshot) & order pricing previewv0.2

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