Skip to content

PRD: Entitlements - policy, target, grant & redemption

ModuleSalePRD IDPRD-ENT-001
StatusReady to devFEATENT
EpicPlaneBANA-1519
Date2026-06-15Versionv1.0
Packages@nx/saleURDENT
SurfaceSale · POS
OwnerPhát Nguyễn

TL;DR

Sell bundles and passes - coffee 10-packs, gym memberships, class bundles, metered minutes - that a customer draws down visit by visit. A policy ties a sellable variant to a quota and validity window; targets scope which items qualify; selling it mints a grant that freezes the policy at sale time so catalogue edits never alter a sold pass; each use appends a redemption to an audit ledger; reversals return quota; and the grant lifecycle (pending → active → exhausted / expired / suspended / cancelled) always tells you what's left.

Implementation status: the data model is in place - the policy, target, grant, and redemption entities (including the frozen-snapshot fields, quota total/used counters, lifecycle statuses, and redeem/reversal entry types) all exist and are persisted. The mint, redeem, and reverse behaviour described below is the target design and is not yet implemented: the entities are currently exposed only through generic create/read/update/delete, selling the entitlement variant does not yet mint a grant, and no redemption logic draws down the counter, validates scope, converts units, or reverses. The sections below describe the intended behaviour, not a shipped flow.

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 lays the entitlements foundation 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 models every use as an immutable redemption so the balance can be auditable and the sold terms honoured exactly as sold. To date only the data model has landed; the runtime that mints grants and applies redemptions is still to be built.

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 with the product's pricing (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 a unique code (like 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 confined to the user's own merchant 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.

Status: FR-1 through FR-9 and FR-14 are satisfied only at the data-model / CRUD level (the columns, statuses, and snapshot fields exist; entities are confined to the user's merchant and soft-deleted). The behavioural requirements - minting a grant on sale (FR-6, FR-7), drawing down and validating redemptions (FR-10, FR-12), unit conversion (FR-11), reversals (FR-13), and lifecycle transitions (FR-9) - are not yet implemented.

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 frozen snapshot, not from a live lookup
Tenancy & authzAll operations are confined to the user's own merchant 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 (English & Vietnamese)

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

Conceptual only - the full domain model and invariants live in the sale domain model. Cross-area references (variant, order, customer) are tracked by the system, not enforced as hard links.

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.

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 system that applies each redemption.

11. Risks & Open Questions

Risk / questionMitigation / status
Catalogue edit silently changes a sold passDesigned mitigation (not yet active) - the grant model carries a policy + targets snapshot to be frozen at sale; the minting logic that populates it is still to be built
Balance and ledger could divergeDesigned mitigation (not yet active) - the used counter is intended as the source of truth with redemptions reconciling it via append-only reversals; no runtime applies this yet
Redeem against an out-of-scope itemDesigned mitigation (not yet active) - redemption is meant to validate the item against the frozen target scope; the validation logic is not yet implemented
Customer redeems in a different unit than the quotaDesigned mitigation (not yet active) - quantity is intended to convert via the unit ratio before drawing down the counter; conversion is not yet implemented
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 confined to the user's own merchant
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.