Inspiration
Every expense-splitting app stops at the same place: it tells you who owes whom, and then nothing happens. Someone still has to send a Venmo request, someone still forgets, and the balance sits there for weeks. I wanted to close that loop; an app where settling up isn't a separate manual step, it's a button.
What it does
Fair Split lets a group track shared expenses (a trip, a dinner, rent) and automatically computes each member's balance. Instead of suggesting every possible payment between every pair of people, it runs a debt-simplification algorithm to find the minimum number of transactions needed to settle the whole group. Each suggested payment is backed by a real Stripe Connect transfer, clicking "Record" moves real money (in Stripe's test mode) between connected accounts and returns a real, verifiable transfer ID.
How we built it
- Frontend/backend: Next.js 14 (App Router) + TypeScript + Tailwind, with Server Actions for all mutations
- Data: SQLite via better-sqlite3; Groups, Members, Expenses, ExpenseSplits, and Settlements
- Payments: Stripe Connect (Express accounts), each group member has a real connected account; settlements call stripe.transfers.create() directly
- Algorithm: a greedy min-transaction debt simplifier, matches the largest creditor with the largest debtor repeatedly until the group nets to zero
- Design: a custom "travel ledger" visual system (Fraunces serif for headers/amounts, Inter for UI, IBM Plex Mono for account/transfer IDs) with settlement suggestions styled as ticket stubs, and a "SETTLED" stamp animation on a completed transfer
- Workflow: built solo, with Cursor handling implementation from detailed, incremental prompts while I drove architecture, the settlement algorithm, and the Stripe integration
Sample verified transfer from testing: tr_1U8S8dIDZeW0vlN5CNlXEXwI
Challenges I ran into
Stripe Connect account types took real debugging: accounts created for accepting payments ("merchant" configuration) can't receive transfers, only accounts with "recipient" configuration can. I hit this as a live transfers.create() error mid-build, traced it to account capabilities, and fixed my test account setup accordingly. I also hardened the settlement flow after finding that a rapid double-click could theoretically trigger two real transfers for the same suggested payment,fixed with a synchronous submission lock.
Accomplishments that I'm proud of
Every transfer in this app is real: not mocked, not simulated client-side. The settlement math is also verifiably correct: my debt-simplification algorithm was tested against known cases (e.g., one creditor with multiple debtors) and produces the exact minimum transaction set.
What I learned
Stripe Connect's account-type model (merchant vs. recipient capabilities) isn't obvious until you hit it in practice. I also learned a lot about scoping a solo hackathon build: cutting multi-currency, real auth, and custom split percentages early let me spend the available time on the two things that actually mattered, a real payment integration and a settlement algorithm that's provably correct.
What's next for Fair Split
Real (live-mode) bank payouts instead of test mode, multi-currency support for international trips, and a two-leg payment flow (debtor pays the platform via Checkout, platform pays the creditor) instead of the simplified single-leg transfer used in this demo.
Built With
- better-sqlite3
- cursor
- next.js
- node.js
- react
- sqlite
- stripe
- stripe-connect
- tailwind-css
- typescript
Log in or sign up for Devpost to join the conversation.