01 / Systems / ThriftIt

ThriftIt

A thrift marketplace where every item is one of one, so only one buyer can win.

Role
Founder and developer
Stack
TypeScript, Express, Next.js
Payments
Paystack, NGN, GHS, KES, ZAR
Status
In progress, 2026
Checkout locks the product row. If the item is available it is reserved for 10 minutes and the buyer goes to Paystack, otherwise they get item already taken. A verified webhook marks the item sold and splits the vendor's share, or releases it on failure. A job every 60 seconds releases expired reservations. YES NO FAIL SUCCESS AFTER 10 MIN BUYER starts checkout LOCK ROW SELECT FOR UPDATE FREE? RESERVED 10 minutes PAYSTACK 409 item already taken WEBHOOK HMAC verified BACK ON SALE lock released EXPIRY JOB every 60 seconds SOLD vendor share split
Fig. 1 · Checkout, first to pay wins

Thrift vendors in Nigeria, Ghana, Kenya and South Africa mostly sell through WhatsApp and Instagram. Every item is a single piece. If two people check out the same jacket at the same moment, one of them is about to pay for something they can't have.

  • A buyer must never pay for an item that's already gone.
  • Vendors need their money without chasing anyone for it.
  • Payments run in four currencies.

First to pay wins. When a buyer starts checkout, the server locks the product row in Postgres (SELECT FOR UPDATE) inside a transaction. If the item is still available, it's marked reserved for that buyer and they go to Paystack. Anyone else who tries gets "item already taken" straight away.

The reservation lasts 10 minutes. A background job checks every minute and puts expired reservations back on sale. I chose a scheduled job over a timer in memory, because a timer dies with the server and the job doesn't.

Paystack tells us how the payment went by webhook. Every webhook is verified with an HMAC signature before we trust it. A successful charge marks the item sold. A failed one releases it straight away.

Each vendor has a Paystack subaccount, so their share is split off at checkout instead of being paid out by hand later.

Pro vendors can upload a photo and get a draft listing back: title, description, tags, condition and a suggested price. The AI sits behind a small provider interface, with Claude as the first implementation, so I can swap models without touching the listing code. Free accounts get a clear "upgrade to Pro" response instead.

A reserved item is off the market for up to 10 minutes, even if the buyer walks away. That felt fairer than letting two people pay and refunding one.

Not live yet. Before launch I'm adding concurrency tests for the lock and making the webhook handler idempotent, so a retried webhook can't be processed twice.

TypeScript, Node.js and Express on Railway · Next.js on Vercel · PostgreSQL on Supabase with Prisma · Paystack · Cloudinary