Skip to content

PRD: Loyalty points at order

ModuleSalePRD IDPRD-PNT-001
StatusReady to devFEATPNT
EpicPlaneBANA-1559
Date2026-03-23Versionv1.0
Packages@nx/saleURDPNT
SurfaceBackend
OwnerPhát Nguyễn

TL;DR

When a customer-linked order is fully paid, the customer automatically earns loyalty points from the order total at a per-merchant conversion rate. Each award fires once per order, posts to a read-only ledger, and rolls into the customer's running balance - no extra step for the cashier.

1. Context & Problem

Orders can already be linked to a customer (URD-ORD-014) and reach a terminal COMPLETED state on full payment (URD-ORD-011), but nothing accrues on that relationship - there is no way to reward repeat purchase. Merchants who want a simple "spend X, earn a point" loyalty mechanic have to track it off-system or not at all, which is unworkable for the HKD/SME retail and F&B venues BANA targets.

This PRD closes that gap: fully-paid, customer-linked orders earn points automatically off the payment-success path - one idempotent ledger entry per order, reflected in a running per-customer balance.

2. Goals & Non-Goals

Goals

  • Earn points for the customer when an order is fully paid with a customer attached, hooked into both the order and the per-check payment-success paths.
  • Compute the award from the order total against a per-merchant conversion rate, floored to whole points.
  • Make the award idempotent per order - a replay of the same completed order awards nothing extra.
  • Track a point balance per customer per merchant, incremented together with the ledger entry as a single atomic write.
  • Record each award as a read-only point ledger entry (earn) carrying the order, points, and conversion rate.

Non-Goals

  • Point redemption / spending - only the earn entry type exists in this increment.
  • Refund / return reversal of awarded points (refund flow is itself a module Non-Goal).
  • Tiers, expiry, multipliers, promotions, or campaign-driven bonus points.
  • A customer-facing UI for the balance or ledger.

3. Success Metrics

MetricTarget / signal
CoverageCustomer-linked, fully-paid orders that produce a point award (where a conversion rate is configured)
IdempotencyZero duplicate awards - at most one ledger entry per order
Balance integrityCustomer balance always equals the sum of that customer's earn ledger entries
AdoptionMerchants that configure a conversion rate and accrue points

4. Personas & Use Cases

PersonaGoal in this feature
CustomerAccumulate points automatically on every completed purchase
CashierTake payment as usual; points accrue with no extra step
OwnerRun a simple earn-based loyalty program scoped to each merchant

Core scenario: cashier attaches a customer → takes full payment (order or final check) → system floors the order total by the conversion rate to whole points, records one earn ledger entry, and increments the balance - idempotently, even on replay.

5. User Stories

  • As a customer, I want to earn points when my order is fully paid, so that repeat purchases build a balance I can later be rewarded for.
  • As a cashier, I want points to accrue automatically on payment, so that I don't have to remember a manual loyalty step.
  • As an owner, I want to set a per-merchant conversion rate, so that I control how much spend earns a point.
  • As an owner, I want a given order to award points only once, so that a replayed payment event can't inflate balances.
  • As an owner, I want a read-only ledger of awards, so that every point in a customer's balance is traceable to an order.

6. Functional Requirements

#RequirementURD ref
FR-1Earn points for the customer when an order becomes fully paid with a customer attached, triggered from both the order and the per-check payment-success pathsURD-PNT-001
FR-2Compute points as the order total divided by the conversion rate, rounded down to whole points; skip when the result is ≤ 0URD-PNT-001
FR-3Read the per-merchant point conversion rate setting; skip when no setting exists or the rate is non-positive; default 1000URD-PNT-001
FR-4Award is idempotent per order - short-circuit if an award already exists for the orderURD-PNT-002
FR-5Record the award as a point ledger entry (earn) carrying the order, points, and conversion rateURD-PNT-001
FR-6Increment the customer's point balance and record the ledger entry together as a single atomic write; roll back on errorURD-PNT-003
FR-7Expose the ledger read-only (list, get by id, get one, count), confined to the user's own merchant; awards are never written by a clientURD-PNT-001..003

Full requirement text and acceptance criteria live in the Orders URD. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Data integrityLedger entry and balance increment are written together as a single atomic write - no balance change without a matching ledger entry, and vice versa
IdempotencyAt most one award per order regardless of how many times the payment-success event fires
ImmutabilityThe ledger is written only via the payment-success path and is read-only for clients; entries are not edited by clients
Tenancy & authzAll operations confined to the user's own merchant; ledger reads gated by point-ledger read permissions
PrecisionOrder total read with 4-decimal precision; points are whole numbers
i18nUser-facing labels/statuses are bilingual (English & Vietnamese)

8. UX & Flows

The award path has no dedicated UI - it runs in the background off payment success. The read-only ledger and per-customer balance are consumed by Orders/Customer surfaces in the front end.

9. Data & Domain

EntityRole
Point ledger entryImmutable ledger entry (earn type) - the order, points, conversion rate, customer & merchant scope
Customer point balanceRunning per-customer per-merchant balance, incremented atomically with each award
Point conversion rate settingPer-merchant conversion rate setting; default 1000
OrderSource of the order total and the customer link that drives the award

Conceptual only - full schema and invariants in the sale domain model.

10. Dependencies & Assumptions

Depends on

  • Sale Order lifecycle (URD-ORD-011) - the award fires on the full-payment transition.
  • Customer link (URD-ORD-014) - only customer-linked orders earn points.
  • Check splitting (URD-CHK) - sale-check payment success is a second trigger path.
  • Point conversion rate setting - supplies the per-merchant conversion rate.

Assumptions

  • The order carries a settled total at payment-success time.
  • A merchant that wants points configures the conversion rate; absent that, the award is skipped (default 1000 applies only when the setting exists without a value).

11. Risks & Open Questions

Risk / questionMitigation / status
Duplicate awards from replayed payment eventsIdempotency short-circuit keyed on the order
Ledger and balance could diverge on partial failureBoth written together as a single atomic write; rolled back on error
Refund / return after points awardedOut of scope - no reversal yet; refund is a module Non-Goal
No redemption pathAccepted for this increment; only earn exists, redemption is a future increment
Merchant without a configured rate earns nothingBy design - award is skipped on missing config or non-positive rate

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - see URD feature catalog
RolloutAll merchants; no feature flag (inert until a merchant sets a conversion rate)
MigrationNew point ledger entry and a customer point balance
Launch criteriaFull payment on a customer-linked order awards the correct floored points; replay awards nothing extra; balance equals ledger sum; reads are merchant-scoped and read-only
MonitoringAward volume per merchant, idempotency-skip rate, balance-vs-ledger consistency checks

13. FAQ

When exactly are points awarded? When a customer-linked order becomes fully paid - via either the order or the per-check payment-success path.

How many points does an order earn? The order total divided by the conversion rate, rounded down to whole points, where the conversion rate is the merchant's setting (default 1000). Sub-point remainders are dropped.

What if the merchant hasn't configured a rate? No points are awarded - the award is skipped on a missing config or a non-positive rate.

Can a replayed payment double-award? No - the award is idempotent per order; a second attempt for the same order is short-circuited.

Can customers spend or redeem points? Not in this increment - only the earn entry type exists. Redemption, tiers, and expiry are future work.

Can a client write to the ledger? No - the ledger is read-only; awards are written solely by the payment-success path.

References

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