Xkimi Xa Mali
A private savings collective platform, built for real money and real family stakes.
- Next.js
- React
- TypeScript
- PostgreSQL
- Prisma
- Inngest
- Redis
- Vercel
- JavaScript
- CSS
- PLpgSQL
- Shell
- Started
- May 2026
- Last push
- 6 days ago
- Commits this year
- 537
Written by AI from the repository · updated 5 days agoAI
The problem
A family savings group collects a monthly contribution from every member. Done over chat messages and memory, nobody can say with certainty who has paid, what the pool holds, or what happened to a payment that was later reversed. When the money is real, "roughly right" is not good enough.
How it works
Three Next.js applications share one backend: a member portal, an admin dashboard and a public website. The member portal also hosts the REST API (/api/v1, route → service → repository) and the scheduled jobs.
- Ledger — every rand lands in an append-only, double-entry pool ledger in PostgreSQL. The balance is the sum of credits minus debits, and a nightly reconciliation job checks it.
- Payments — an administrator records each transfer or cash payment against the member and the month, with proof of payment. A payment can be reversed but never erased.
- Jobs — 23 durable, retryable Inngest functions: contribution reminders, overdue sweeps, month rollover, ledger reconciliation, financial-anomaly watch, backups and more.
- Members — each member has a live view of their standing, a year-end forecast and on-time rate, group goals with pledges, and an inbox fed by SMS, email and in-app notifications.
Engineering decisions
The repository records its decisions as architecture decision records:
- PostgreSQL on Neon, chosen over Supabase — ACID transactions are non-negotiable for money.
- Inngest over plain cron — jobs that touch money must be durable and retryable.
- Netcash DebiCheck over PayFast — a South African recurring-debit mandate. The debit-order machinery is built and tested but deliberately dormant: a deployment with no collections provider refuses every money operation rather than pretending.
- Encryption at rest — bank and ID numbers are AES-256 encrypted, with a documented key-rotation procedure.
Built to be trusted
- 179 test files across the three apps.
- 55 database migrations, applied by CI.
- Six GitHub workflows: CI, governance, scheduled backups, a backup self-test, restore drills and preview-database clean-up.
- POPIA-aware data requests and erasure, an audit trail, and admin-signed PDF statements.
Where it stands
All three apps are live and the operating model works end to end: members pay, an administrator records it with proof. Development paused at a deliberate point (September 2026) — what the group needs next is a record of real contributions that a collections partner will accept, and only time and real payments produce that.
How it's moving.
537 commits in the last 26 weeks · 12 in the last 4
Latest commits
- fix(reports): give PDF export room to finish, and a real error message on failure (#536)
- fix(admin): surface stuck-PENDING members and let an admin resend verification (#535)
- fix(utils): invite form default matches the actual minimum (#534)
- fix(admin): every invitation was failing server-side, plus a real misclick bug (#533)
- chore(web): remove the fee-buffer retro audit, now that it has run (#532)
- chore(web): add a read-only audit for fee-buffer retro candidates (#531)
- fix(web): carve out bank-charge padding on an offline payment, not overpayment (#530)
- fix(web): enforce June 2026 as the earliest contribution period, and due dates fall at month-end everywhere (#529)
What it proves.
- GitHubused in 11 systems
- Lucideused in 4 systems
- Tailwind CSSused in 4 systems
- TypeScriptused in 4 systems
- Vitestused in 4 systems
- ESLintused in 3 systems
- Next.jsused in 3 systems
- PostgreSQLused in 3 systems
- Prismaused in 3 systems
- Reactused in 3 systems
- React Testing Libraryused in 3 systems
- Vercelused in 3 systems
- Zodused in 3 systems
- React Hook Formused in 2 systems
- React PDFused in 2 systems
- Redisused in 2 systems
- Sentryused in 2 systems