Loyalty / customer fidelity — Nostr-native points and tiers #5

Open
opened 2026-05-10 14:49:44 +00:00 by padreug · 0 comments
Owner

Tracks the loyalty layer of #2 (full tier).

Background

DarthCoin's nudge:

There's also the part of customers fidelity, keep in mind to add it,
with loyalty points.

A regular customer should be recognized — and rewarded — without
having to install a per-restaurant app or sign up to anything. We
already have the right primitive baked in: every order can carry an
optional customer_pubkey (Nostr identity). That's the loyalty key.

Why this is interesting

  • No accounts. A customer's pubkey is the loyalty card. They
    show up at a venue, the venue sees them, points accrue. No
    registration, no email, no per-merchant identity friction.
  • Portable. A customer who's been loyal at one venue can have
    that history follow them if the loyalty events are published
    publicly (per the Nostr-native direction of the rest of the
    stack). The customer chooses what the next venue can see.
  • Wallet-native rewards. A "free coffee after 10" reward can
    pay out as a literal LNbits internal transfer / discount, not a
    paper stamp.

Data model (sketch)

loyalty_programs              — per restaurant
  id, restaurant_id, name,
  earn_rule  (e.g. "1 point per 1000 sat spent")
  is_enabled, time

loyalty_tiers                 — Bronze / Silver / Gold etc.
  id, program_id, name,
  threshold_points,
  perks_json   (free items, % discount, priority queue, etc.)

loyalty_balances              — running total per (program × pubkey)
  program_id, customer_pubkey, points, tier_id, updated_at

loyalty_events                — append-only ledger
  id, program_id, customer_pubkey, kind, points_delta,
  order_id (nullable), reason, time, nostr_event_id

kind values: earned, redeemed, expired, granted (manual
operator gift), revoked.

Balances are denormalized — derived from the events ledger so the
ledger is the source of truth and loyalty_balances is a cache.

Behavior

  • Earn — order paid → if customer_pubkey present and program
    enabled → emit earned event (points_delta = sats * earn_rate).
  • Redeem — operator (or webapp) redeems N points for a
    configured perk (free menu item, % discount on next order).
    Emits redeemed event with negative points_delta.
  • Tier transitions — recompute on every event; surface the
    current tier in the operator's order monitor and on the
    receipt.
  • Customer-facing balance — public read endpoint
    GET /api/v1/restaurants/{id}/loyalty/{customer_pubkey} returns
    {points, tier, recent_events}.

Nostr surface

  • Loyalty events optionally published as Nostr events so the
    customer's pubkey carries a portable history. Open question
    what kind / NIP — could be a custom kind for now (30404 or
    similar), or fold into NIP-15-style commerce events.
  • Tier perks could be issued as signed grant events (kind
    pending) the customer's wallet stores; redemption proves the
    grant.
  • A future cross-venue loyalty network falls out of this:
    curators publish NIP-51 lists of programs, customers see all
    their balances in one place.

Operator UX

Visible only when mode == 'full' (per #2). A new CMS page:

  • Define earn rule + tier ladder.
  • See top customers (filtered by points / by recency).
  • Manually grant points (gift, complaint compensation).
  • Redeem flow tied to an active order.

Stretch

  • Streak bonuses — visit N days in a row → bonus.
  • Referral credit — customer A's pubkey introduces customer B
    (B's first order tagged with A's pubkey) → A earns referral
    points.
  • Multi-venue rollup — same earn rules across a chain or a
    curated NIP-51 list of programs.
  • Bitcoin-direct rewards — instead of points, a tiny
    satoshi-back rebate per order (LNbits internal transfer to the
    customer's wallet).

Acceptance

  • Tables above; mode-gated to full.
  • Earn on mark_order_paid when customer_pubkey set.
  • Operator CMS page (define program + view balances + manual
    grant + redeem).
  • Public balance read endpoint.
  • Customer pubkey + tier surfaced on operator order detail
    so the waiter sees "Gold tier — 4th visit this month".
  • docs/loyalty explaining the Nostr identity story.

See also

  • #2 — tiered operator modes (parent)
  • #4 — kitchen workflow (waiter monitor surfaces tier)

References

  • Telegram conversation 2026-05-09.
Tracks the loyalty layer of #2 (`full` tier). ## Background DarthCoin's nudge: > There's also the part of customers fidelity, keep in mind to add it, > with loyalty points. A regular customer should be recognized — and rewarded — without having to install a per-restaurant app or sign up to anything. We already have the right primitive baked in: every order can carry an optional `customer_pubkey` (Nostr identity). That's the loyalty key. ## Why this is interesting - **No accounts.** A customer's pubkey is the loyalty card. They show up at a venue, the venue sees them, points accrue. No registration, no email, no per-merchant identity friction. - **Portable.** A customer who's been loyal at one venue can have that history *follow them* if the loyalty events are published publicly (per the Nostr-native direction of the rest of the stack). The customer chooses what the next venue can see. - **Wallet-native rewards.** A "free coffee after 10" reward can pay out as a literal LNbits internal transfer / discount, not a paper stamp. ## Data model (sketch) ``` loyalty_programs — per restaurant id, restaurant_id, name, earn_rule (e.g. "1 point per 1000 sat spent") is_enabled, time loyalty_tiers — Bronze / Silver / Gold etc. id, program_id, name, threshold_points, perks_json (free items, % discount, priority queue, etc.) loyalty_balances — running total per (program × pubkey) program_id, customer_pubkey, points, tier_id, updated_at loyalty_events — append-only ledger id, program_id, customer_pubkey, kind, points_delta, order_id (nullable), reason, time, nostr_event_id ``` `kind` values: `earned`, `redeemed`, `expired`, `granted` (manual operator gift), `revoked`. Balances are denormalized — derived from the events ledger so the ledger is the source of truth and `loyalty_balances` is a cache. ## Behavior - **Earn** — order paid → if customer_pubkey present and program enabled → emit `earned` event (`points_delta = sats * earn_rate`). - **Redeem** — operator (or webapp) redeems N points for a configured perk (free menu item, % discount on next order). Emits `redeemed` event with negative points_delta. - **Tier transitions** — recompute on every event; surface the current tier in the operator's order monitor and on the receipt. - **Customer-facing balance** — public read endpoint `GET /api/v1/restaurants/{id}/loyalty/{customer_pubkey}` returns `{points, tier, recent_events}`. ## Nostr surface - Loyalty events optionally **published as Nostr events** so the customer's pubkey carries a portable history. Open question what kind / NIP — could be a custom kind for now (`30404` or similar), or fold into NIP-15-style commerce events. - Tier perks could be issued as **signed grant events** (kind pending) the customer's wallet stores; redemption proves the grant. - A future cross-venue loyalty network falls out of this: curators publish NIP-51 lists of programs, customers see all their balances in one place. ## Operator UX Visible only when `mode == 'full'` (per #2). A new CMS page: - Define earn rule + tier ladder. - See top customers (filtered by points / by recency). - Manually grant points (gift, complaint compensation). - Redeem flow tied to an active order. ## Stretch - **Streak bonuses** — visit N days in a row → bonus. - **Referral credit** — customer A's pubkey introduces customer B (B's first order tagged with A's pubkey) → A earns referral points. - **Multi-venue rollup** — same earn rules across a chain or a curated NIP-51 list of programs. - **Bitcoin-direct rewards** — instead of points, a tiny satoshi-back rebate per order (LNbits internal transfer to the customer's wallet). ## Acceptance - [ ] Tables above; mode-gated to `full`. - [ ] Earn on `mark_order_paid` when `customer_pubkey` set. - [ ] Operator CMS page (define program + view balances + manual grant + redeem). - [ ] Public balance read endpoint. - [ ] Customer pubkey + tier surfaced on operator order detail so the waiter sees "Gold tier — 4th visit this month". - [ ] [[docs/loyalty]] explaining the Nostr identity story. ## See also - #2 — tiered operator modes (parent) - #4 — kitchen workflow (waiter monitor surfaces tier) ## References - Telegram conversation 2026-05-09.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/restaurant#5
No description provided.