~/austinmula
carloura botique
Web ApplicationLive

Carloura Elegance Website

A headless storefront for a Nairobi fashion boutique, built mobile-first around M-Pesa rather than cards. Next.js App Router and Strapi 5, with a Pesapal checkout that writes the order before redirect and confirms payment server-side — so a dropped mobile connection never loses a paid transaction.

Next.js App Router front end, Strapi 5 CMS, and Pesapal checkout so customers pay with M-Pesa instead of a card. Mobile-first, because the audience shops on a phone.

Stack

Front end - Next.js 16 (App Router, ISR), React 19, TypeScript, Tailwind v4, Framer Motion, Zustand

Back end - Strapi 5, PostgreSQL, Cloudinary

Payments - Pesapal API v3 (M-Pesa, KES)

Infra - Netlify (storefront) + Railway (CMS)

Design - Pencil.dev for layout variants

Case study

The constraint

Card penetration in Nairobi is low - M-Pesa is how people actually pay, so a Stripe-shaped checkout would have converted badly no matter how good the site looked. The catalogue is visual but consumed on mobile over variable connections. And the owner needed to run the catalogue without a developer or a deploy.

M-Pesa checkout

Pesapal v3 is a redirect flow. The order is written to Strapi as pending before the redirect - if a customer pays and then loses signal on the return trip, the order still exists with its number, ready to reconcile. Creating it on return would lose the transaction entirely, which on Kenyan mobile networks isn't a rare edge case. Payment is confirmed server-side via GetTransactionStatus rather than by trusting the callback's query string. Order numbers read as CE-YYYYMMDD-XXXXX so they work over WhatsApp, which is how support actually happens.

Cart

Zustand with persist, so a bag survives a refresh. Lines are keyed on product id + size + colour, so the same dress in two sizes is two lines. Persisted state forced a hydration guard on checkout - a server-rendered empty cart disagreeing with a rehydrated full one flashed the empty-bag state.