Home / Portfolio / IRCA Case Study
Case Study · Membership & Ticketing

Istead Rise Community Association Membership Portal

A 900-member community centre run from spreadsheets, a TouchOffice EPOS till, and a Paxton Net2 door system that never talked to each other. We built one portal for joining, paying, renewing, event ticketing, door check-in, and room hire, and shipped the ticketing first so it could earn its keep. It did: the first event sold out in three hours.

Client
Istead Rise Community Association
Sector
Community & Venues
Project Type
Membership & ticketing platform
Launched
September 2026

First event on sale · 5 September 2026

3 hrs
First event sold out (100 tickets, 47 buyers)
900
Members ready to migrate in one morning
0
Oversold seats, double charges, or office interventions
£0
Monthly platform cost beyond Stripe's fees

The problem

Istead Rise Community Association runs a community centre in Kent: a main hall, a bar, a members' door system, and a calendar of events from kids' Halloween parties to quiz nights. It has around 900 members, and until this year every one of them lived in a set of spreadsheets, plus a TouchOffice EPOS till system, plus a separate Paxton Net2 door-entry system, none of which talked to each other.

  • Joining was a physical act. New members filled in a form at the centre, brought ID, and paid however they could. Nothing could happen online.
  • Every member was entered three times. Once in the membership spreadsheet, once in the TouchOffice till, once in the Paxton Net2 door system. Any of the three could drift, and the till record had no photo, so there was no way at the bar to check the card matched the person holding it.
  • Money was untracked. Cash, cheques, bank transfers, and card-at-the-bar all arrived through different hands. Reconciling who had paid for which year meant cross-referencing sheets.
  • January was a wall. Around 900 renewals fall due in the same month. Every member had to be contacted, every payment recorded, and lapsed members quietly kept their door cards working until someone noticed.
  • Events sold tickets by hand. Names on a list, cash in an envelope, no idea how many were still to arrive on the night.
  • The committee changes. Volunteers rotate, so the system had to be runnable by people who weren't there when it was designed, with no shared logins and no one seeing more than they should.

The association's own brief put it plainly: the portal had to become "the single source of truth for all IRCA members, regardless of how they pay or whether they use the portal themselves."

How we approached it

We wrote the product requirements first: a versioned PRD covering tiers, households, the membership year, grace periods, every payment method, and every admin flow, signed off by the association before any code. That document is still in the repo; the database schema was built from it.

The architectural decision that shaped everything: one Postgres database, one login, and a service layer that never trusts the browser. Every price, capacity check, and permission decision happens on the server. The key that ships to the browser can read exactly two things (published events and their ticket types), enforced by row-level security in the database itself rather than by application code remembering to check.

The second decision was to build event ticketing on top of the membership foundation rather than as a separate tool. Tickets reuse the same payment ledger, email pipeline, admin roles, and audit trail, so when the membership side launches, the office will already know the portal.

What we built

Online joining, ID, and payment

A public join form where an applicant adds every adult in the household, uploads a photo, photo ID, and proof of address, chooses a tier, and pays the joining fee and first year via Stripe Checkout in one payment, with the card saved for next December's renewal. Uploads are verified on the server by their actual bytes, not the filename, and stored in private buckets only the office and the member's own household can read.

An office that works the way the office works

  • Applications: review documents, approve or reject with a reason; approval triggers the welcome email and activates the member by a configurable rule.
  • Payments: cash, cheque, bank transfer, and card-in-person recorded alongside Stripe, with CSV export for the treasurer. Prices are append-only: change a fee and the old price is closed, not overwritten, so historical payments still point at what was charged.
  • Renewals: reminder emails from 1 December, automatic Stripe charges for saved cards in mid-December, and a January worklist for everyone else. Manual payers get a personal pay-by-card link needing no login. Grace runs to the end of January, then lapsing is automatic. The office can take one email per renewal, or a daily digest, because 900 individual emails in January is its own problem.
  • One trusted record: TouchOffice and Paxton Net2 stay as they are; the portal tells the office exactly when a member is approved or lapses so both can be updated from one record, with a photo, instead of three.
  • Migration and GDPR: a CSV importer brings the 900-member spreadsheet across in one morning (validate-only dry run, households grouped, tiers worked out from age). One click exports everything held about a member; another erases their personal data while keeping anonymised accounts.

Event ticketing, end to end

  • Set-up in one screen: title, image, venue, sales window, capacity, members-only toggle, and ticket types (adult, child, member-only, free).
  • A public event page whose checkout adapts to who is looking: guests buy with name and email; signed-in members see member prices pre-filled.
  • Tickets that work at the door: every seat gets an unguessable code and a QR. Scanning opens an admit screen for the steward; a second scan is refused with the time of the first. Printable door list, attendees CSV, and live checked-in counters.
  • Aftercare: purchasers can resend their own tickets; the office can take cash for pending orders, cancel and refund with one confirmation (refunded through Stripe, tickets voided, purchaser emailed), or issue complimentary tickets with an audit note of who and why.
  • Sold out means sold out: when the last seat goes, a SOLD OUT stamp lands across the header image and checkout closes, quietly reopening if a refund or abandoned checkout releases seats.
  • Sharing that looks right: event links unfurl properly on WhatsApp, Facebook, and iMessage. We resize the share image to a 200 KB JPEG on the fly because WhatsApp silently drops previews over roughly 300 KB.

The room-hire diary

Week and month calendar views with a colour per room, double-bookings made impossible by the database rather than by care. Creating a booking emails the hirer a private payment link (no login needed): deposit by card, balance due 48 hours before the booking and charged automatically for card payers after a reminder a week out. Weekly, fortnightly, or four-weekly repeat series up to two years ahead, and the office's old calendar imported from an .ics file with clashes skipped and reported. Putting an event in a room blocks the diary automatically, widened by set-up and tidy-up buffers the public never sees.

Roles for a rotating committee

Super Admin, Membership Secretary, Treasurer, Office, and Read-only roles, plus Event Admin and Event User (the door page and nothing else). Every module is tied to a permission; Super Admins can create new roles from a checklist. New admins set their own password from a one-time link, no passwords are ever shared, and non-super-admins can only hand out roles they hold themselves, so a "manage users" permission can't be used to self-promote. Every action by an admin, member, guest, Stripe, or the system lands in a filterable audit log.

A hard problem: selling the last ticket twice

The obvious way to check capacity (count what's sold, compare, insert) has a gap between the count and the insert: under a ticket-release rush, two requests can both see "one left". We moved order creation into a single database function that takes an advisory lock on the event, recounts under that lock, and inserts the order atomically. The function also snapshots the price from the database row, so the client never sends an amount.

The subtler half is the hold. A pending order must count against capacity while the customer is on Stripe's payment page, but not forever. Stripe sessions expire after 30 minutes; our hold lasts 35, so a session completing at minute 29 always keeps its seat. If a session expires, a webhook releases the hold; if a payment lands on an order the office already cancelled, the tickets are not issued: the charge is refunded automatically and the office is told.

Pre-launch security review

Before the association put it in front of the public we ran a three-track review (authentication and server actions, database and row-level security, public endpoints and integrations) and fixed everything it found in one day: a privilege-escalation path through profile email changes, Stripe webhook replays that could double-record payments, missing rate limits on every public form, a latent data bug in the join flow, and missing security headers. The site now sends a content-security policy, rate-limits from the database, and passes npm audit with zero production vulnerabilities. Each finding and fix is in the commit history.

The first event sold out in three hours

Tickets for the Halloween party went on sale on the morning of 5 September 2026. By lunchtime it was gone: all 100 tickets sold to 47 buyers, every payment by card through Stripe, every attendee named for the door list, plus three complimentary tickets issued to the event's sponsor.

The morning also proved the failure paths. Three buyers abandoned Stripe's payment page mid-checkout; each came straight back and bought successfully, and each abandoned order cancelled itself half an hour later, releasing its held seats. No office intervention, no double charges, no oversold seats.

By the numbers: 32 database tables and 17 versioned migrations, 63 pages and API routes, around 16,200 lines of TypeScript, 69 commits from PRD to sell-out, and £0 monthly platform cost beyond Stripe's per-transaction fee at the association's scale.

Stack and delivery

Next.js 16 (App Router, Server Actions), Supabase (Postgres, Auth, Storage, London region) with row-level security as the last line of defence, Stripe Checkout and webhooks, AWS SES for transactional email with a fallback provider, and Vercel functions pinned to London so the whole stack is UK-hosted for GDPR. The door page is designed to be used on a phone in a dark hall.

The PRD was signed off on 19 May 2026, a beta of the portal was live by the end of June, and the events function was added through July. A full security review came before public release, with the room-hire diary joining at the end of August. Tickets for the first event went on sale on 5 September and sold out in three hours. The membership side is built and reviewed, launching once the office has run the 900-member spreadsheet through the importer, ahead of the January 2027 renewal season.

Running a community venue on spreadsheets and goodwill?

We build the systems that let volunteers run things like professionals: memberships, tickets, payments, and the door, without a full-time administrator.

Book a Consultation See More Work