A full-stack, AI-assisted e-commerce platform built solo β from checkout reliability to admin operations.
π Live: shopflowai.me
Features β’ Tech Stack β’ Getting Started β’ Architecture β’ Testing β’ Live Demo
ShopFlowAI is a production-style e-commerce SaaS platform covering the full lifecycle of an online store β customer browsing and checkout, Stripe payments, and a complete admin back office β built with a deliberate focus on the problems that break real systems in production: overselling, duplicate webhook events, dead sessions, and inconsistent state after a crash mid-transaction.
It was designed and built end-to-end by a single developer as a deep dive into full-stack engineering, distributed-systems thinking, and production-readiness β not just "does the feature work," but "does it hold up under failure."
The app is deployed and publicly accessible β frontend on Vercel, backend API on a Node host, MongoDB on Atlas. Stripe is running in test mode, so you can walk through the full checkout flow without a real card:
| Field | Value |
|---|---|
| Card number | 4242 4242 4242 4242 |
| Expiry | Any future date |
| CVC | Any 3 digits |
- Browse, search, and filter products by category
- Cart & wishlist, with persistent state across sessions
- Secure Stripe checkout using Stripe Elements (no raw card data ever touches the server)
- Google Sign-In via Firebase, alongside standard email/password auth
- Email verification, password recovery, and account-recovery flows
- Order tracking, downloadable PDF invoices, and product reviews
- Revenue and sales analytics
- Product & category management, with Cloudinary image uploads
- AI-generated product descriptions when creating/editing products (Gemini API)
- Order management β update fulfillment status, issue refunds, export invoices
- User management β search, filter, paginate; change roles; ban/unban accounts with immediate session termination
- Inventory reservation system β stock is held (not deducted) during checkout and auto-released if payment never completes, preventing overselling
- Idempotent Stripe webhooks β duplicate events are detected and safely ignored, with amount/currency/order cross-checks
- Outbox pattern for refunds and media cleanup, so a crash mid-transaction never leaves the database pointing at something that no longer exists
- Hashed refresh tokens (SHA-256, unique
jti) β a database leak alone can't be used to hijack a session - In-memory access tokens with automatic, deduplicated silent refresh β nothing sensitive sits in
localStorage - Redis caching for products, categories, and analytics
- Rate limiting, input validation, and centralized error handling across every module
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite, React Router |
| Backend | Node.js, Express |
| Database | MongoDB (Mongoose), replica-set transactions |
| Caching | Redis |
| Payments | Stripe (Elements, webhooks, idempotency) |
| Auth | JWT (access + refresh), Firebase Google Sign-In |
| Media | Cloudinary |
| AI | Google Gemini API |
| Resend (HTTPS API) | |
| Testing | Node.js test runner, mongodb-memory-server, Vitest |
| CI/CD | GitHub Actions (lint β test β build β audit) |
- Node.js 22.12+ (
nvm use) - Docker (for a local MongoDB replica set β required for transactions)
docker compose up -d mongo
docker compose ps # wait for the health check (initializes replica set rs0)
# optional Redis cache
docker compose --profile cache up -dcp server/.env.example server/.env
cp client/.env.example client/.envFill in your MongoDB, JWT, Resend, Stripe, Cloudinary, Gemini, and Firebase credentials. Never commit .env or Firebase service-account JSON.
cd server && npm ci && npm run dev
# in a second terminal
cd client && npm ci && npm run dev| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| API health check | http://localhost:5000/api/v1/health |
π³ Payment lifecycle (click to expand)
Stripe checkout uses a bounded inventory reservation and idempotent payment flow:
- The client sends a UUID
checkoutId; duplicate order requests return the same order. POST /ordersreserves inventory for Stripe but keeps cart items until payment succeeds.- The reservation expires after
PAYMENT_RESERVATION_MINUTES(default 30). POST /payments/create-intentsnapshots the exchange rate, USD minor amount, and currency, and uses a Stripe idempotency key.- Signed webhooks are stored once by Stripe event ID and must match order ID, user ID, PaymentIntent ID, amount, and currency.
- A success webhook commits inventory, marks the order paid/processing, removes purchased cart items, and sends paid invoice/notifications.
- Failed card payments remain retryable until reservation expiry.
- Expired/cancelled reservations restore stock and queue PaymentIntent cancellation.
- Late payment after cancellation never revives the order; it queues an idempotent refund.
- Refunds run through a Mongo-backed outbox (
OutboxEvent) rather than inside Mongo transactions. - Cloudinary image replacement/deletion is also queued through the outbox so DB state never points at an image deleted before commit.
The API process starts the commerce worker automatically. For a separate scheduled worker, set DISABLE_COMMERCE_WORKER=true on API instances and run:
cd server
npm run worker:onceRun it every 30β60 seconds via your process manager/scheduler. Multiple workers are safe β events are atomically claimed and Stripe calls use idempotency keys.
π Session security (click to expand)
- Access tokens live only in browser memory; legacy
localStoragetokens are removed automatically. - The HttpOnly refresh cookie restores a session after reload.
- One failed authenticated request performs a single deduplicated refresh and retries once.
- Refresh tokens are stored as SHA-256 hashes in MongoDB with a unique JWT
jti. - Existing raw refresh-token sessions migrate automatically on their next rotation/logout.
- Banned users are rejected immediately β even with a still-valid access token β and lose all active sessions the moment they're banned.
ποΈ Upgrading an existing database (click to expand)
Back up MongoDB first, deploy with the commerce worker disabled, then run once:
cd server
npm run migrate:phase1 # marks legacy inventory state, snapshots Stripe amounts
npm run migrate:phase2 # hashes any legacy raw refresh tokens (idempotent)Fresh databases do not need either step.
π₯ Firebase Admin setup (click to expand)
The adapter resolves credentials in this order:
FIREBASE_SERVICE_ACCOUNT_JSONenv var β a stringified service-account JSON, used in production (Render/Vercel/etc. where dropping a local file isn't practical).- Fallback: a local file at
server/src/config/firebase/shopflowai-firebase-adminsdk.jsonβ used for local development. That path is gitignored; download the service-account file from Firebase console.
# Client
cd client
npm run lint
npm test
npm run build
# Server
cd server
npm testGitHub Actions runs this full pipeline β lint, test, build, and npm audit β on every push and pull request to main. See .github/workflows/ci.yml.
Server integration tests run against a real in-memory MongoDB replica set (via mongodb-memory-server), so transactional flows (checkout, reservations, refunds) are tested against real Mongo transaction semantics β not mocks.
Stripe local webhook testing
stripe listen --forward-to localhost:5000/api/v1/payments/webhookCopy the CLI webhook secret to STRIPE_WEBHOOK_SECRET. Use Stripe test mode only.
ShopFlowAI/
βββ client/ # React 19 + Vite frontend
β βββ src/
β βββ pages/ # Route-level views (customer + admin)
β βββ components/ # Shared UI components
β βββ contexts/ # Auth, Cart, Wishlist, Toast providers
β βββ services/ # API client, Stripe, Firebase
βββ server/ # Express + MongoDB backend
β βββ src/
β βββ controllers/ # Route handlers
β βββ services/ # Business logic (orders, payments, reservationsβ¦)
β βββ models/ # Mongoose schemas
β βββ middleware/ # Auth, validation, rate limiting, error handling
β βββ routes/ # API route definitions
βββ docker-compose.yml # Local MongoDB replica set + optional Redis
βββ .github/workflows/ci.yml # Lint β test β build β audit pipeline
- Use Node 22.12+ on all API, worker, build, and CI machines.
- Use MongoDB Atlas or a production replica set β never deploy a standalone MongoDB (transactions require a replica set).
- Run at least one commerce worker continuously.
- Redis is used for response caching only (products, categories, analytics) β rate limiting is in-memory per instance (
express-rate-limit), so it resets per process and isn't shared across horizontally-scaled API instances yet. - Configure Stripe webhooks with the raw request body and HTTPS.
- Back up and monitor the
orders,stripeevents, andoutboxeventscollections. - Alert on failed outbox events and
refundStatus: failed.
Ali Hassan BS Computer Science, COMSATS University Islamabad (Sahiwal Campus)
- GitHub: @alisheikh2