Muhammad Rafi Arsya
Back to All Projects
Full Stack  ·  Marketplace  ·  Self-Hosted  ·  Ongoing 2026

CampusBay

A verified peer-to-peer student marketplace — built and self-hosted solo. Real-time chat, Stripe payments, escrow, flash sales, AI support, and a wallet system. Live at campusbay.store.

React 18 + Vite Node.js + Express PostgreSQL + Redis Socket.IO Stripe Docker Cloudflare Tunnel
Live & Actively Developing — campusbay.store · Self-hosted on Linux mini PC · Docker Compose · Shipped solo
Status
In Development
Year
2026
Role
Solo Founder & Dev
Platform
Web + Android TWA
01 Project Overview

CampusBay is a production-grade peer-to-peer student marketplace I'm building solo as a self-development project. Originally built for University of Malaya, now expanding Malaysia-wide — a trusted, verified space for students to buy and sell textbooks, electronics, hostel furniture, and more.

Registration is gated to institutional email addresses, verified via 6-digit OTP — every user is a real verified student, eliminating the scammers that are rampant on Carousell and Facebook Marketplace.

The honest answer: Building a full marketplace solo — auth, payments, real-time chat, escrow, notifications, admin panel, hosting, DevOps — while learning as I go. Every feature is production-deployed and live.
02 System Architecture
React 18 + Vite (Frontend)
SPA · Context API · TanStack Query
Node.js + Express (Backend API)
15+ route modules · JWT · Sequelize ORM
PostgreSQL 16 + Redis 7
Primary DB · OTP/session cache · wallet_transactions table
Socket.IO (Real-Time Layer)
Chat · order tracking · notifications · admin broadcasts
Stripe + Wallet System
Checkout · webhook fulfillment · in-app wallet balance · topup
Docker Compose + nginx + Cloudflare Tunnel
5 containers · self-hosted Linux mini PC · protocol: http2
03 Order Flow & Escrow Logic

The order lifecycle uses a state machine to move funds safely through an escrow-style flow:

Pending
Confirmed
Meetup Scheduled
Completed
Cancelled
Rejected (30-min)
01Wallet Payment Math
order.totalAmount = listingPrice × quantity
                     - discountAmount
                     + (SST if applicable)

buyer.wallet_balance  -= order.totalAmount   -- atomic SQL UPDATE
seller.wallet_balance += order.totalAmount   -- on order Completed

escrow: funds held in system until Completed
        released to seller only on meetup confirmation
scroll for full derivation

Deduction runs as a single atomic SQL statement — UPDATE users SET wallet_balance = wallet_balance - $amount WHERE id = $id AND wallet_balance >= $amount — so two concurrent checkouts can never both succeed against a balance that only covers one of them. The buyer's funds move into an escrow state immediately, but the seller only sees the credit once both parties confirm the meetup, which is what makes the marketplace safe without a third-party payment processor in the loop.

# Buyer wallet: RM 120.00 — buying item priced RM 45, qty 1
# Voucher applied: RM 5 off, SST not applicable (digital good)

totalAmount = 45 × 1 - 5 + 0 = RM 40.00

UPDATE users SET wallet_balance = wallet_balance - 40.00
WHERE id = 'buyer_id' AND wallet_balance >= 40.00
-- rows affected: 1  → success, new balance RM 80.00

-- funds sit in escrow until meetup confirmed by both sides
-- only then: seller.wallet_balance += 40.00
scroll to see full computation
04 Key Features
Email OTP Verification
Institutional email gating via 6-digit OTP stored in Redis with TTL. Unverified accounts can't transact — eliminates fake accounts.
Real-Time Chat + AI Support
Socket.IO buyer-seller messaging with typing indicators and read receipts. AI-powered support chatbot at /support/ai-chat with 3× daily rate limit.
Stripe + In-App Wallet
Full Stripe Checkout (MYR) with webhook fulfillment. In-app wallet with topup, balance display, and atomic deduction at checkout. SST breakdown in seller earnings.
Flash Sales + Product Boost
Admin-triggered flash sales with realtime stock counter. Sellers can boost listings (1d RM2 / 3d RM5 / 7d RM10) paid from wallet. Featured products on homepage.
Admin Dashboard (7 Tabs)
Platform analytics, user management, maintenance mode (Redis setex + Socket.IO broadcast), announcement banners, support chat acceptance, flash sale control.
Android TWA App
Native Android TWA APK (package: store.campusbay.twa) with Digital Asset Links verified, downloadable from campusbay.store/download.html.
05 The Math Behind Payments & Security
02Voucher & Discount Application
basePrice   = listing.price × quantity
discount    = voucher.discountAmount  (flat or % calculated)
sst         = (basePrice - discount) × 0.08  (if SST enabled)
totalAmount = basePrice - discount + sst

-- Stored on Orders table:
orders.discountAmount = discount
orders.voucherCode    = voucher.code
orders.totalAmount    = totalAmount
scroll for full derivation

Discounts are resolved once, at checkout, and the resulting amount is frozen onto the order row rather than recomputed later. That matters because voucher definitions can change or expire — if the order only stored a reference to the voucher code, a later edit to that voucher would silently rewrite historical order totals. Storing discountAmount directly keeps every past order mathematically honest no matter what happens to the voucher afterward.

# Listing: RM 80, qty 2 → basePrice = 160
# Voucher "WELCOME10": 10% off, SST enabled at 8%

discount = 160 × 0.10        = RM 16.00
sst      = (160 - 16) × 0.08 = RM 11.52
total    = 160 - 16 + 11.52  = RM 155.52

orders.discountAmount = 16.00
orders.voucherCode    = "WELCOME10"
orders.totalAmount    = 155.52
scroll to see full computation
03Boost Visibility Score
boost.expiresAt = NOW() + INTERVAL '${days} days'
boost.active    = (expiresAt > NOW())

-- Featured query: boosted products rank first
SELECT * FROM products
WHERE boost_expires_at > NOW()
ORDER BY boost_expires_at DESC, created_at DESC
scroll for full derivation

Boost is intentionally time-based rather than score-based — there's no decaying relevance algorithm, just a hard expiry timestamp written once at purchase time. This keeps the /products/featured endpoint a single indexed range query instead of a recomputed ranking job, which matters on a single mini-PC host where background cron jobs compete for the same CPU as the live API.

# Seller buys a 3-day boost on 2026-06-19 14:00:00
boost.expiresAt = '2026-06-19 14:00:00' + INTERVAL '3 days'
                = '2026-06-22 14:00:00'

# Featured query at 2026-06-20 09:00:00:
WHERE boost_expires_at > '2026-06-20 09:00:00'
  → '2026-06-22 14:00:00' > '2026-06-20 09:00:00'  → True
  → product appears in /products/featured
scroll to see full computation
04JWT Auth & Rate Limiting
Access Token:  HS256, expires in 15min
Refresh Token: HS256, expires in 7d, stored httpOnly cookie

Rate limits (express-rate-limit):
  /api/auth/*    → 10 req / 15min
  /api/wallet/*  → 20 req / 15min
  /api/support/* → 3 requests / day  (AI chat)
scroll for full derivation

Short-lived access tokens limit the damage window if one ever leaks — 15 minutes is long enough for normal browsing but short enough that a stolen token is nearly worthless by the time it could be misused. The refresh token never touches client-side JavaScript at all, since httpOnly cookies are invisible to XSS payloads; that's the one line of defence that matters most against token theft on a self-hosted stack without a CDN-level WAF in front of it.

# Login flow:
POST /api/auth/login → 200 OK
  Set-Cookie: refreshToken=...; HttpOnly; Secure; SameSite=Strict
  Body: { accessToken: "eyJhbGci...", expiresIn: 900 }

# 16th request to /api/auth/* within 15 minutes:
→ 429 Too Many Requests
  { "error": "Too many auth attempts, try again in 4 minutes" }
scroll to see full computation
06 Use Cases & Impact
Student Textbook Exchange
Buy and sell second-hand textbooks at fraction of bookstore price within campus community.
Hostel Furniture Handoffs
Students graduating sell hostel items to incoming students — meetup scheduling built in.
Electronics & Gadgets
Verified student-to-student device resale with escrow protection and meetup coordination.
Student Entrepreneurship
Students running small businesses (food, crafts, services) can list and reach peers directly.
By the Numbers
15+
API Modules
5
Containers
24/7
Self-Hosted
7
Admin Tabs
Build Progress
Core Auth + OTP100%
Marketplace Core100%
Stripe + Wallet100%
Real-Time Chat100%
Settings Pages40%
Public Launch65%
Tech Stack
React 18 Vite Node.js Express PostgreSQL Redis Sequelize Socket.IO Stripe Docker nginx Cloudflare JWT Nodemailer
Built solo. Shipped to production.
Every line — Docker Compose, Stripe webhooks, Socket.IO, JWT, wallet escrow — written alone. It's messy sometimes, and I ship things that break, but it's the fastest way I've found to actually learn full-stack engineering.
Visit campusbay.store