befday Points Currency
Setel/Mesra-style spendable points — earned proportionally to spend across the merchant network (plus referrals), redeemed via a reward catalog or converted to merchant-funded credit/discount.
Status: Accepted (direction); implementation deferred
Date: June 2026
Decision: Evolve the platform-level befday card from a single completion card into a spendable points currency, modelled on Setel/Mesra. Points are earned proportionally to spend across the befday merchant network (plus referrals/sign-ups), accumulate as a balance, and are redeemed two ways: a reward catalog or converted to credit/discount applied at checkout. Credit-converted discounts are merchant-funded. Referral loop, verified-phone anti-fraud, and idempotency from Platform Stamps & Referral carry forward.
Supersedes: Platform Stamps & Referral — replaces the fill-to-goal → one birthday perk mechanic with a points-balance currency.
TL;DR
The befday card becomes a points wallet. Points are earned proportionally to spend across the network (plus referrals/sign-ups), held as a balance, and redeemed via a reward catalog or converted to merchant-funded credit at checkout. Per-merchant stamp cards stay unchanged.
Context
Platform Stamps & Referral introduced a befday card: a weighted points ledger that fills to a goal and unlocks one birthday perk. Reviewing Setel.my (Petronas) reframed it:
- In Setel, every purchase earns points proportional to spend.
- Points are a currency — redeemable for catalog rewards or converted to credit/discount at checkout.
For a multi-merchant platform this is the key unlock: points earned at café A become spendable at restaurant B, turning siloed loyalty into a network-wide currency. The befday card becomes the points wallet, not a punch card.
Decision
The befday card is a points wallet. Points are earned proportionally to qualifying spend (plus referrals/sign-ups), held as a balance, and redeemed via a reward catalog or converted to merchant-funded credit/discount at checkout.
Earning
| Source | Basis | Notes |
|---|---|---|
| Purchase (primary) | Proportional to spend (e.g. X pts / RM1) | Earned on paid orders at befday merchants |
referral |
Fixed bonus (verified referee) | Carried from Platform Stamps; idempotent per (A, B) |
signup |
Fixed bonus (verified new user) | Carried from Platform Stamps; idempotent per user |
- Earn rate is config (
store_settings-style): a base network rate, optionally overridable per merchant for promos. - Purchase points issue inside the existing
orders.paytransaction (alongside the per-merchant stamp), only when the order’sstore_customeris linked to a befdayuser_id. - Per-merchant stamp cards remain unchanged and run alongside the currency (stamps = visit-based reward at one shop; points = spend-based currency across the network).
Redemption (two paths — full Setel model)
- Reward catalog — spend points on vouchers, birthday gifts, or partner items at a points price.
- Credit / discount — convert points to credit at a fixed redemption rate (e.g. 100 pts = RM1) and apply as a discount line at checkout.
Both paths debit the same balance via the ledger. Redemption is server-verified and idempotent.
Funding
Credit-converted discounts are merchant-funded — the merchant absorbs the discount like a voucher (it lands as a discount line on the order, settled in their books). This bounds befday’s liability and aligns cost with the merchant getting the sale.
Implication: the POS/order flow records point-redemption discounts as a merchant-funded discount line (distinct from
voucher_discount_centsandmanual_discount_cents), so settlement and analytics attribute it correctly.
Anti-fraud & integrity (carried + extended from Platform Stamps)
- Referral/sign-up points: verified phone only, idempotent per
(referrer, referee)/ per user; self-referral blocked. - Purchase points: bound to a real paid order, idempotent per
order_id(mirrorsorders.stamp_issued). - Redemption: balance check inside a transaction; cannot spend more than held; idempotent per redemption.
- Negative-balance impossible — debits are atomic against the current balance.
Points-as-currency: liabilities to design for
Points convert to money-equivalent credit, so this is a financial instrument, not just a game mechanic. Decide explicitly:
- Earn rate (points per RM) and redemption rate (points per RM credit) — the spread defines program cost.
- Expiry — points should expire (e.g. 12–18 months of inactivity) to bound liability and model breakage (unredeemed points).
- Outstanding-liability tracking — the sum of unredeemed balances is money owed (by merchants, under merchant-funded model); surface it in analytics.
- Rounding — define rounding for partial redemptions to avoid penny-shaving exploits.
- Min/max redemption per order, to bound abuse and protect merchant margins.
Data Model Impact (sketch)
Evolves the Platform Stamps tables into a wallet + ledger. Keyed by user.id.
befday_wallets (replaces befday_cards)
| Column | Type | Notes |
|---|---|---|
id |
uuid | PK |
user_id |
text | FK → user.id, unique |
points_balance |
integer | current spendable balance (never negative) |
lifetime_earned |
integer | denormalized, for tiers/analytics |
updated_at |
timestamp |
befday_point_events (ledger — earn and spend)
Generalizes the Platform Stamps ledger to signed entries (credits earn, debits redeem).
| Column | Type | Notes |
|---|---|---|
id |
uuid | PK |
wallet_id |
uuid | FK → befday_wallets.id |
direction |
enum | earn | redeem |
event_type |
enum | purchase | referral | signup | catalog_redeem | credit_redeem | expiry |
points |
integer | positive magnitude; sign implied by direction |
order_id |
uuid | nullable FK → orders.id (for purchase earn and credit_redeem) |
referrer_user_id |
text | nullable FK → user.id |
referee_user_id |
text | nullable FK → user.id |
catalog_item_id |
uuid | nullable FK → befday_reward_items.id (for catalog_redeem) |
expires_at |
timestamp | nullable — for earn lots subject to expiry |
issued_at |
timestamp |
Idempotency: purchase unique per (order_id); referral unique per (referrer_user_id, referee_user_id); signup unique per (referee_user_id); redemptions unique per redemption request id.
befday_reward_items (catalog)
| Column | Type | Notes |
|---|---|---|
id |
uuid | PK |
name |
text | |
points_price |
integer | points required |
kind |
enum | voucher | gift | partner |
is_active |
boolean |
orders extension (merchant-funded credit)
Add a discount line so point→credit redemption is attributable and settled merchant-side:
| Column | Type | Notes |
|---|---|---|
points_discount_cents |
integer | default 0 — merchant-funded discount from redeemed pts |
Total formula becomes: total = subtotal − manual_discount − voucher_discount − points_discount + tax.
Earn rate / redemption rate live in config (extend store_settings or a new befday_program_settings).
API Impact (sketch)
| Procedure | Status | Notes |
|---|---|---|
consumer.wallet.get |
New | Balance, lifetime earned, expiring-soon summary |
consumer.wallet.history |
New | Ledger entries (earn/redeem) |
consumer.rewards.catalog |
New | List catalog items + points prices |
consumer.rewards.redeem |
New | Redeem catalog item (debits balance, idempotent) |
pos.points.preview |
New | At checkout: look up balance, show max credit redeemable |
pos.points.applyCredit |
New | Convert points → points_discount_cents on the order (merchant-funded) |
(internal) grantPurchasePoints |
New | Idempotent earn inside orders.pay (per order_id) |
consumer.referral.* |
Carry | From Platform Stamps — unchanged |
consumer.befdayCard.* |
Replaced | By consumer.wallet.* above |
Earning/redeeming are server-side, transactional, idempotent, mirroring stamp + voucher patterns.
Consequences
| Type | Consequence |
|---|---|
| Pro | Network-wide spendable currency — earn at one merchant, redeem at another. This is the core platform unlock. |
| Pro | Two redemption paths (catalog + credit) match Setel and maximize perceived value/flexibility. |
| Pro | Merchant-funded credit bounds befday’s liability and aligns cost with the merchant getting the sale. |
| Pro | Reuses verified-phone anti-fraud, transactional issuance, and the ledger pattern already in the stack. |
| Con | Points-as-currency is a financial instrument — needs earn/redeem rates, expiry, breakage, rounding, liability tracking. |
| Con | Requires POS + order changes (new points_discount_cents line, balance lookup, apply-credit flow) — heavier than Platform Stamps. |
| Con | Merchant settlement/analytics must correctly attribute merchant-funded point discounts. |
| Con | “Redeem at any merchant” raises cross-merchant funding fairness (who funds points earned at A but spent at B?) — see Open Questions. |
Resolved Decisions
| Question | Decision |
|---|---|
| Relationship | Evolve the befday card (Platform Stamps) into a points currency |
| Earning basis | Proportional to spend (+ carried referral/sign-up bonuses) |
| Redemption | Both — reward catalog and convert to credit/discount |
| Funding | Merchant-funded credit/discount (like a voucher) |
| Per-merchant stamps | Kept — run alongside the currency, unchanged |
| Anti-fraud | Verified phone, transactional idempotent earn/redeem, no negative balance |
Open Questions
- Earn & redemption rates: points per RM earned, and points per RM credit — define the spread (program cost).
- Cross-merchant funding fairness: ✅ Resolved in Cross-Merchant Points Funding — launch constrained Model 1 (earning-merchant-only redemption + caps), architect for Model 2 (earner pre-funds a pool), reserve Model 3 (befday float).
- Expiry & breakage: expiry window (e.g. 12–18 months) and how breakage is modelled/reported.
- Catalog ownership: who funds catalog rewards (platform, merchant, partner)?
- Min/max redemption per order: caps to protect merchant margin and bound abuse.
- Birthday hook: does the birthday still grant something special (bonus points, multiplier, exclusive catalog item) to keep befday’s brand identity?
- POS without payments gateway: point-credit is a discount line (works with cash today); confirm no gateway dependency for v1.