Skip to content

PRD: Entitlements - policy, target, grant & redemption

ModuleSale (CORE-07)PRD IDPRD-ENT-001
StatusShippedOwnerSale squad
Date2026-06-15Versionv1.0
Packages@nx/sale · @nx/coreURDENT

TL;DR

Lets a merchant sell something the customer uses over time - a coffee 10-pack, a 30-day gym pass, a class bundle, a metered minutes top-up - and then redeem it down to zero. The merchant defines a policy (what is sold, its quota and validity), scopes it with targets (which items it covers), and the moment the entitlement variant is sold the system mints a grant: a per-customer balance that freezes a snapshot of the policy so later catalogue edits never touch a sold pass. Each use writes a redemption against the grant - an append-only ledger that draws down the used counter, validates the redeemed item against the frozen scope, and supports reversals that hand quota back. The grant's lifecycle (pending → active → exhausted / expired / suspended / cancelled) tells everyone exactly what the customer still has.

1. Context & Problem

A POS sells items that are consumed instantly. But merchants increasingly sell claims on future value: a prepaid drinks bundle, a membership window, a punch card, a block of service minutes. None of these fit a one-shot order line - the customer pays once and then draws the value down across many later visits, each of which must check "is there balance left, is it still valid, does it cover this item?".

Without a first-class model for this, a merchant fakes it with manual notes or discount codes - no balance tracking, no expiry, no audit of who used what when, and no protection against a sold bundle silently changing when the catalogue is edited. The result is disputes, leakage, and no way to answer "how many sessions does this customer have left?".

This increment ships an entitlements system as a self-contained part of the Sale module. It separates the template a merchant configures (policy + targets) from the sold instance a customer holds (grant), and records every use as an immutable redemption so the balance is always auditable and the sold terms are always honoured exactly as sold.

2. Goals & Non-Goals

Goals

  • A policy that turns a sellable variant into an entitlement with a quota axis, a validity axis, an activation mode, and declared limit dimensions.
  • Targets that scope which items a policy's grant may be redeemed against - empty scope means open access.
  • A grant minted when the entitlement variant is sold, carrying a unique code, a quota balance, a resolved validity window, and a frozen policy snapshot so catalogue edits never alter a sold grant.
  • A redemption ledger that draws the grant's used counter down, validates the redeemed item against the frozen scope, and supports reversals that return quota.
  • A grant lifecycle - pending, active, exhausted, expired, suspended, cancelled - that always states what the holder still has.

Non-Goals

  • The pricing of the entitlement variant - the price lives in fares (Product); this PRD covers what is granted, not what it costs.
  • Payment capture and order checkout - owned by Orders and Payment; a grant is minted off a completed sale, it does not run the sale.
  • Loyalty point earning (PNT) - a separate reward mechanic; entitlements are sold balances, not earned points.
  • A customer-facing wallet UI to browse remaining balances - the balance is recorded and queryable; a dedicated end-user screen is a later increment.

3. Success Metrics

MetricTarget / signal
Balance accuracyA grant's available balance equals quota total minus the sum of its redemptions at all times
Snapshot integrityA policy edited after a sale never changes the terms of an already-sold grant
Scope safetyA redemption against an item outside the grant's frozen scope is refused (unless scope is open)
Audit completenessEvery quota change traces to an append-only redemption entry - no silent balance edits
Lifecycle honestyA fully-used grant reads EXHAUSTED; one past its window reads EXPIRED

4. Personas & Use Cases

PersonaGoal in this feature
Owner / ManagerConfigure what is sold as an entitlement - its quota, validity, scope - and read remaining balances
CashierSell an entitlement and redeem a customer's balance at the point of sale in a tap
CustomerBuy a bundle / pass once and draw it down across many visits, confident the terms won't change

Core scenario: an owner configures a "10 Coffees" policy on the matching variant - quota 10 cup, no expiry, activates on purchase, scoped to the coffee category via targets. A customer buys it; the system mints a grant with code ENT-…, balance 10, status ACTIVE, and freezes the policy + its targets into the grant. On each later visit the cashier redeems one cup: a redemption draws the used counter to 1, 2, … 10; at 10 the grant flips to EXHAUSTED. A mistaken redeem is corrected with a reversal that returns one cup and reopens the balance.

5. User Stories

  • As an owner, I declare a sellable variant as an entitlement with a quota and an optional expiry, so I can sell bundles and passes.
  • As an owner, I scope an entitlement to the items it covers, so a coffee pass can't be spent on food.
  • As an owner, I trust that editing the catalogue later never changes a pass a customer already bought.
  • As a cashier, I redeem one use of a customer's balance in a tap, and see what's left.
  • As a cashier, I reverse a redemption I made by mistake, and the balance comes back.
  • As a customer, I buy once and draw the value down over many visits, and the pass expires only when its window says so.

6. Functional Requirements

#RequirementURD ref
FR-1A policy binds 1:1 to a sellable variant and declares its limit dimensions (count / time-window / metered)URD-ENT-001..002
FR-2A policy carries an optional quota (amount + unit) and an optional validity duration; either axis may be absentURD-ENT-003
FR-3A policy declares an activation mode - on purchase, on first use, or scheduled - governing when validity startsURD-ENT-004
FR-4A policy may require an attached customer, or allow a bearer entitlementURD-ENT-005
FR-5Targets scope a policy to specific items; an empty target set means open accessURD-ENT-006..007
FR-6Selling the entitlement variant mints a grant with a unique code, quota balance, and resolved validity windowURD-ENT-008
FR-7The grant freezes a snapshot of the policy and its targets at sale time, isolating it from later catalogue editsURD-ENT-009
FR-8A grant tracks quota total vs used (used counter is source of truth; available = total − used)URD-ENT-010
FR-9A grant moves through pending → active → exhausted / expired / suspended / cancelledURD-ENT-011
FR-10A redemption draws the used counter down for a quantity and is captured against its consuming order / itemURD-ENT-012 · URD-ENT-016
FR-11A redemption quantity in a different unit from the grant's quota unit is converted via the unit ratioURD-ENT-013
FR-12A redemption validates the redeemed item against the grant's frozen target scope (open when no targets)URD-ENT-014
FR-13A reversal redemption returns quota to the grant; the ledger is append-only - corrections are new entriesURD-ENT-015
FR-14Every policy, target, grant, and redemption is merchant-scoped and soft-deletedURD-ENT-017

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

7. Non-Functional Requirements

AreaRequirement
Source of truthThe grant's used counter is authoritative for balance; redemptions are the reconciling audit ledger behind it
ImmutabilityThe redemption ledger is append-only; a correction is a reversal entry, never an edit or delete of a prior redeem
Snapshot isolationA grant's terms come from the policy snapshot frozen at sale - never re-read live - so catalogue edits cannot change a sold grant
Scope from snapshotRedemption validates targets from the grant's denormalized snapshot, not a live target lookup
Tenancy & authzAll operations scoped per merchant (x-merchant-id) and gated by entitlement permissions
PrecisionQuota and redemption quantities use decimal precision; cross-unit redemptions convert via the unit ratio
i18nPolicy name and description are bilingual ({ en, vi })

8. UX & Flows

The configuration surface lets an owner define a policy on the entitlement variant (quota, validity, activation, scope via targets). The point-of-sale surface sells the entitlement, looks up a customer's grant, shows the remaining balance, redeems a use, and reverses one when needed.

9. Data & Domain

EntityRole
EntitlementPolicyThe template a merchant configures - bound 1:1 to a sellable variant; carries quota, validity, activation mode, required-customer flag, and limit dimensions
EntitlementTargetA scope binding of a policy to a specific item (variant by default), ordered; empty scope = open access
EntitlementGrantThe sold instance held by a customer - unique code, quota total / used balance, validity window, lifecycle status, and a frozen policy + targets snapshot
EntitlementRedemptionAn append-only ledger entry against a grant - a redeem or a reversal, with quantity, unit, and the consuming order / item

Conceptual only - full schema and invariants live in the sale domain model. Cross-schema references (variant, order, customer) are soft references; integrity is enforced in the service layer.

10. Dependencies & Assumptions

Depends on

  • Sale orders (ORD) - a grant is minted off a completed sale of the entitlement variant; redemptions reference the consuming order / item.
  • Product (Product) - a policy binds to a sellable variant, and targets scope to catalogue items.
  • Units of measure (Inventory) - quota and redemption quantities carry a unit; cross-unit redemptions convert via the unit ratio.
  • @nx/core - the policy / target / grant / redemption models and merchant-scoped schemas.

Assumptions

  • The merchant configures a policy before selling its variant; selling an unconfigured variant yields no grant.
  • A customer is identified when a policy requires one (named entitlement); bearer entitlements may omit the customer.
  • The quota counter on the grant and the redemption ledger are kept consistent by the service that applies each redemption.

11. Risks & Open Questions

Risk / questionMitigation / status
Catalogue edit silently changes a sold passResolved - the grant freezes a policy + targets snapshot at sale; terms are read from the snapshot, never live
Balance and ledger could divergeThe used counter is the source of truth; redemptions reconcile it; reversals (not edits) keep the ledger append-only
Redeem against an out-of-scope itemRefused - redemption validates the item against the frozen target scope; open access only when the snapshot has no targets
Customer redeems in a different unit than the quotaQuantity is converted via the unit ratio before drawing down the counter
Per-day redemption capsOut of scope this increment - a per-day cap field is carried on the policy for a future runtime check

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - ENT in the URD feature catalog
RolloutAll merchants; no feature flag
MigrationNew tables only (policy, target, grant, redemption); no change to existing order flows
Launch criteriaSelling the variant mints a grant with a frozen snapshot; redeem draws the balance down and exhausts at zero; reversal returns quota; out-of-scope redemptions are refused; everything is merchant-scoped
MonitoringGrant balance vs ledger reconciliation, redemption refusal rate by reason (out of scope, exhausted, expired), grants by lifecycle status

13. FAQ

What can I sell as an entitlement? Anything the customer draws down over time - a drinks bundle, a class pass, a membership window, a block of metered minutes. You bind a policy to the variant and set its quota and validity.

Does editing the policy later change passes already sold? No. Each grant freezes a snapshot of the policy and its scope at sale time and reads its terms from that snapshot, so a catalogue edit never alters a sold grant.

How is the remaining balance tracked? The grant carries a used counter (the source of truth); every use appends a redemption to an audit ledger that reconciles it. Available is the quota total minus what's used.

Can a customer spend a coffee pass on food? Only if the policy is scoped to allow it. A redemption checks the item against the grant's frozen target scope; an empty scope means open access, otherwise out-of-scope items are refused.

What if a cashier redeems by mistake? They record a reversal - an append-only correction that returns the quota. The original redeem is never edited or deleted.

When does a grant expire or exhaust? It reads EXHAUSTED once the used counter reaches the quota total, and EXPIRED once it passes its validity window - the window starting on purchase, first use, or a scheduled date per the policy's activation mode.

References

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