RPG Loyalty System — Design Summary
Concept
A gamified membership program modeled on tabletop RPG progression. The store functions as a quest hub. Members earn XP through purchases, event attendance, and real-world quests. The goal is healthy gamification: get people into the shop, spending money, making friends, doing things outside. No dark patterns.
XP System
Source: BECMI (Basic/Expert/Companion/Master/Immortal)
BECMI Fighter XP table chosen over B/X for its higher level cap (36 vs ~14), providing years of progression runway.
| Level | Cumulative XP | Notes |
|---|---|---|
| 1 | 0 | Starting level |
| 2 | 2,000 | |
| 3 | 4,000 | |
| 4 | 8,000 | |
| 5 | 16,000 | |
| 6 | 32,000 | |
| 7 | 64,000 | |
| 8 | 120,000 | |
| 9 | 240,000 | Name Level |
| 10–36 | +120,000/level | Linear tail |
All values are cumulative totals. The curve doubles through early levels (fast initial progression), then flattens to a fixed 120,000 XP per level from 10 onward (steady endgame grind).
XP Earn Rates
Base rate: $1 spent = 100 XP
| Source | XP Range | Analogy |
|---|---|---|
| Purchases | 100 XP per $1 (base) | Gold-for-XP (treasure) |
| Event attendance | 500–1,500 flat | Session XP |
| Easy quests (e.g., photo of a dog) | ~50 XP | Kobold encounter |
| Hard quests (e.g., visit 5 local businesses) | ~2,000 XP | Dragon encounter |
| Streaks, referrals, seasonal challenges | Variable | Story awards |
Target ratio: ~3:1 or 4:1 purchase XP to activity XP, matching B/X treasure-to-monster ratios in actual play. Most XP comes from spending, but quests and events provide meaningful acceleration.
What does NOT earn XP
Not every dollar or action should count. RC is explicit: “salaries don’t count, only money obtained during dangerous or challenging experiences.” Magical item sales cap at 10% of creation cost, not full value. Applied to our system:
| Action | Earns XP? | Reasoning |
|---|---|---|
| Purchase at shop | Yes (base × multiplier) | The core “treasure” mechanic |
| Membership subscription fee (monthly) | No | Passive renewal; “salary-equivalent” |
| Membership upgrade (tier change) | One-time XP on upgrade only | First payment at new tier counts; subsequent renewals do not |
| Store credit redemption | No | Already-earned money being spent; would double-count |
| Gift card purchase (for self) | No | Prepaid; XP awarded when the credit is used on a real purchase |
| Gift card purchase (for someone else) | Yes (buyer earns on purchase, recipient does not) | The buyer spent money; the recipient didn’t |
| Refunds | Negative points AND negative XP — both reversed, level may drop | If XP didn’t decrease, big-purchase-then-refund is a trivial exploit to level up for free. See “Refund Handling” below for mechanics. |
| Check-in at kiosk | Yes (flat, dedup’d) | Deliberate action, requires presence |
| Event attendance | Yes (flat) | Deliberate action |
| Quest completion | Yes (variable) | Deliberate effort |
| Browsing the portal / opening the app | No | Not a meaningful action |
Every ambiguity here is a future “why did X get more points than me” complaint. Make the exclusions explicit in member-facing docs when we ship them.
Discretionary XP Bonuses (Exceptional Action)
Adapted from Rules Cyclopedia: DMs can award small, capped, discretionary XP bonuses for things the rules don’t cover — exceptional role-play, clever solutions, heroic sacrifice, exceptional skill use. Amount is 1/20 of the points needed to reach the next level, max once per session.
Retail translation
Staff can award a discretionary XP bonus from the admin panel for cultural contributions the automated system can’t see:
- Helped a newcomer learn a game (Magic, D&D, a new board game) at the shop
- Brought a friend who signed up as a member
- Handled a customer-service moment well (even for someone else)
- Contributed to a shop event beyond what was asked
- Showed up to clean after an event
- Any other qualitative contribution staff notices on the floor
Mechanics
- Amount: 5% of the XP gap to the member’s next level, calculated at award time (matches RC’s 1/20).
- Cap: one bonus per member per month (the RC “one per session” rule, adapted to our cadence where monthly is a reasonable session-equivalent).
- Award authority: Staff role only (not GM, not Member). See guild-roles — this is one of the levers that differentiates the Staff tier from GM and Member.
- Audit: every bonus logged with staff name, member, amount, and reason text. Appears in the member’s XP ledger as a distinct transaction type (“Staff commendation” or similar flavor).
- Visibility: member sees the bonus and the reason; bonuses are not publicly visible to other members (avoids social comparison and incentive-gaming).
Why this mechanic matters
The automated system rewards transactions the system can observe (purchases, check-ins, event signups). It cannot observe the social fabric of the shop — the moments that actually make the community work. Giving staff a small, capped, discretionary lever lets them reward those moments without creating a parallel grinding economy. This is also the cleanest answer to “how do staff recognize cultural contribution without conflating it with labor/work expectations.”
The cap matters: without it, this becomes a parallel economy. With it, it’s a genuine recognition mechanism.
Retroactive Progression
Adapted from Rules Cyclopedia Chapter on Creating High-Level Player Characters: a framework for spinning up a character at higher than level 1 when they’re joining an existing campaign or have a prior history that should count.
The problem for us
Customers who shopped at the store for years before the Guild system existed have real lifetime contributions that aren’t reflected in their current XP. A long-time patron who’s spent thousands of dollars pre-Guild is starting at level 1 alongside someone who signed up yesterday. That’s wrong.
The framework
When onboarding a pre-existing customer (or auditing an existing member who predates the Guild):
- Find their lifetime spend at the shop (Square historical data).
- Apply the standard XP conversion: $1 = 100 XP at base rate.
- Decide whether to apply tier multipliers retroactively. Default: no — they weren’t members during those purchases, so base rate only. This also keeps retroactive XP from vastly exceeding current-member XP.
- Cap the backfill at level 8 (one level below name level). Let them earn the final name-level transition under the current system — it’s the meaningful milestone. Pre-system activity should not deliver name level directly; that moment needs to happen under the current mechanic.
- Log the backfill as a distinct XP transaction with a “Retroactive progression” flavor so the ledger shows it explicitly. No surprises.
Who this applies to
- Pre-Guild long-time customers joining the system now
- Existing members who may have been under-credited due to earlier system bugs or pre-launch purchases not being counted
- Special cases at staff discretion (e.g., someone who’s been volunteering at events for years but isn’t in the purchase log)
Why cap at level 8
Name level (9) is the design’s biggest ritual moment. Triggering it via backfill would waste it. Better to backfill to level 8 and let the next real purchase or event push them to 9 — turning the ritual into a genuine event, not an administrative recalculation.
Hard Caps
Adapted from RC: “A character cannot gain more than one level of experience in one adventure, regardless of how many experience points are awarded.” Prevents runaway advancement from a single big treasure haul.
Our equivalents
- Max XP per calendar day: TBD. Prevents kiosk-tap grinding (if someone finds a way) and bulk-purchase level-dumps. Should be calibrated to allow a genuinely big in-shop day (e.g., event + purchase + check-in combo) without capping it, but stop a $5K bundle from vaulting multiple levels.
- Max level gain per month: 1. Even if XP accrual would support more, only one level-up can process per calendar month. Extra XP carries over but the level itself takes another month to realize. Matches RC’s “one level per adventure” while adapted to a longer real-world cadence. Preserves the meaningfulness of each level-up moment.
- Max retroactive level: 8 (per Retroactive Progression above).
These caps should be configured, not hardcoded. Calibrate against real member data once the member base is large enough to see the distribution.
Why these caps matter
Without them, one generous customer with disposable income can outlevel the community overnight. That breaks the progression signal — the leaderboard stops meaning “who’s engaged” and starts meaning “who spent big.” Both axes matter; caps force them to stay somewhat proportional to time spent, not just money spent.
Refund Handling
Implemented 2026-04-14.
Refunds reverse both points and XP awarded by the original purchase. The member’s level is recomputed from the new XP and may drop. Without this, there’s an obvious exploit: buy something big → earn XP / level up → refund it → keep the progression. Same logic for points — refunded money is no longer earned.
Dungeon Books has a 7-day refund policy (customers have 7 days to return). Refunds happen in practice, both for legitimate returns and because people can otherwise read books and return them. Refund handling has to be correct or the loyalty system is gameable.
Mechanics (webhook path)
- On a Square
refund.updatedwebhook withstatus === 'COMPLETED', resolve the originating order viapayment.order_id(notrefund.order_id— Square creates a separate refund-order for card refunds; only the payment reliably links back to the original purchase). - Find the Payload
Purchaserow bysquareOrderId. Read itsearnMultiplier— this is the multiplier in effect at the time of the original purchase, which is what we reverse at (not the member’s current multiplier, which may have changed). - Points reversed:
refundCents × Purchase.earnMultiplier, floored. - XP reversed: look up the original purchase transaction by
squareOrderId. If it exists and has a higherxpthanpoints(because a buff was active), scale its xp proportionally to the refunded fraction. Otherwise XP reversed = points reversed (the backfilled-purchase case, no buffs). - Create a
loyalty-transactionsentry withtype: 'adjustment',source: 'manual',points: -reversePoints,xp: -reverseXp, andmetadata.kind: 'refund-reversal'(plussquareRefundIdfor idempotency). - Decrement
member.loyaltyPointsby the reverse amount (allowed to go negative — a member who earned and redeemed before a refund legitimately has a negative balance; the redemption guard inredeemPointsprevents further redemptions until the debt is repaid). - Decrement
member.xpby the xp-reverse amount (floored at 0 — never negative total). - Recompute
member.levelfrom new xp usinglevelFromXp. - Decrement
Purchase.pointsEarnedby the reverse amount (floored at 0 for partial-refund accumulation).
Idempotency
All refund handling is idempotent on metadata.squareRefundId. Square’s webhook retries and multi-phase status transitions (PENDING → APPROVED → COMPLETED) may fire refund.updated multiple times for the same refund; only the first COMPLETED event creates a ledger entry. Subsequent duplicates are no-ops.
Backfill
The new-member backfill path in lib/square-customer.ts needs to know about refunds for historical purchases so it doesn’t over-credit on initial import. Square’s Orders API does not populate order.refunds for card-payment refunds — those live on the Payment and only surface via the Refunds API. So backfill now:
- Pre-loads a
payment_id → refund_centsmap from the Refunds API at the start of a backfill. - Per order, looks up the tender payment_ids in that map and subtracts the aggregate refund amount from the gross.
- Stores the Purchase row with
totalMoney= gross andpointsEarned= effective (post-refund) × multiplier.
For historical refunds that landed before the webhook handler was deployed (i.e., before 2026-04-14), use scripts/reverse-historical-refunds.ts. It scans the Refunds API, cross-references payment.order_id against existing Purchase rows, and issues reversal adjustments. Idempotent, safe to re-run. Supports --apply-xp-correction for one-time migration of reversals that predated the XP-reversal fix.
Known exploit: redemption-refund
Open vulnerability. A member could:
- Make a large purchase and earn points (and XP — but XP is now reversed, closing that vector)
- Redeem the earned points for store credit before the refund window closes
- Refund the original purchase
After step 3:
member.loyaltyPointsgoes negative (the reversal subtracts the full earn amount).- The member’s
redeemPointscalls are blocked until the negative balance is cleared (the guardWHERE loyalty_points >= pointsinredeemPoints). - But the store credit already exists — it was issued at step 2 and is now unrecoverable through the loyalty system.
Net loss to the shop: the value of the issued store credit. At current tier rates, a 20 of store credit (10,000 pts/$1 ratio). The exploit cap is roughly 2% of the purchase value — not catastrophic per instance, but cumulative across attackers.
Mitigation (not yet implemented): 7-day redemption hold.
- Track
eligibleForRedemptionAtper purchase transaction (purchase date + 7 days, matching the shop’s refund-window policy). redeemPointscomputes available balance as(sum of purchase XP where eligibleForRedemptionAt < now) + (all non-purchase points) - (prior redemptions).- Points earned from a purchase are shown in the UI as “pending until [date]” for the first 7 days.
- After 7 days, the refund window has closed; the points become redeemable.
Why 7 days: matches the refund policy. If a member can’t refund after 7 days, they can’t exploit the loop. Using a shorter hold would leave an exploit window; a longer hold would frustrate legitimate members who want to redeem quickly on earned activity.
Scope priority: the current negative-balance guard is sufficient for the current 13-member cohort (all known, in-person-vouched). Fix before any of:
- Invite flow that doesn’t require in-person validation
- Scaling beyond ~50 members
- Enabling any Solana/on-chain redemption path (where the “recovery by future earning” property is harder to guarantee)
This is a hard blocker for on-chain redemptions specifically because the abstraction of points as a tradable on-chain asset means the negative-balance guard doesn’t cleanly apply.
What refunds still do NOT do
- Do not revoke already-issued redemption (store credit, merch already shipped, Discord role already assigned). Redemption is a one-way operation; once the value is transferred out of the loyalty system, the system can’t recall it. See the exploit note above.
- Do not reverse independent non-purchase engagement: check-ins, event attendance, quest completions from the same day are untouched. Only the refunded purchase’s own contribution (including its buff XP portion) is reversed.
- Do not cascade: refunding Purchase A doesn’t affect Purchase B’s pointsEarned or the XP awarded by B, even if they happened the same day.
Immortality Paths (V5 parking lot)
RC post-name-level endgame: characters who reach level 26+ pursue Immortality via one of four paths — Dynast (builds a great empire), Hero (classic epic hero trajectory), Paragon (masters one profession deeply), Polymath (learns broadly).
These map to real patron archetypes and are worth preserving in notes as a long-term trajectory framework:
| Path | Shop-world archetype |
|---|---|
| Dynast | The network-builder — recruits members, sponsors events, grows the community |
| Hero | The big-gesture patron — rare but significant contributions, epic moments |
| Paragon | The subject-matter expert — deep focus on one genre/game/system |
| Polymath | The generalist — broad engagement across everything the shop does |
Not for MVP. Most members will take years to approach a level where this matters. Captured here so it’s not lost; revisit when the first member approaches that tier.
Seasonal/Special Events
IRL events modeled on MMO seasonal content: double XP periods, limited-time quest chains, community-wide unlock thresholds. Mithril members get 50% off event costs, making them the most likely attendees — correct feedback loop.
Membership Tiers
| Tier | Monthly Cost | Store Discount | Event Discount | XP Multiplier |
|---|---|---|---|---|
| Bronze | $10 | None | 10% | 1x |
| Silver | $20 | 10% | 10% | 2x |
| Gold | $50 | 20% | 25% | 2.5x |
| Mithril | $100 | 25% | 50% | 3x |
Tier Design Logic
- Bronze is a cover charge, not a value proposition. It filters for engaged players and covers platform costs. No store discount. Value = game access + event discount.
- Silver break-even: ~$200/month spend on store discount alone.
- Gold break-even: ~$250/month spend on store discount alone. 25% event discount provides a smooth step between Silver and Mithril.
- Mithril break-even: ~$400/month spend on store discount alone. 50% event discount is the key differentiator from Gold.
Multiplier Rule
XP multipliers apply to purchase XP only, not quest/event XP. This prevents whales from outleveling active community members who engage with the full system. A high-spending member who never participates shouldn’t outlevel a lower-spending member who does everything.
Free Tier
No free tier at launch. Costs associated with maintaining free accounts aren’t justified pre-scale. If the platform goes viral, introduce a spectator mode: view-only access to leaderboards, other players’ levels, upcoming events. No XP earning, no participation. Functions as a demo and FOMO funnel toward Bronze.
Dual Currency: XP + Points
Every dollar earns both XP and points simultaneously at the same rate with the same multipliers.
- XP: Permanent. Tracks level. Never decreases.
- Points: Spendable. Tracks balance. Redeemed for rewards.
A player who spends all their points remains at their current level. No de-leveling.
Points-to-Store-Credit Conversion
Target ratio: 100 points = $0.01 (1 cent)
This yields effective cashback rates of:
| Tier | Multiplier | Effective Cashback |
|---|---|---|
| Bronze | 1x | 1% |
| Silver | 2x | 2% |
| Gold | 2.5x | 2.5% |
| Mithril | 3x | 3% |
These rates are competitive with major credit cards (Chase Sapphire Preferred offers 1–5% category-dependent, Discover rotates 1–5% quarterly). Combined with store/event discounts, Mithril delivers ~28% total value return on store purchases — no credit card competes at a single merchant.
The conversion ratio is intentionally inefficient enough that most players will prefer keeping points for leveling and rewards rather than cashing out.
Point Sinks
All purchases are permanent. No decay, no expiring roles, no maintenance costs. Earned = kept.
| Tier | Cost Range | Examples |
|---|---|---|
| Low | 5,000–20,000 pts | Stickers, Discord roles (zero fulfillment cost) |
| Mid | 50,000–200,000 pts | Tote bags, store credit |
| High | 500,000+ pts | T-shirts, exclusive merch, event VIP access |
Seasonal items rotate but are never permanently exclusive. Past seasonal items return annually or become available off-season at slightly higher cost.
Classes
Four cosmetic classes at launch: Fighter, Magic-User, Cleric, Rogue (renamed from Thief).
- Purely cosmetic. No mechanical differentiation.
- Provides player identity, community sorting (Discord channels, class leaderboards, team events).
- One character, one class, chosen at signup. No rerolling, no alts.
- Mechanical class bonuses can be added later based on community feedback.
- If class changes are eventually allowed: expensive one-time reincarnation that costs significant points but preserves level.
BECMI race-as-class options (Elf, Dwarf, Halfling) deferred. Start small with four human classes and expand based on demand.
Launch Strategy
Early access at $10 minimum (Bronze). Ship with:
- BECMI XP curve
- Four cosmetic classes
- Tiered membership with discounts and multipliers
- Point sinks for physical goods and Discord roles
- Quest system
- Event calendar
Iterate based on player feedback. The community will signal what needs expansion and what’s unused.
Open Questions
- Exact XP values for quest tiers need calibration against real purchase data
- Middle-tier quest design (between “photo of a dog” and “visit 5 businesses”)
- Whether post-level-9 XP should increase beyond 120k/level to slow Mithril progression
- Discord integration specifics (role automation, class channels, leaderboard bot)
- Seasonal event cadence and structure