Multi-Vendor Commerce Platform

ShopWiz.mk

A production multi-vendor ecommerce marketplace for Macedonia: bilingual (EN/MK), cPay card payments, vendor dashboards, advanced product variants, and a full admin console.

ShopWiz.mk: Multi-Vendor Marketplace

Executive summary

Challenge
Independent Macedonian vendors needed a shared marketplace with their own storefronts, accurate variant-level inventory, per-vendor fulfilment and local card payments, in two languages and priced in Macedonian Denar.
Solution
Three React applications (storefront, vendor portal and admin) over one Express/MongoDB API with JWT and Google OAuth, Redis caching, Sharp image processing and cPay card payments with preauthorization, served behind Nginx with PM2 in cluster mode.
Result
Live bilingual marketplace at shopwiz.mk. The non-technical co-founder runs the whole store without a developer, down to scheduling a Black Friday theme from a colour picker.
Our role
Product, architecture, storefront, vendor portal, admin console, API and infrastructure. Designed and built by Risto Radanliev, RiseGravity's founder, who co-owns it.
Scope
Customer storefrontVendor portalAdmin consoleREST APIPayments (cPay)Infrastructure
Key complexity
Per-vendor order lifecycles inside a single shared orderCommission model with per-category overrides cascading to subcategoriesVariant combinations that carry their own SKU, price, stock and weightBilingual EN/MK storefront with bulk Excel import and automatic translation

Technical deep dive

Overview

ShopWiz.mk is a production multi‑vendor ecommerce marketplace built for the Macedonian market. Independent vendors apply, get approved, and run their own storefronts under one roof — managing products, orders, and payouts — while shoppers browse a unified catalog with search, faceted filters, comparison, wishlists, and a streamlined checkout. The entire experience is fully bilingual (English / Macedonian) with prices in Macedonian Denar (MKD).

Homepage

Architecture

  • Storefront (client/): React 18 + Vite + Tailwind; Zustand for state, TanStack Query for data, react‑i18next for EN/MK localization, React Hook Form for forms
  • Admin (admin/): a separate React + Vite SPA served on admin.shopwiz.mk for platform operators
  • API (server/): Node + Express with MongoDB (Mongoose 8); JWT access/refresh auth plus Google OAuth, Sharp image processing, Redis caching, and hardened security middleware
  • Infra: Nginx reverse proxy across shopwiz.mk, admin.shopwiz.mk, and api.shopwiz.mk, with PM2 running the API in cluster mode
[client]  → pages: Home, Products, Product, Categories, Category, Brands, Brand,
                   Vendors, Store, Cart, Compare, Checkout, Account, Wishlist
           vendor portal: Dashboard, Products, Orders, Earnings, Customers, Reviews
[admin]   → Dashboard, Vendors, Products, Orders, Categories, Brands, Coupons,
                   Shipping, Pages (CMS), Users, Settings
[server]  → routes: products, categories, brands, vendors, cart, orders, coupons,
                   reviews, wishlist, payments(cPay), auth(JWT+OAuth), admin/*
           models: User, Vendor, Product, Category, Brand, Order, OrderItem,
                   Coupon, Review, Wishlist, Invoice, Payout, ShippingClass

Storefront Features

  • Catalog & discovery: hierarchical categories, brand pages, MongoDB text search, faceted listing with price / vendor / brand filters
  • Product variants: variant combinations carry their own SKU, price, stock, and weight (not just price deltas) — accurate inventory at the combination level
  • Cart & checkout: persistent cart (localStorage, synced on login), product comparison, guest checkout (email + token) alongside account checkout, separate shipping & billing addresses
  • Engagement: wishlist with price‑drop alerts, reviews with verified‑purchase badges and threaded vendor↔customer conversations, loyalty tiers (Bronze→Platinum)

Products — listing & filters

Product detail — variants, pricing & reviews

Brands & Categories

  • Hierarchical categories with ancestor breadcrumbs, denormalized product counts, per‑category default sort, and dedicated icon / card / banner imagery
  • Brand catalog with bilingual names and logos, browsable brand landing pages
  • Both surfaces are bilingual (EN/MK) and feed the homepage merchandising

Categories

Brands

Vendor Portal

  • Onboarding: vendor application → admin approval / rejection / blocking, store profile (logo, banner, hours, vacation mode, business & bank info)
  • Per‑vendor order statuses: each vendor confirms, processes, and ships its portion of a shared order independently, with tracking numbers
  • Flexible commission model: a vendor‑level default rate with per‑category overrides that cascade to subcategories
  • Operations: earnings & payout history, customer lists, low‑stock alerts, review replies, and bulk Excel import with automatic EN→MK translation

Admin Console

  • Dashboard: revenue, orders, AOV, new customers/vendors, refunds, completion rate, revenue‑over‑time, order/payment status, top products & vendors, catalog health, and recent orders
  • Management: vendors & approvals, products & inventory, orders & refunds, hierarchical categories, brands
  • Marketing & ops: a coupon engine with granular scoping (vendor / product / category / brand, usage limits), weight‑based shipping classes, CMS static pages, users & roles, and platform settings (cPay credentials, email, theme)

Admin — analytics dashboard

Advanced Implementation

Bilingual storefront & data layer

// client — react-i18next (EN/MK) + TanStack Query + Zustand
i18n.use(initReactI18next).init({ resources: { en, mk }, fallbackLng: 'mk' })
const { data } = useQuery(['products', filters], () => api.products.list(filters))
// product variant combinations: per-SKU price, stock, weight
{ attributes: { Color: 'Red', Size: 'M' }, sku: 'XXX-RED-M', price: 1490, stock: 12, weight: 0.4 }

Payments, security & jobs

// server — cPay card gateway with preauthorization (reserve, capture on ship)
app.use(helmet()); app.use(rateLimit({ windowMs: 60_000, max: 300 })); app.use(compression())
router.post('/payments/cpay/init', initPreauth)   // → capture on fulfilment
// images: Sharp → WebP + thumbnails; emails: Resend + React Email; invoices: PDFKit
await sharp(buf).resize(1600).webp({ quality: 80 }).toFile(out)

Localization & SEO

  • Full EN/MK localization via client/src/locales, MKD currency formatting throughout
  • Per‑product SEO fields (title, description, slug) and bilingual category/brand metadata
  • Transactional email automation (order, shipping, review, price‑drop) through Resend with React Email templates

What made it work

  • A clean three‑app split — storefront, vendor portal, admin — over one well‑modeled API
  • Marketplace primitives done properly: per‑vendor order lifecycles, cascading commissions, and combination‑level inventory
  • Bilingual and payment‑local from day one (EN/MK + cPay + MKD), purpose‑built for the Macedonian market

Deliverables

  • Customer storefront (browse, search, cart, guest & account checkout)
  • Vendor portal (store profile, products, orders, payouts, reviews)
  • Admin console (vendors, products, orders, coupons, categories, CMS, settings)
  • cPay card payment integration with preauthorization
  • Bilingual EN/MK experience (react-i18next, MKD pricing)
  • Bulk Excel product import with automatic translation