Point-of-sale for charity golf events.
Accept in-person payments via Tap to Pay on iPhone.
Built with production-grade tools for reliability and performance
iOS only — Deployment target iOS 16.7+ — New Architecture enabled — Hermes JS engine — Zod 4 validation — Reanimated 4 spring animations
Everything needed to run payment collection at a charity golf event
Accept contactless NFC payments using just an iPhone. No card reader, no dongle, no extra hardware. Powered by Stripe Terminal SDK with Apple's Tap to Pay framework.
Three-tier permission system enforced on both frontend and backend
Global admins bypass all event-level permission checks. Owners are auto-assigned when creating an event.
From category selection to confirmed payment in under 30 seconds
Defense in depth: auth, validation, and integrity at every layer
Every API call verified via JWKS signature verification against Clerk. HTTP endpoints use jose library for full JWT validation.
Processing fee calculated authoritatively on the server. Client displays estimated fee, server enforces actual charge. No client-side manipulation.
Transactions always start as "pending". Only Stripe webhooks can transition to "succeeded" or "failed". Client cannot set final status.
Permissions enforced on both frontend (UI gates) and backend (mutation guards). Global admins bypass event-level checks.
Stripe webhook signatures verified via stripe.webhooks.constructEvent(). Prevents forged webhook attacks.
All Stripe secret keys are server-side Convex env vars. Client only holds publishable keys. Tokens stored in encrypted SecureStore.
Stripe-hosted onboarding keeps PCI burden off Golfund
Event owner opens Settings and taps the bank account connection button.
Stripe Connect creates an Express account with business_type: "non_profit" via our API.
Owner completes identity verification, bank details, and EIN on Stripe's secure hosted flow.
Account verified. All payments for this event flow directly to the charity's bank account.
Stripe hosts the entire onboarding flow. Golfund never touches bank credentials.
Trust badge displayed during onboarding. Industry-standard payment infrastructure.
Each event connects its own charity bank account. Funds route automatically.
Create events, invite volunteers, and manage roles with ease
Name, date, and optional location. Creator becomes the Owner automatically.
Add volunteers and admins by email. They get access instantly.
Owner, Admin, Member. Each level with clearly defined permissions.
Each user picks their active event. Own Event A, volunteer at Event B.
Owners can transfer event ownership to another member when needed.
Each event connects its own Stripe account. Funds route to the right charity.
A single user can own one event and volunteer at another, switching active events instantly.
Real-time insights during the event, detailed reporting after
Category-by-category breakdown with live running total. Updates across all devices instantly.
Full transaction log with text search and category filter. Find any transaction in seconds.
Export all transactions to CSV for post-event reporting. Cells sanitized against injection attacks.
Full receipt view with golfer info, fees, timestamps. Share receipts via native share sheet.
Admins and owners can process refunds. Status tracked through Stripe webhooks.
Share formatted transaction receipts via Messages, Email, or any installed app.
Full navigation structure and screen inventory
Email/password + Google SSO via Clerk
Category grid, event header, running total
Amount input with presets or custom
NFC tap interface, no back gesture
Success/failure with haptics
Search + filter by category
Full receipt + share
Category breakdown + CSV export
User info, Tap to Pay status, sign out
Link bank account via Stripe
Create, switch, manage events
Invite, remove, change roles
Custom category builder
PaymentProcessing and PaymentConfirmation disable back gesture and hardware back to prevent accidental navigation during payment.
Provider stack, data flow, and real-time architecture
Convex reactive queries are the single source of truth. No Redux, no Zustand, no Context-based stores.
useQuery hooks subscribe to Convex. Data pushes to all connected devices in under 100ms.
Transaction status flows: pending (creation) then succeeded / failed / refunded (Stripe webhooks only).
Recent changes and improvements