← Back

Project deep dive

Odessa Symphony Guild

A production membership and payment platform for the Belles & Beaux program — replacing manual spreadsheet intake with structured enrollment, QuickBooks invoicing, and automated payment reconciliation via signed webhooks.

Next.js 16React 19TypeScriptTailwind CSSSupabasePostgreSQLQuickBooks Online APIIntuit OAuth 2.0ZodReact Hook FormWeb Crypto APIVercel
Odessa Symphony Guild Belles & Beaux platform

Overview

The Odessa Symphony Guild runs a student membership program called Belles & Beaux. Before this platform, enrollment happened through paper forms and email, dues were tracked in spreadsheets, and payment status required manual reconciliation with QuickBooks. The system worked — until it didn't.

This project replaced that workflow end-to-end: families complete a structured three-step registration with dynamic guardian capture, legal consent, and live dues calculation. Submitting the form generates a QuickBooks customer record and sends a payment-enabled invoice automatically. When payment clears, a signed webhook updates the student's status in Supabase without any admin involvement.

The technical challenge wasn't any single feature — it was the combination: real third-party payment integration, compliance-grade security, schema evolution without breaking historical records, and making sure none of it could fail in a way that blocked a family from registering their kid.

Features

What it does

QuickBooks OAuth 2.0

Full Intuit OAuth flow: authorization endpoint, callback handler, token exchange, and secure storage. Tokens encrypted at rest with AES-256-GCM using environment-managed keys. Automatic access token refresh on expiry, including refresh-token expiry handling.

Payment → Invoice reconciliation

Signed webhook endpoint for Intuit payment notifications with HMAC-SHA256 signature verification using timingSafeEqual. Resolved the non-obvious event modeling problem: Payment events don't directly contain Invoice IDs — you have to traverse Payment.Line[].LinkedTxn[] to find them. Automating paid-status sync in Supabase required understanding this structure from the QB API docs and logs.

Edge-compatible session security

Admin session validation originally used Node.js crypto — which breaks on the Next.js Edge runtime. Rewrote HMAC-SHA256 verification using Web Crypto API (crypto.subtle) for full Edge middleware compatibility. httpOnly, sameSite=strict, secure cookies with 8-hour bounded lifetime.

3-step enrollment flow

Student registration with React Hook Form + Zod step-aware validation. Dynamic guardian system (1–4 guardians) with useFieldArray, relationship selection, and a "same address as Guardian 1" checkbox that auto-copies fields. Zod superRefine enforces cross-field rules: at least one guardian email required for invoice delivery.

Live dues and reCAPTCHA

Dues and late fees loaded from a settings API at runtime with resilient hardcoded fallback. Grade-aware membership logic auto-assigns freshman pricing. Google reCAPTCHA v2 on the client with server-side token verification to prevent bot submissions.

Legal consent and e-signature

Media release authorization embedded directly into the signup flow — capturing guardian consent, social media opt-out, e-signature, and submission timestamp. Stored per-student alongside the enrollment record.

Graceful degradation

QuickBooks failures at any stage — invoice creation, customer lookup, payment link generation — do not block student registration persistence. The enrollment saves to Supabase first; QB operations are best-effort with logged errors and retry paths.

Admin tooling

Real-time roster with search, grade filter, paid/unpaid filter, KPI summary cards, and one-click payment status toggle. Print-optimized layout with hidden controls. Pricing panel for 6 configurable dues/fee keys with live updates reflected immediately on the public join form.

Technical Highlights

Under the hood

Security

  • AES-256-GCM encryption for QuickBooks OAuth tokens at rest
  • HMAC-SHA256 webhook signature verification with timingSafeEqual (timing-safe comparison)
  • Web Crypto API (crypto.subtle) for Edge-compatible admin session validation
  • httpOnly, sameSite=strict, secure session cookies with 8-hour lifetime
  • All /admin/* routes protected via Next.js middleware with Edge-compatible session verification
  • Secure response headers: no-cache, nosniff, DENY framing on all sensitive API routes

Database

  • Supabase migration set covering students, encrypted QB tokens, settings, guardian expansion, and media-release fields
  • Flexible 1–4 guardian arrays mapped to 36 relational DB columns with zero data loss for existing records
  • Student records scoped by school year for clean multi-year data isolation
  • Backward compatibility: supports both new guardian_N_* columns and legacy mom_*/dad_* records in display
  • Supabase service role key for trusted server-side operations; RLS preserved for public routes

QuickBooks

  • Customer find-or-create flow keyed by primary email to avoid duplicate payer records
  • Invoices with due dates, line items, and ACH/credit-card online payment enabled (AllowOnlinePayment: true)
  • Multi-recipient invoice delivery for additional guardian email addresses
  • intuit_tid correlation IDs logged on all QB API calls for production debugging
  • MOCK_PAYMENT_MODE feature flag with singleton mock QB client — full local dev without live credentials

Frontend

  • React Hook Form + Zod with step-aware validation and progressive completion UX
  • useFieldArray for dynamic guardian management with add/remove and conditional field copying
  • Zod superRefine for cross-field business rules across dynamic arrays
  • Live phone number formatting during keypress for student and guardian inputs
  • Live dues loaded from settings API with resilient hardcoded fallback

Engineering Challenges

Where it got interesting

01

QB Payment → Invoice reconciliation

QuickBooks webhook events for payments don't include the Invoice ID directly. To automate paid-status sync, you have to traverse Payment.Line[].LinkedTxn[] to find the linked invoice. This structure isn't obvious from the top-level documentation — it required reading the API reference carefully, reproducing the event shape from logs, and building the traversal logic to extract the correct IDs.

02

Edge runtime crypto rewrite

The original admin session validation used Node.js crypto module, which is unavailable in the Next.js Edge runtime. Middleware runs on the Edge, so all Node APIs break silently or throw. The fix was a full rewrite to Web Crypto API (crypto.subtle) — same algorithm, different API surface, with async/await throughout since crypto.subtle is promise-based unlike the synchronous Node version.

03

Dynamic guardian schema migration

The original schema had mom_* and dad_* columns — a two-guardian assumption baked into the data model. Expanding to 1–4 guardians required a migration that added 36 new relational columns while keeping all existing records readable and displayable. The display layer had to support both old and new column formats simultaneously without a big-bang migration of historical data.

04

Graceful QB degradation

QuickBooks is a third-party dependency that can fail at any point — auth expiry, API rate limits, network timeouts. Student registration couldn't be blocked by any of those. The solution was a clear separation: Supabase persistence is the primary operation, QB operations are secondary and non-blocking. Failures are logged with enough context (intuit_tid, error shape) to retry manually if needed.

← Back to portfolio