PRD: Promotions, methods & segment rules
| Module | Pricing | PRD ID | PRD-PROMO-001 |
| Status | Ready to dev | FEAT | PROMO |
| Epic | — | Plane | BANA-1573 |
| Date | 2026-06-15 | Version | v1.0 |
| Packages | @nx/pricing | URD | PROMO |
| Surface | Client · Owner/Mgr | ||
| Owner | Phát Nguyễn | ||
What promotions, methods & segment rules are
This feature lets a merchant configure a discount campaign as one self-contained object: a promotion carries exactly one method - fixed amount or percentage; aimed at line items, the whole order, or shipping; spread per item, across items, or once - and a scheme: a straight discount (standard) or a buy-X-get-Y deal (buy-get). Three rule sets segment the campaign: eligibility decides who/when qualifies, source decides what must be bought, target decides what gets discounted. The whole graph - promotion, method, all three rule sets - is created and edited in one atomic aggregate; stored rule counts are set at create time and not yet recomputed on aggregate update.
Applying the discount at checkout is a deliberate later increment; this PRD specifies the promotion configuration aggregate - promotion, method, and three rule sets.
Why a first-class promotion object is needed
Pricing already selects fares (FARE) and computes taxes (TAX), but a merchant had no way to express a discount campaign - "10% off drinks before 5pm", "buy 2 coffees get 1 free", "50,000₫ off orders over 300,000₫". A campaign is not a fare: it spans more than one line, it can target the order or shipping rather than an item, and it must say not just how much to discount but who qualifies, what triggers it, and what it lands on.
Without a first-class promotion object an owner cannot build these offers at all, and the data needed to apply them at checkout does not exist. This increment closes the configuration gap: it gives the owner a promotion with one method, a standard or buy-get scheme, and three distinct rule sets, all authored and edited as one atomic unit - so a campaign is always saved complete or not at all, never half-built.
One example, start to finish
| Step | What happens | Result |
|---|---|---|
| An owner creates the "Buy 2 coffees, get 1 free" promotion | One request: a buy-get scheme, one percentage method valued 100 targeting items (source-min 2, target 1), one source rule (the qualifying drink), one target rule (the free drink), one eligibility rule limiting it to the dine-in channel | The promotion, its method, and all three rules are saved together in one atomic call; the rule counts read back correctly |
| Later the owner edits the campaign | An aggregate update: drop the channel rule, raise the usage limit | All changes are saved in one single atomic save |
The easiest thing to get wrong
Rule counts do not update themselves when the aggregate is edited
Eligibility / source / target rule counts are written once, at create time. A later aggregate update - adding, changing, or removing rules - does not recompute these numbers, so the displayed count can drift from the rules actually stored after an edit.