Skip to content
Dynamic Reasoning

Case study

2025–present

Who's Brew

A multi-vendor specialty coffee marketplace, built end-to-end and live in production.

Client

Who's Brew

Role

Sole engineer — system design, frontend, backend, infrastructure

Status

Live in production

Services

Production web & commerce · Applied LLM


The problem

Specialty coffee is a fragmented market. Every roaster runs their own storefront. Every checkout is separate. Every customer pays shipping a dozen different ways and reassembles a single order out of half a dozen browser tabs. The customer experience punishes curiosity, and the vendors — many of them small, family-run roasters — spend more on payment processing and shipping software than they ought to.

The brief was to fix that with a single marketplace where independent vendors share a unified cart, a single checkout, and a transparent shipping flow, without losing the per-vendor autonomy that makes specialty coffee specialty. Vendors keep their brand, their pricing, their wholesale relationships, their inventory. Customers see one cart and one receipt. Who’s Brew sits in the middle, makes the payments work, generates the shipping labels, and earns a commission per order.

I was the sole engineer. I designed the system, built the frontend, built the backend, set up the infrastructure, and continue to ship to it.

What’s in the system

A short tour of the surface area, because the scope matters for what comes after:

  • Customer experience. Browse, search (both keyword and natural-language), a multi-vendor cart, a single checkout, order tracking, monthly subscriptions, reviews, free-sample requests for business buyers, and a passport that visualises every country of origin the customer has purchased from.
  • Vendor experience. A four-step onboarding (storefront, shipping, products, Stripe Connect), a dashboard for order fulfillment with one-click shipping-label printing, inventory tracking, discounts and promotions, a public storefront page, and a web crawler that auto-populates products from the vendor’s existing site during onboarding.
  • Admin experience. Vendor approvals, platform-wide discounts, commission configuration at three levels (site default, per-vendor, per-product), an affiliate program with HMAC-signed referral cookies, store-credit grants with an immutable audit ledger, refund-as-credit workflows, customer support tooling, and analytics.
  • Affiliate experience. Tracked referral links, conversion ledger, configurable payout schedule via either Stripe Connect or PayPal batch, the ability to purchase as a customer (with a self-referral block on the commission).
  • Subscriptions. Monthly recurring delivery of selected products with per-product subscription pricing.

Five user roles in total: admin, employee (with department-scoped permissions across seven presets), vendor, affiliate, customer. A given email can hold different roles in separate accounts, but never two roles in the same one.

The engineering decisions that matter

A few choices defined the shape of the system.

A split data layer

Customer-facing data and staff/admin data live in separate MongoDB databases, not just separate collections. That separation enforces a hard boundary: the customer application physically cannot read staff records by accident, and analytics queries against staff operations don’t ride the same connection pool as a checkout. It made the permission model simpler and the operational posture safer.

Prices are never trusted from the client

The cart is a hint. The real number is computed on the server, twice — once when the payment intent is created, and again at the moment the order is written. Both passes re-resolve every line item’s price from the database, recompute bundle discounts, bulk-pricing tiers, wholesale eligibility, coupon validity (with stacking rules), shipping, and the service fee. A price drift between the two passes is rejected with a clear price_changed response so the client can refresh and retry. This is unglamorous but essential: the marketplace handles real money and the math has to be defensible.

Idempotency everywhere

Payment intents carry a deterministic idempotency key derived from the user, amount, cart contents, and a five-minute window. Per-vendor Stripe Connect transfers carry per-order, per-vendor keys. Affiliate conversion writes are deduplicated. Stripe webhooks are processed through a DB-backed event log so a retried webhook doesn’t double-credit anything. None of these are interesting until they matter, and when they matter they matter a lot.

A store-credit system with a real ledger

Store credit is a payment method as far as the math is concerned — applied last at checkout, after subtotals, discounts, shipping, and fees. The source of truth is an immutable creditLedger collection that records every grant, every spend, every reversal. The fast-path creditBalance field on the user profile is treated as a denormalized cache, and a reconciliation script audits the drift on a schedule. If a balance ever disagrees with the ledger, the ledger wins.

There are caps in admin configuration — a maximum per-grant amount and a maximum total balance per user — to keep human error from becoming a financial incident.

Stripe’s minimum charge, handled honestly

Stripe will reject a charge under fifty cents. If a customer applies store credit and the residual cash amount falls below that floor, the payment intent is rejected with amount_too_small_after_credit and the customer is invited to adjust the cart. If credit covers the full order, the Stripe call is skipped entirely and the order is written directly with a zeroPayment flag.

A five-role permission model with department presets

Admins have implicit full access. Employees carry an explicit permissions[] array in the form <resource>.<action>, with .manage implying .view. Department presets — Developer, Marketing, Accounting, Social Media, Customer Support, Operations, Content — exist so that hiring a new employee is one dropdown, not a manual permission audit. Vendors are scoped to their own products. Affiliates can purchase as themselves but never see their own commission dashboards from the customer view.

Permissions are always re-checked against a fresh user record on the server. The JWT carries the role for routing, not for authorization. Session revocation is two-pronged: a per-token jti Redis key for surgical revocation, and a sessionVersion bump on the user record for everything-everywhere.

AI features served from a single provider

Natural-language search (“a fruity light-roast Ethiopian under twenty dollars”), personalized recommendations, vendor news curation, and onboarding-content rewriting all run through one Cerebras-served Llama 3.1 8B integration. Standardising on a single provider kept the operational footprint small and the cost model predictable. The recommendation engine is fed by browse history, purchase history, and the stored prompts the customer has searched with — a small but real feedback loop.

A passport that doubles as gamification

Customers earn a stamp in their passport for every country of origin they buy from. The passport is the centrepiece of the landing page and the customer dashboard — a world map, achievement badges, leaderboards. It exists because specialty coffee is fundamentally about origin, and a feature that celebrates that fact turns out to drive both repeat purchase and conversation. The data model is a simple per-customer ledger of countries-visited, but the visual implementation was the most fun frontend work in the project.

Affiliate attribution that holds up

Commission attribution is resolved against a four-step priority chain: the affiliate’s own coupon at checkout, the customer profile’s stored attribution if they were logged in when they arrived via ?ref=, a Redis cache of the most recent attribution event, and a thirty-day HMAC-signed cookie as the final fallback. Self-referrals are blocked at order creation by comparing the affiliate’s user ID against the customer’s. Payouts run automatically on a configurable weekday once the balance crosses a hundred dollars, through either a Stripe Connect transfer or a PayPal batch payout.

Security posture

The marketplace handles real money and real customer data, so the security work was non-negotiable:

  • Stripe Elements for PCI scope reduction — no raw card data on the server
  • Webhook signature verification on every incoming Stripe event, with DB-backed deduplication
  • HMAC CSRF tokens with timing-safe comparison and an origin/referer check on every non-GET request
  • Rate limiting via Redis with a configurable window, fail-open if Redis is unreachable so the site doesn’t go dark on a cache outage
  • HSTS, secure cookies, server-side input validation on every endpoint
  • Image uploads through presigned S3 URLs so credentials never reach the client
  • Session versioning revokes outstanding JWTs on password or role change

The stack, briefly

Next.js 14 on EC2. MongoDB Atlas with the split-database pattern above. Redis 7. Stripe + Stripe Connect. Shippo for centralised label generation. SendGrid for transactional email through SQS. Sentry for error tracking. AWS for the rest of the infrastructure — S3 buckets scoped per concern, SQS queues for the async work, Lambda for the web crawler that auto-populates vendor catalogues. CloudFront for static assets with aggressive cache headers and shorter TTLs on product imagery. Frontend in React 18 with Tailwind and GSAP for the passport animation.

What’s live now

The site is live at whosbrewshop.com. Vendors onboard, customers check out, orders ship, payouts fire on the configured weekday, the affiliate ledger reconciles, and the passport fills in country by country. The system is in active development with new capability shipping continuously.

Why this matters for what I do

Who’s Brew exists because a small team needed to compete with infrastructure that takes large teams to build elsewhere. That’s the kind of work I take on: production systems where the engineering has to be right and the team to deliver it is intentionally small. If you’re sitting in that position — a real product, a real budget, and not enough people to throw at it — that’s the conversation I want to have.


Next

Working on something with this shape?

Book a call