Membership & Loyalty Platform

Overview and architecture plan for the Dungeon Books membership and loyalty platform. This is both a production system for Dungeon Books and an R&D prototype for a future product targeting independent bookstores, game shops, cafes, and other third spaces running Square POS.

Context

This project is a custom-built, headless membership platform designed to be portable to other Square shops.

The full ecommerce rebuild (Medusa v2 + Solace storefront) is separate and not as pressing — the current webshop is functional. This platform is scoped independently and ships first.

Goals

  • Custom membership and loyalty system
  • Build around Square POS as the primary integration (most indie shops already have it)
  • Own the loyalty logic — tiers, not just points — loosely coupled to Square
  • Attribute in-store purchases to members
  • Let members check in via NFC, QR, or phone tap
  • Give members a portal to view purchases, events attended, tier status, and profile
  • Give staff a dashboard to manage members, view check-ins, and see loyalty status at POS
  • Track event attendance and tie it to member profiles
  • Learn what works at Dungeon Books, then generalize into a product for other shops

Non-Goals (for now)

  • Full ecommerce rebuild (Medusa + online storefront) — separate project, ships later
  • Nonprofit revenue routing — not yet incorporated, all revenue goes to the LLC
  • Multi-location support — Dungeon Books is one shop; multi-location is a product concern for later
  • Mobile app — web-first, responsive; native app is a future product consideration

Architecture

┌─────────────────────────────────────────────────────┐
│                   FRONTENDS                          │
│                                                      │
│  Member Portal    Staff Dashboard    Kiosk/Check-in  │
│  (Next.js)        (Payload Admin     (tablet web     │
│                    + custom views)    app)            │
└────────┬──────────────────┬──────────────┬──────────┘
         │                  │              │
         └──────────┬───────┘──────────────┘
                    │
         ┌──────────┴──────────┐
         │   Payload CMS       │
         │   (headless API)    │
         │                     │
         │   Members           │
         │   Tiers & Loyalty   │
         │   Events/Attendance │
         │   Content           │
         │   Webhook handlers  │
         └──────────┬──────────┘
                    │
      ┌─────────────┼─────────────┐
      │             │             │
┌─────┴─────┐ ┌────┴────┐ ┌─────┴─────┐
│  Square   │ │Hi.Events│ │  Future:  │
│  POS      │ │         │ │  Medusa   │
│           │ │ tickets │ │  ecomm    │
│ purchases │ │ RSVPs   │ │  customer │
│ customers │ │ check-in│ │  sync     │
│ terminal  │ │         │ │           │
└───────────┘ └─────────┘ └───────────┘

Payload CMS — the hub

Payload is the source of truth for member identity and loyalty state. It handles:

  • Member profiles — name, email, phone, tier, join date, NFC card UID, linked Square customer ID, linked Hi.Events attendee ID
  • Loyalty engine — tier calculation, points/credit accumulation, reward definitions, promotion rules
  • Purchase log — synced from Square via webhooks (not the source of truth for transactions, but a denormalized record for the member portal and tier calculation)
  • Event attendance — synced from Hi.Events via webhooks
  • Content — member-facing pages, announcements, tier benefit descriptions
  • Auth — member login for the portal, staff access for the dashboard
  • Webhook receivers — ingests events from Square and Hi.Events, updates member state

This is deliberately not Square Loyalty. Square Loyalty is points-based and would lock us (and future product customers) into Square’s model. Our loyalty engine lives in Payload, uses Square only for purchase data and POS-side customer identification.

Square POS — purchase attribution and in-store identity

Square is the cash register. We integrate with it, not build on top of it.

Square APIWhat we use it for
Customers APILink Square customer ID to Payload member; look up member at POS
Customer Custom Attributes APIPush tier, points, and member status to Square customer profile (see below)
Orders API / WebhooksReceive purchase events → update member purchase history and loyalty
Terminal APICustom workflows: member check-in screen, upsell prompts, loyalty status display
Catalog APIRead-only — product names and categories for purchase log context

We do NOT use:

  • Square Loyalty API (we own loyalty logic)
  • Square Subscriptions API (membership billing lives in our system, likely Stripe)
  • Square customer notes field (may be used by staff for other purposes; not safe to overwrite)

Square customer sync — custom attributes

Design decision: Use Square Custom Attributes (not customer notes) to push member data to the POS. Custom attributes are namespaced to the app, don’t conflict with staff-written notes, and work cleanly for other shops that may already use notes. This is the portable approach for the future product.

Custom attribute definitions to create:

KeyDisplay nameTypeVisibilityExample value
guild_tierGuild TierSTRINGVISIBILITY_READ_WRITE_VALUES"Gold"
guild_pointsGuild PointsSTRINGVISIBILITY_READ_WRITE_VALUES"1,500 ($15.00)"
guild_statusMembership StatusSTRINGVISIBILITY_READ_WRITE_VALUES"active"
guild_member_idGuild Member IDSTRINGVISIBILITY_READ_ONLY"42"

Sync triggers:

  • After loyalty points awarded (purchase webhook)
  • After points redeemed
  • After Stripe subscription change (tier upgrade/downgrade/cancel)

Implementation approach:

  1. On app startup or via script: create custom attribute definitions via POST /v2/customers/custom-attribute-definitions
  2. After each trigger event: upsert attributes via POST /v2/customers/{customer_id}/custom-attributes/{key}
  3. Staff sees tier, points, and status on the Square customer profile at POS — no need to check Payload

Webhook flow:

Square POS sale → order.completed webhook → Payload webhook handler
  → match Square customer_id to Payload member
  → log purchase
  → recalculate loyalty (points, tier progression)
  → push updated tier/points back to Square via custom attributes

Hi.Events — event ticketing and attendance

Hi.Events handles what it’s good at: selling tickets, managing RSVPs, and QR code check-in at the door. We sync attendance data back to Payload.

ConcernHow
Ticket paymentsStripe Connect (Hi.Events native) — separate from Square
Event creationHi.Events admin
Event pagesHi.Events hosted or embedded
Attendance trackingHi.Events QR check-in → webhook → Payload
Member attributionMatch attendee email to Payload member

Future: Medusa ecommerce integration

When the online store rebuild ships, Medusa will have its own customer records. At that point:

  • Medusa customer can be linked to Payload member (store medusa_customer_id on member profile)
  • Online purchases flow through Medusa → webhook → Payload → same loyalty calculation as Square purchases
  • A member’s portal shows both in-store (Square) and online (Medusa) purchase history

This is additive. The membership platform does not depend on Medusa.


Member Identity

A member exists in up to three systems. Payload is the join.

SystemIDWhen created
Payloadmember record (primary)When someone signs up for membership
Squarecustomer_idWhen they first buy in-store, or when we create/link during signup
Hi.Eventsattendee recordWhen they first buy a ticket or RSVP

Linking is done by email as the common key, with explicit ID references stored on the Payload member record:

Member (Payload collection)
├── email (unique, indexed)
├── name
├── phone
├── tier (bronze | silver | gold | mithril)
├── loyalty_points (never expire)
├── join_date
├── nfc_card_uid (nullable)
├── square_customer_id (nullable)
├── hi_events_attendee_id (nullable)
├── medusa_customer_id (nullable, future)
├── purchases[] (synced from Square/Medusa)
├── events_attended[] (synced from Hi.Events)
└── membership_subscription (billing status, plan, renewal date)

Online-only members

Some members may never visit the physical store. They can:

  • Sign up via the member portal
  • Earn loyalty from online purchases (once Medusa ships)
  • Attend virtual events (if offered)
  • Have no Square customer ID or NFC card — that’s fine

Walk-in customers without membership

A Square customer who buys something in-store but isn’t a member just stays in Square. No Payload record is created. If they later sign up for membership, we link their Square history retroactively.


Tiers & Loyalty

Inspired by credit card models: you pick and pay for a tier (like choosing a card), and you earn loyalty points by engaging with the shop (like earning miles). Two separate systems that interact.

Design principles

  • Points never expire. If a member takes a year off and comes back, their balance is there.
  • No dark patterns. No “use it or lose it” urgency, no expiration emails, no loss aversion hooks. We want people to redeem — redemption means they’re in the shop.
  • Redemption should feel like celebration, not transaction. End-of-year points party, exclusive redemption events, community milestones. Spending points is a good time, not a checkout line discount.
  • No breakage by design. Credit card companies profit from unspent points. We don’t want that. High redemption rate = healthy program.

Tiers (what you pay for)

Members choose and pay for their tier. Billing via Stripe (see Membership Billing section). Benefits are battle-tested, keeping as-is.

Iron is the internal designation for non-member customers tracked in Square’s customer directory. They have no Payload member record.

Bronze — $10/mo

BenefitDetails
Welcome giftDungeon Books member sticker (pickup)
Store discountNone
Event discount10% off all events (online tickets only)
Event accessEarly access to RSVP for special events
Birthday perk$10 store credit during birthday month
Loyalty multiplier1x

Silver — $25/mo

BenefitDetails
Welcome giftDungeon Books member sticker (pickup)
Yearly giftDungeon Books member tote bag (Dec 15)
Store discount10% off all store purchases
Event discount10% off all events (online tickets only)
Guest sharingShare your discount with 1 additional guest per event
Event accessEarly access to RSVP for special events
Birthday perk$10 store credit during birthday month
Free ARC1 free advance reader copy per month (in-store pickup only)
Loyalty multiplier1.5x

Gold — $50/mo

BenefitDetails
Welcome giftDungeon Books member sticker (pickup)
Yearly giftDungeon Books member tote bag (Dec 15)
Store discount20% off all store purchases
Event discount15% off all events (online tickets only)
Guest sharingShare your discount with 1 additional guest per event
Event accessEarly access to RSVP for special events
Birthday perk$10 store credit during birthday month
Free ARC1 free advance reader copy per month (in-store pickup only)
Loyalty multiplier2x

Mithril — $100/mo

BenefitDetails
Welcome giftDungeon Books member sticker (pickup)
Yearly giftDungeon Books member tote bag & member t-shirt (pickup or delivery, shipping included)
Curated itemsHand-picked items from the collection, every 3 months (pickup or delivery, shipping included)
Store discount25% off all store purchases
Event discount50% off all events (online tickets only)
Guest sharingShare your discount with 1 additional guest per event
Event accessEarly access to RSVP for special events
Birthday perkHand-picked book by the Guildmaster team, just for you (pickup only)
Free ARC1 free advance reader copy per month (in-store pickup only)
Private eventUse of the bookstore for a private event once per year (birthday, book club, engagement party, etc.)
Loyalty multiplier3x

One-time contributions

Not a tier. Members and non-members can make a one-time contribution of any amount to support the shop.

Loyalty points (what you earn)

Points are earned through engagement with the shop. The earn rate scales with tier.

Design decision: points as universal loyalty currency. Points are the internal currency stored in Payload. They are not automatically converted to store credit. Members accumulate points and choose how to redeem them — store credit is one option among many. This keeps the system flexible (double-point promos, cross-platform redemptions, non-monetary rewards) and avoids tax implications until redemption. The member portal always shows the dollar-equivalent alongside the point balance (e.g. “1,500 points ($15.00 store credit value)”) so members understand the value without needing to do math.

Earning:

ActionBase rateScaled by tier multiplier?
In-store purchase (per $1 spent)1 pointYes
Online purchase (per $1, future)1 pointYes
Event attendanceTBD (flat bonus)Yes
Check-in / visitTBD (flat bonus)Possibly

Tier multipliers (example, tunable):

TierMultiplier
Bronze1x
Silver1.5x
Gold2x
Mithril3x

A Mithril member spending $50 earns 150 points. A Bronze member earns 50. The multiplier is the core incentive to upgrade tiers.

Bonus events (staff-configured, not automated dark patterns):

  • Double point days tied to real occasions (store anniversary, Free RPG Day, new releases)
  • Community milestones — “the shop collectively hit 100,000 points this quarter, everyone gets a bonus”
  • Category bonuses — 2x on RPG products this month, 2x during an author visit

These are configured by staff as promotions in Payload, not hardcoded. They should feel like gifts and celebrations, not manufactured urgency.

Redeeming:

Points are a universal currency redeemable across multiple paths. Store credit is the primary path, but not the only one.

Reward typeExampleHow it works
Store credit100 points = $1Tracked in Payload, applied as discount at POS by staff
Experience access500 points = priority seat at a special eventUnlocks ticket in Hi.Events
Exclusive items2,000 points = limited edition merchStaff fulfills, logged in Payload
Community eventsEnd-of-year points party, redemption pop-upsPoints are the entry ticket
Digital perks1,000 points = Discord role/flairAutomated via bot integration

Redemption values are configurable per shop (important for the future product). The engine tracks earn and redeem as a ledger — every point transaction is logged with source, amount, and timestamp. Tax implications only arise at the moment of redemption for monetary-value rewards (store credit), not when points are earned or held.

How tiers and loyalty interact

TIER (paid monthly)                LOYALTY (earned by engagement)
├── determines benefits            ├── accumulated from purchases,
├── determines earn multiplier         events, visits
├── member chooses their tier      ├── points never expire
│                                  ├── redeemed for rewards
│                                  └── higher tiers earn faster
│
└── upgrading tier = higher        └── points incentivize spending
    multiplier = faster                at your shop instead of
    point accumulation                 elsewhere

The tier fee pays for itself through the multiplier and exclusive access. The loyalty points keep them coming back. Neither system uses expiration, artificial scarcity, or loss aversion to retain members.


Check-in & Identification

How a member is recognized when they walk in or buy something.

At purchase (Square POS)

MethodFlowComplexity
Phone number lookupStaff asks for phone → Square POS customer search → purchase attributedNone — built into Square
Square loyalty tapMember taps phone (Apple/Google Wallet) on reader → Square identifies customerLow — requires Square Loyalty enabled, even if we don’t use their points system
Terminal API promptTerminal shows “Member?” screen → member enters phone/scans QR → Payload API lookup → Square customer linkedMedium — custom Terminal API workflow

At the door (non-purchase check-in)

MethodFlowComplexity
NFC card tapMember taps physical NFC card on tablet → tablet reads card UID → Payload API lookup → check-in loggedMedium — need NFC-enabled tablet + small web app
QR code scanMember shows QR from portal on phone → staff scans or kiosk reads → Payload API lookupLow-medium
Self-serve kioskTablet at door → member enters phone/email or taps NFC → welcome screen, loyalty status shownMedium

NFC cards

Issue physical NFC cards (NTAG215/216, ~$0.30 each) with a unique ID written to the chip. The ID maps to the Payload member record. Cards can be branded with the adventurers guild aesthetic.

For phone-based NFC: Apple Wallet and Google Wallet passes can carry a member ID. Libraries exist for generating these (passkit-generator for Apple, Google Wallet API). The pass could display tier, points, and a barcode/QR fallback.


Member Portal

A web app (Next.js) authenticated against Payload. What the member sees:

  • Profile — name, tier, loyalty points, membership status
  • Purchase history — in-store purchases (from Square sync), online purchases (from Medusa, future)
  • Events — past events attended, upcoming RSVPs
  • Rewards — available rewards, redemption history
  • Membership — plan details, billing, renewal date
  • Digital card — QR code and/or Apple/Google Wallet pass download

Staff dashboard

Payload’s admin UI, extended with custom views:

  • Member management — search, view, edit members; manual tier overrides
  • Check-in log — who’s in the shop right now, recent check-ins
  • Loyalty overview — tier distribution, engagement metrics
  • Event attendance — per-event attendee lists, cross-referenced with members

Membership Billing

Membership itself needs a recurring payment. Options:

OptionProsCons
Stripe (direct)Hi.Events already uses Stripe; one payment processor for all non-POS revenue; Stripe Billing is battle-testedAnother processor alongside Square
Square SubscriptionsSingle processor for in-store + membershipTies the product to Square; less flexible billing logic
Payload + StripePayload manages plans/status, Stripe handles billing, webhooks update PayloadMost flexible, most portable for a product

Recommendation: Payload + Stripe. Keeps billing decoupled from Square (important for product portability). Stripe is already in the stack via Hi.Events. Square stays focused on what it’s best at: in-store POS.


Tech Stack Summary

ComponentTechnologyPurpose
Member identity & loyalty enginePayload CMS 3.xSource of truth for members, tiers, loyalty rules
Member portalNext.js (App Router)Member-facing web app
Staff dashboardPayload Admin UI + custom viewsMember management, check-in log, metrics
Check-in appWeb app (tablet)NFC/QR reader for door check-in
In-store POSSquarePurchase attribution, customer lookup
Event ticketingHi.Events (self-hosted)Tickets, RSVPs, QR check-in at events
Membership billingStripe (via Payload)Recurring membership payments
DatabasePostgreSQLPayload data store
HostingRailwayAll services
Digital wallet passesApple Wallet / Google Wallet APIsMember cards on phone

Integration Map

Square POS ──webhook──→ Payload (log purchase, update loyalty)
Hi.Events  ──webhook──→ Payload (log attendance, update loyalty)
Stripe     ──webhook──→ Payload (membership billing status)
Payload    ──API call──→ Square (create/update customer, push tier to notes)
Payload    ──API call──→ Hi.Events (future: auto-create attendee for member)
NFC reader ──HTTP──→ Payload API (check-in lookup)
Member portal ──API──→ Payload (read profile, purchases, events)

What We Build vs. What We Buy

ConcernBuild or buyNotes
Member profiles & authBuild (Payload collections)Core of the platform
Loyalty engine (tiers, rules)Build (Payload custom module)Product differentiator — must be ours
Purchase sync from SquareBuild (webhook handler)Straightforward — Square webhooks are well-documented
Event managementBuy (Hi.Events)Self-hosted, has API and webhooks
Membership billingBuy (Stripe Billing)No reason to build payment processing
NFC cardsBuy (blank NTAG cards + USB reader)Write member ID to card, ~$0.30/card
Digital wallet passesBuild (Apple/Google Wallet APIs)Libraries exist, moderate effort
Member portalBuild (Next.js + Payload API)Custom UI, moderate effort
Staff dashboardBuild (Payload admin extensions)Payload’s admin is extensible, moderate effort

Open Questions

Resolved

  1. Tier thresholds — Tiers are purchased (25/100 per month), not earned.
  2. Membership pricing — Bronze 25/mo, Gold 100/mo.
  3. POS behavior — Staff dashboard (Payload admin) as primary tool + push tier/status to Square customer notes for visibility inside Square. Terminal API custom workflow on the roadmap for v2.
  4. Retroactive history — Yes, backfill Square purchase history when a walk-in becomes a member. Configurable per shop (backfill: { enabled: true, max_months: null, rate_multiplier: 1.0 }). Strong onboarding moment.
  5. Multi-shop architecture — Multi-tenant shared database with Organization → Shop → Member hierarchy. Add organization and shop fields to every collection from day one, hardcoded to Script Wizards LLC / Dungeon Books. Supports multi-location and future cross-org neighborhood networks.
  6. Earn rates and redemption — 1 point per 1 store credit. Generous starting point, tunable. Event attendance and check-in bonuses TBD.
  7. Tier-specific benefits — Battle-tested, keeping as-is (see Tiers section above). Loyalty points are a new layer on top.

Remaining

  1. Event attendance and check-in point values — Launch with purchase-based earning only. Add event/check-in bonuses post-launch (starting point: ~25 points per event, ~5 per check-in). Not blocking.
  2. Discount application at POS — Manual for now (staff applies in Square). Square does not expose an API for automatically creating or updating discounts — this is a known platform limitation. No automated path exists. Staff applies manually; the Payload dashboard and Square customer notes tell them the rate.
  3. Member migration — Need to export existing members, tiers, join dates, billing. Research before cutover, not blocking for build.
  4. Buying points / point boosts — Analogous to video game season pass point boosts. Could translate to buying gift cards that grant loyalty points (e.g., $25 = 2,500 points, with tier multiplier applied). Blurs the line between gift cards and loyalty in an interesting way. Future consideration — not MVP.

Relationship to Other Projects

  • Store rebuild (Medusa + Solace storefront) — ships separately, later. When it does, Medusa customers sync to Payload members. The membership platform doesn’t depend on it.
  • Webshop (current, Square-backed) — unaffected. Membership platform is a new, parallel system.
  • Marty (AI chatbot) — could eventually look up member info for personalized recommendations via Payload API.
  • Future product — architecture should be generalizable to other indie shops.