Overview
A rewrite of a fashion retailer's loyalty program, which had grown inside a legacy Java system. Members use a mobile app as their loyalty card. Staff use a web portal at the counter to find a member, award points for a purchase and confirm redemptions. The back office manages offers, notifications and reports.
What I built
- Points ledger. Points are stored as lots with their own expiry, spent first-in first-out, and can be held while a redemption waits for confirmation. Every change carries an idempotency key, so a retried request never awards points twice.
- One writer. Only one service changes balances. The old system had two point engines writing one balance column; the new design makes that impossible, and a dashboard check reconciles cached balances with the ledger.
- API and worker. One NestJS codebase with two entry points: the HTTP API, and a BullMQ worker for scheduled push notifications and hold expiry that calls the same services.
- Shared contracts. Zod schemas in a shared package, used by the API, the portal and the mobile app.
- Apps. A Next.js portal for the counter and back office, and an Expo React Native member app, in TestFlight.
Stack
NestJS, Prisma, MySQL, Redis, BullMQ, Zod, Next.js, Expo (React Native).



