Lottery Pools
Group lottery pools with payments and OCR ticket import
What it is
A platform for running group lottery pools: people join a pool, contribute money, and own a fractional share of the tickets the pool buys. It tracks contributions, computes each participant’s ownership percentage, handles payments and refunds, and can even import physical paper tickets by scanning them. Not publicly hosted — it runs locally — but it’s the most feature-complete full-stack project in this set.
Architecture
A Go + Gin API (versioned /api/v1) with GORM over MySQL in
production and SQLite in tests, paired with a Next.js 15 (App Router) +
React 19 + TypeScript frontend. Auth is JWT; a centralized Axios client
attaches the token and redirects on 401.
Engineering highlights
- Payment-provider abstraction — Stripe’s PaymentIntent flow (SCA / 3-D
Secure ready) sits behind a
PaymentProviderinterface with a mock implementation, so the whole contribution → payment → confirmation workflow is testable without hitting Stripe. - OCR with a human in the loop — physical tickets are scanned via Google Cloud Vision, extracting serial numbers, barcodes, draw dates, and the numbers on multi-line plays with confidence scores. Extracted data is returned for review before it’s saved — never blindly trusted.
- Pool economics — fractional ticket shares, configurable contribution deadlines and refund windows, platform-fee accounting, and a clear pool lifecycle state machine (active → closed → completed/cancelled).
- Transactional integrity — multi-step operations (payment + ticket writes) are wrapped in GORM transactions that roll back as a unit, so a failure never leaves a pool in a half-updated state.
- End-to-end type safety — Go structs mirror TypeScript interfaces, with Zod validating forms on the client and matching the server’s rules.
- Tested both sides — service layer covered with testify + sqlmock (the same models run against SQLite in tests); React flows with Jest + Testing Library.
What I learned
Splitting thin HTTP handlers from a service layer kept the payment, OCR, and pool-economics logic independently testable — which mattered a lot once real money (Stripe) and unreliable input (OCR) entered the picture and “mostly works” stopped being good enough.