2026-03-27

guild — picking up next work

Reviewed git history and roadmap. Member portal (login, dashboard, activity, tiers) and Stripe billing portal are done (PRs #3 and #4).

Notes from Carrie

Marty

  • Merged MTG embed stuff

guild — Square customer linking (PR #5)

Built auto-linking of Square customers on Guild signup. When a new member subscribes via Stripe, the webhook now:

  1. Searches Square by email (then phone) for an existing customer
  2. If matched — links them and backfills loyalty points from past orders (base 1x)
  3. If no match — creates a new Square customer, flags for staff review

Key decisions:

  • Backfill at 1x regardless of tier — tier multiplier only applies going forward
  • All-time history — every past purchase counts
  • Orders search, not payments list — Square payments.list doesn’t reliably return by customer ID in sandbox
  • Runs async in the Stripe webhook, not the checkout route (keeps signup fast, only links confirmed subscribers)

Tested all 3 scenarios locally (match + backfill, match + no purchases, no match + create new).

PR review deferred items (PR #5)

  • Unit tests for square-customer.ts — tracked in #6
  • Fire-and-forget webhook — linking runs inline, could hit Stripe’s 30s timeout with huge order histories. Fine for a bookshop, revisit with job queue
  • Multi-shop backfill — only backfills from first shop in org. Single shop for now
  • Batch dedup — per-order DB lookup in backfill loop. Fine at current scale (~50-100 orders)

Future: job queue

When async work stacks up (Hi.Events webhooks, achievement calculations, email notifications), bring in BullMQ + Redis. Redis will already be in the stack for the store rebuild (Medusa). Not worth adding for one webhook handler — the signal is Stripe retries or needing to decouple work from request/response.

v1 alpha gap analysis

Reviewed full codebase for launch readiness. ~75% there. Key gaps:

Must fix (tonight):

  • Redeem endpoint auth bug — any member can redeem anyone’s points
  • No email at all — need Resend + welcome email + password reset
  • No password reset flow

guild — email + password reset + redeem fix (PR #7)

Knocked out all three “must fix” items in one PR:

  • Resend email@payloadcms/email-resend adapter, domain verified, DKIM/SPF passing
  • Password reset — forgot password page → email with token → reset page. Payload auto-logs in after reset (nice UX). Custom email template points to frontend, not admin panel
  • Welcome email — fires on customer.subscription.created webhook, includes tier name. Carrie confirmed receipt
  • Redeem auth — added user.collection !== 'users' check, members now get 403

Railway deployment plan

Deploying to Railway for alpha with Panat + Carrie as the only users. Goal: use real-ish data without spending real money or touching other customers’ data.

Integration strategy per service

Square — prod, filtered

  • Use real production token so we get real purchase data flowing through loyalty
  • Add a customer ID filter to the Square webhook — only process payments tied to Panat’s Square customer ID, skip everything else
  • Backfill on signup pulls real order history, new purchases award points in real-time
  • The webhook still receives all payment events from the shop; the filter just skips processing. No data is written for other customers
  • Remove the filter when ready to go live for real members

Stripe — test mode

  • Keep test mode. Subscriptions, billing portal, webhooks all work identically to prod
  • Sign up with test cards (4242...), get real member accounts
  • Only difference from prod: no actual charges
  • Flip to live keys when ready to charge real dues

Hi.Events — real test event (when built)

  • Create a real event in Hi.Events (e.g. “Guild Test Night”)
  • RSVP/check in to trigger the webhook with real payload format
  • Better than a sandbox — tests the actual integration path
  • Most event platforms don’t have staging environments anyway

Resend — already live

  • Domain verified, DKIM/SPF passing. Emails are cheap. No reason to sandbox this.

The gap to accept

Member accounts will have a real Square customer link but a test Stripe subscription. That’s fine — they’re independent systems. Points come from Square purchases, billing comes from Stripe. They don’t need to talk to each other.

Env vars needed on Railway

  • DATABASE_URL — Railway Postgres
  • PAYLOAD_SECRET — strong random value (not the dev one)
  • SQUARE_ACCESS_TOKEN — prod token
  • SQUARE_WEBHOOK_SIGNATURE_KEY — new, for Railway URL
  • SQUARE_WEBHOOK_URL — Railway domain
  • SQUARE_ALLOWED_CUSTOMER_IDS — Panat’s Square customer ID (new, for filtering)
  • STRIPE_SECRET_KEY — test mode key
  • STRIPE_WEBHOOK_SECRET — new, for Railway URL
  • RESEND_API_KEY — same as dev
  • NEXT_PUBLIC_SERVER_URL — Railway domain

Railway deploy — Docker build fixes

First successful Railway build after a painful round of Docker issues. All common Payload CMS + Next.js + Docker problems:

  1. Prerender failures — any page calling getPayload() or getMember() crashes during next build because env vars aren’t available at Docker build time. Fix: export const dynamic = 'force-dynamic' on every page that touches Payload. This is the standard community solution (confirmed on Reddit, Payload Discord). Added to: login, forgot-password, signup, signup/success, dashboard layout.

  2. Missing output: 'standalone' — Dockerfile expects .next/standalone but Next.js doesn’t produce it by default. Added output: 'standalone' to next.config.ts.

  3. No /app/public directory — Dockerfile tried to COPY --from=builder /app/public ./public but we don’t have a public dir. Removed the line (Dockerfile even had a comment saying to do this).

  4. No .dockerignoreCOPY . . was shipping .env, .git, node_modules into the image. Created .dockerignore.

  5. sharp in standalone — standalone mode doesn’t always bundle native modules. Set NEXT_SHARP_PATH=/app/node_modules/sharp in the runner stage.

  6. Non-null assertion lint warnings — replaced (await getMember())! with proper null check + redirect in 3 dashboard pages. Unrelated to Docker but cleaned up in the same pass.

Live at: https://dungeon.club (Railway, guild-production-1747.up.railway.app)

Prod setup completed

  • Railway deploy working (Dockerfile, standalone output, force-dynamic pages)
  • Postgres migrated and seeded (org, shop, 4 tiers with Stripe price IDs)
  • Admin user created, Square location ID set
  • Stripe webhook registered and verified (dungeon.club/api/webhooks/stripe)
  • Square webhook registered (dungeon.club/api/webhooks/square) — same endpoint as local, no customer filter yet
  • First prod signup tested — welcome email delivered via Resend
  • Gotcha: Stripe webhook secret was set wrong (had VAR_NAME=value as the value), caused invalid signature errors

E.164 phone fix

Square customer linking was failing because phone numbers were stored as raw digits (2015550123) but Square requires E.164 format (+12015550123). Added toE164() helper in square-customer.ts. Redeployed, created fresh test member — Square customer linked successfully.

Profile completion + integrations vision

Carrie pitched Hardcover.app integration — when a member buys a book in-store (tracked via Square), they can add it to their Hardcover reading profile. Guild becomes the identity layer tying the whole ecosystem together.

Planned post-signup profile flow:

  • After signup success, optional “Complete your profile” page (skippable)
  • Birthday (already a Members field, used for birthday month perks)
  • Address (needed for Mithril tier shipping — curated items quarterly)
  • Link Hardcover.app (auto-track book purchases to reading profile)
  • Link Discord (member role sync)
  • Accessible anytime from dashboard settings

Design principle: don’t add friction to signup. Keep the core flow fast (name, email, phone, password → payment), let members fill in details later when they see the value.

Remaining for v1 alpha

Tomorrow (2026-03-28):

  • Kiosk app (NFC cards arrived, using Flipper Zero as reader until reader ships)
  • Test Square purchases flowing through loyalty engine on prod

Later:

  • Profile completion flow (birthday, address, integrations)
  • Hardcover.app integration
  • Discord role sync
  • Square webhook customer filter (if needed — skipping for now since it’s just us)
  • Staff redemption UI (currently API-only)
  • Staff dashboard (check-ins, recent activity)
  • E2E Playwright tests are stale
  • Hi.Events webhook
  • Achievement system