Skip to content

PRD: Points Earning

ModuleLoyaltyPRD IDPRD-PTS-001
StatusReady to devFEATPTS
EpicPlaneBANA-1509
Date2026-03-23Versionv0.1
Packages@nx/saleURDPTS
SurfaceBackend
OwnerPhát Nguyễn

TL;DR

Every paid, customer-linked order earns loyalty points automatically: the balance grows the instant payment completes, at a per-merchant conversion rate. Each order is awarded exactly once as an auditable ledger entry - no manual entry, a verifiable point trail.

1. Context & Problem

The Loyalty module turns purchases into points so customers come back. Until now, paid orders left no loyalty trace - no ledger, no balance, no link from an order to the points it should have earned. Customers got nothing for their spend, and merchants had no foundation for redemption, tiers, or rewards.

Points earning is the first slice (feature PTS, phase P1): a paid, customer-linked order adds points to that customer's balance automatically, once, at a per-merchant conversion rate. It builds on customer identity (which holds the balance) and the order/payment flow (which signals completion), and is the prerequisite for every later loyalty capability.

2. Goals & Non-Goals

Goals

  • Award points automatically on a paid, customer-linked order - no manual entry.
  • Make each award idempotent per order so a duplicate or redelivered payment signal never double-awards.
  • Read the rate from per-merchant configuration: no config entry means no earning at all; a built-in default applies only when the entry exists but carries no value.
  • Record each award as a ledger entry and increment the customer's balance in one atomic step.
  • Offer a read-only way to list and look up ledger entries, scoped to the user's own merchant.

Non-Goals

  • Redemption, tiers, and the rewards catalogue - feature RDM (phase P2, URD-RDM).
  • Cross-merchant / franchise-wide point pooling (URD Non-Goal).
  • Marketing messages on earn - owned by Marketing.
  • Points for anonymous sales - only identified customers accrue (constraint C-01).

3. Success Metrics

MetricTarget / signal
Coverage100% of paid, customer-linked orders award points (where a config entry exists and the rate is positive)
IdempotencyZero double-awards - at most one ledger entry per order
AccuracyBalance equals the sum of the customer's earning entries
LatencyAward completes inside the payment-confirmation path without delaying order completion

4. Personas & Use Cases

PersonaGoal in this feature
CustomerEarn points automatically and watch the balance grow
OwnerSet the rate and trust that every paid order accrues correctly
ManagerReview member point activity and balances

Core flow: a paid, customer-linked order → read the merchant's rate → compute points from the order total → record one ledger entry and increment the balance, once per order.

5. User Stories

  • As a customer, I want points added automatically when my order is paid, so I'm rewarded without lifting a finger.
  • As an owner, I want the rate configured per merchant, so I control how spend converts to points.
  • As an owner, I want duplicate payment signals to never double-award, so the ledger stays trustworthy.
  • As a manager, I want to list a customer's ledger entries, so I can see how their balance was earned.

6. Functional Requirements

#RequirementURD ref
FR-1Award points when a customer-linked order completes payment; the same logic runs on both payment paths - standard order payment and table-check paymentURD-PTS-001
FR-2Idempotent per order - short-circuit if the order was already awardedURD-PTS-002
FR-3Rate read from per-merchant configuration: no config entry means no points; the built-in default applies only when the entry exists but carries no valueURD-PTS-003
FR-4Points = order total ÷ rate, rounded down to whole points; a non-positive rate or a zero result is skipped (logged, no award)URD-PTS-001
FR-5In one atomic step: record a ledger entry (carrying the points, the rate, and the originating order) and increment the balance; roll back together on errorURD-PTS-001..002
FR-6Read-only listing and look-up of ledger entries, scoped to the user's own merchantURD-PTS-001

Full requirement text and acceptance criteria live in the Loyalty URD; this PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Data integrityLedger entry and balance increment are written together atomically - no balance change without a matching entry
IdempotencyAt most one entry per order, enforced by checking whether the order was already awarded
Tenancy & authzAll operations scoped to the user's own merchant; the read-only listing requires the loyalty viewing permission
PrecisionMoney and point math use fixed decimal precision; points are floored to whole units
PerformanceAward runs inside the payment-confirmation path without delaying order completion
i18nUser-facing labels and statuses are bilingual (English / Vietnamese)

8. UX & Flows

Points earning has no dedicated UI - it runs behind the scenes on payment completion. The balance surfaces in the customer profile; ledger entries are available through the read-only listing.

9. Data & Domain

ConceptRole
Point ledger entryAppend-only earning record - the points, the rate, and the originating order, scoped to the merchant
Customer point balanceThe running balance, incremented atomically with each award
Per-merchant conversion rateThe configured rate for the merchant; a default applies when the entry is empty

Conceptual only - full schema and invariants in the developer customer-points docs and the sale domain model.

10. Dependencies & Assumptions

Depends on

  • Customer identity (Customer) - holds the balance the award increments.
  • Orders / payment (Orders) - order completion on the payment path is the trigger.
  • Per-merchant configuration - supplies the rate; required (no config entry → no earning).

Assumptions

  • The order is linked to an identified customer (anonymous sales earn nothing, C-01).
  • The merchant has a config entry (earning is skipped entirely without one); the built-in default applies only when that entry is empty.
  • Payment-completion signals fire reliably for each paid order.

11. Risks & Open Questions

Risk / questionMitigation / status
Duplicate / redelivered payment signals could double-awardIdempotent per order - check whether the order was already awarded before awarding (C-02)
Award and balance increment could diverge on partial failureBoth written atomically; rolled back together on error
A misconfigured (non-positive) rate could award garbageNon-positive rate or zero points is skipped and logged, never awarded
Refund / cancellation after earnOpen: define the clawback / reversal path for earned points

12. Release Plan & Launch Criteria

AspectPlan
PhaseP1 (foundation) - see URD feature catalog
RolloutAll merchants, no feature flag - the per-merchant config entry is the de facto opt-in (no entry → no earning)
MigrationNone (new ledger; rate from configuration)
Launch criteriaPaid customer-linked order awards once; balance matches the ledger; duplicate signal doesn't double-award; rate read per merchant
MonitoringAward volume per merchant, double-award detection (orders with more than one entry), balance-vs-ledger consistency checks

13. FAQ

When exactly are points awarded? When a customer-linked order completes payment - the same logic runs whether confirmation arrives via the standard order-payment or the table-check path.

Can the same order award points twice? No - earning is idempotent per order. If an entry already exists for the order, the award is skipped.

Where does the rate come from? From the per-merchant conversion-rate setting. A config entry must exist for earning to run at all; the built-in default applies only when that entry exists but carries no value.

How are points calculated? Order total ÷ rate, rounded down to whole points. A non-positive rate or a zero result is skipped (logged, no award).

Do anonymous sales earn points? No - only identified customers accrue (constraint C-01).

Can I redeem points yet? Not in this increment. Redemption, tiers, and the rewards catalogue are the separate RDM feature (phase P2, URD-RDM).

References

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