D2C eCommerce Platform
LiveVedaGlow
Overview
Project Overview
VedaGlows is a live D2C Ayurvedic skincare brand and eCommerce platform I built end to end — frontend, server functions, database schema, payment integration, admin tooling and production deployment. It sells the 28-Day Skin Reset Kit directly to consumers in India, and handles the full commercial path from landing page to delivered order.
Screenshots
What it looks like
Captured from the running product — not mockups. You can check any of it against the live site.



On a phone


Not shown here (7)
- Razorpay payment sheet mid-transactionWould require initiating a real payment on the live store
- Order confirmation with order numberWould require placing a real order
- Admin order list with status transitionsRequires admin credentials
- Admin product and stock editingRequires admin credentials
- Coupon management screenRequires admin credentials
- WhatsApp order-status messageRequires a real order to trigger a notification
- Customer account with order historyRequires a signed-in customer account
Problem
The Problem
VedaGlows needed a complete commercial storefront without the constraints of a hosted SaaS platform, where customisation is capped by the platform's rules and fees start on day one. The store also had to fit how Indian D2C customers actually buy: UPI and cards through a familiar gateway, cash on delivery as a genuine option rather than an afterthought, and order updates arriving on WhatsApp rather than by email.
Role
What I Built
- ↗Designed and built the storefront in React 19 with Vite and TanStack Start
- ↗Designed the PostgreSQL schema — 10 tables across 11 migrations on Supabase
- ↗Wrote every Row-Level Security policy protecting order, coupon and profile data
- ↗Built the cart, multi-step checkout, and coupon application flow
- ↗Integrated Razorpay end to end, including server-side payment signature verification
- ↗Implemented the split advance-payment and cash-on-delivery option
- ↗Built the order lifecycle and the role-guarded admin panel behind it
- ↗Built the WhatsApp order-status notification flow with Indian phone-number normalisation
- ↗Built the storefront settings CMS so pricing, copy and popups change without a deploy
- ↗Deployed and maintain the platform on Vercel
Architecture
System Architecture
VedaGlows is a server-rendered React application built on TanStack Start. Data access runs through typed server functions rather than a separate API service — each one carries its own auth middleware, so authorisation is enforced on the server and again at the database through Row-Level Security. Supabase provides PostgreSQL, authentication and storage. Razorpay handles payment capture, with every payment verified server-side before an order is marked paid.
React 19 · Tailwind v4 · Radix UI · Zustand cart persisted to localStorage
TanStack Start — server-rendered routes with typed server functions
orders · coupons · products · settings · admin, each behind auth middleware
Supabase PostgreSQL — 10 tables, 27 RLS policies, RLS enabled on every table
Supabase Auth — customer accounts, with guest checkout supported alongside
Razorpay Checkout → server-side signature verification → order marked paid
WhatsApp order-status deep links across six lifecycle states
Vercel — production deployment at vedaglows.com
Key Architectural Decisions
- ·Server functions over a separate API service — one deployable, one type boundary, no CORS layer
- ·Row-Level Security as the authorisation backstop — a bug in application code cannot leak another customer's orders
- ·Razorpay over a bespoke payment flow — cards, UPI and netbanking in one integration, with verifiable signatures
- ·Cash on delivery as a first-class payment method, not a fallback — it is how a large share of Indian D2C actually converts
- ·WhatsApp over email for order updates — far higher open rates with Indian consumers
- ·Guest checkout retained after accounts were added — an account is an option, never a checkout blocker
Technical Breakdown
System-by-System Engineering
A detailed look at each layer and subsystem — decisions made, how it was built, and why it was built that way.
Frontend
CompletedServer-rendered React 19 application built with Vite and TanStack Start. Routes render on the server for first paint, then hydrate for interaction.
- ·React 19 with TanStack Start — server rendering plus typed client routing
- ·Tailwind CSS v4 with a Radix UI component layer
- ·Zustand for cart state, TanStack Query for server-state caching
- ·Zod schemas shared between client forms and server functions
- ·WebP imagery with route-level code splitting via Vite
- ·Schema.org product structured data for search result eligibility
Data layer
CompletedSupabase PostgreSQL, evolved across 11 migrations. The schema covers orders, line items, coupons, redemptions, profiles, addresses, product overrides, storefront settings and roles.
- ·10 tables: orders, order_items, coupons, coupon_usage, profiles, shipping_addresses, product_overrides, store_settings, popup_settings, user_roles
- ·Five domain enums: order_status, payment_status, payment_method, discount_type, app_role
- ·Human-readable order numbers generated in the database (VG-YYMMDD-XXXXXX)
- ·Monetary columns typed NUMERIC(10,2) — never floating point
- ·Migration history kept in the repository, so schema changes are reviewable
Authorization & security
CompletedAuthorization is enforced twice: in server-function middleware, and again at the database through Row-Level Security. The database is the final authority on who can read a row.
- ·27 Row-Level Security policies, with RLS enabled on all 10 tables
- ·Role model via a user_roles table and an app_role enum (admin, user)
- ·A shared admin assertion guards every privileged server function
- ·Customers can only ever read their own orders, addresses and redemptions
- ·Supabase Auth owns credentials — no password material is handled by application code
Product catalog
CompletedA focused single-kit catalog. Product records are keyed by SKU and carry admin-editable overrides, so pricing and copy change from the admin panel rather than from a deploy.
- ·SKU-keyed product records with name, description, price, MRP and image
- ·Stock level stored per SKU and editable from the admin panel
- ·Visibility and enabled flags — a product can be hidden without deletion
- ·Storefront copy, pricing and promotional popups managed through settings tables
- ·Public read access granted explicitly; writes restricted to the service role
Cart engine
CompletedCart state lives in a Zustand store with the persist middleware, so it survives refreshes and tab closures. Totals and coupon eligibility are recomputed server-side before an order is created.
- ·Zustand store with persist middleware writing to localStorage
- ·Add, update quantity and remove, with totals recalculated on every mutation
- ·Coupon code applied against the cart and validated on the server
- ·Line items and pricing re-validated server-side at order creation
- ·Separate drawer store so cart UI state never pollutes cart data
Checkout
CompletedA multi-step checkout collecting customer details and a shipping address, then presenting payment options. Address entry is accelerated by PIN-code lookup.
- ·Multi-step flow: cart review → customer and address → payment → confirmation
- ·PIN-code lookup auto-fills city and state from an Indian postal API
- ·Zod validation at each step before the customer can advance
- ·Two payment paths offered: pay in full online, or pay a partial advance with the balance on delivery
- ·Order created server-side with the authoritative total — the client never sets the amount
Payments
CompletedRazorpay integration covering order creation, checkout, verification and failure handling. A payment is only trusted once its signature has been verified on the server.
- ·Razorpay order created server-side; the client receives only an order id, key and amount
- ·Razorpay Checkout opened in-page for cards, UPI and netbanking
- ·Payment id, order id and signature posted back and verified server-side before the order is marked paid
- ·razorpay_order_id, razorpay_payment_id, razorpay_signature and payment_timestamp persisted on the order
- ·Payment failures surfaced to the customer without losing their cart or address
- ·Cash on delivery and split advance payment handled as first-class methods alongside online payment
Order management
CompletedOrders move through a six-state lifecycle, with payment status tracked separately so a delivered order and a refunded payment can coexist.
- ·Order states: pending → confirmed → processing → shipped → delivered, plus cancelled
- ·Payment status tracked independently: pending, paid, failed, refunded
- ·Guest and account orders share one model — user_id is nullable by design
- ·Full shipping address snapshotted onto the order as JSON at purchase time
- ·Admin order views and status transitions behind authenticated server functions
- ·Internal notes field for operational context on an order
Coupons & pricing
CompletedA discount engine supporting percentage and flat-amount coupons, with expiry, activation and a global usage cap. Every redemption is recorded.
- ·Two discount types via a discount_type enum: percentage and flat
- ·Global usage cap enforced by comparing used_count against max_uses
- ·Expiry timestamp checked at validation time
- ·Active flag lets a coupon be switched off without deletion
- ·coupon_usage ledger records coupon, order and discount amount per redemption
- ·Discount value constrained positive at the database level
Notifications
CompletedCustomers receive order updates over WhatsApp rather than email — the channel Indian D2C customers actually read.
- ·Six message templates: confirmed, packed, shipped, out for delivery, delivered, cancelled
- ·Indian phone numbers normalised to wa.me format, accepting +91, 91, 0 and bare 10-digit input
- ·Messages composed from live order data so the customer sees their own order number
Admin & operations
CompletedA role-guarded admin surface for running the store day to day — orders, products, coupons and storefront content, all without a developer.
- ·Admin-only server functions for orders, products, coupons and settings
- ·Product overrides: price, MRP, stock, visibility and imagery
- ·Coupon creation, deactivation and usage visibility
- ·Storefront settings and promotional popup configuration
- ·Every admin mutation passes a shared admin assertion before touching data
Challenges
Engineering Challenges & Solutions
Challenge
Trusting a payment result that arrives from the browser
A payment confirmation reaching the server from client-side code cannot be believed on its own — anything the browser sends can be forged, and marking an unpaid order as paid means shipping goods for free.
Solution
The server creates the Razorpay order and owns the amount. After checkout, the payment id, order id and signature are posted back and verified server-side against Razorpay before the order is marked paid. All three values plus a payment timestamp are persisted on the order, so any dispute can be reconciled later.
Challenge
Making cash on delivery work without carrying the full risk
Cash on delivery converts well in Indian D2C but exposes the seller to refused deliveries and the full cost of return shipping on every abandoned order.
Solution
Checkout offers a split option: pay a smaller advance online now, settle the balance on delivery. The advance is captured and verified through the same Razorpay path as a full payment, which filters out non-serious orders while keeping the low-friction COD option customers expect.
Challenge
Keeping customer data safe when application code has bugs
With authorization enforced only in application code, one missed check in one query is enough to expose another customer's orders and addresses.
Solution
Authorization is enforced twice. Server functions carry auth middleware, and the database independently enforces 27 Row-Level Security policies with RLS enabled on all 10 tables. A customer's session is structurally unable to read another customer's rows even if a server function forgets to filter.
Challenge
Adding accounts without adding checkout friction
Accounts unlock order history and repeat purchases, but forcing registration at checkout reliably costs conversions on a first purchase.
Solution
Accounts were introduced through Supabase Auth while guest checkout was explicitly preserved — a migration made user_id nullable on both orders and coupon redemptions. Signing in is an option that adds value, never a gate in front of a purchase.
Decisions
Technical Decisions
Key technology and architecture choices — what was chosen, what was considered, and why.
Decision — Server functions vs a separate API service
Chosen
TanStack Start server functions
Considered
A standalone Node API deployed separately
Reason
One deployable instead of two, one shared type boundary between client and server, and no CORS or duplicated validation layer. For a single-store D2C product the operational cost of a second service buys nothing.
Decision — Where authorization is enforced
Chosen
Server middleware plus Row-Level Security
Considered
Application-layer checks only
Reason
Application checks are one bug away from a data leak. RLS makes the database the final authority, so a missing filter in a query fails closed rather than exposing another customer's orders.
Decision — Razorpay vs a bespoke UPI flow
Chosen
Razorpay Checkout with server-side verification
Considered
Hand-rolled UPI QR plus manual confirmation
Reason
A manual flow needs a human to verify every order and gives the customer no instant confirmation. Razorpay covers cards, UPI and netbanking in one integration and returns a signature that can be verified server-side, which makes payment state trustworthy rather than asserted.
Decision — WhatsApp vs email for order updates
Chosen
WhatsApp deep links
Considered
Transactional email
Reason
Indian D2C customers read WhatsApp and largely ignore promotional email. Sending status updates through the channel customers already use cut support questions without operating email infrastructure or fighting deliverability.
Decision — Cart persistence strategy
Chosen
Zustand with persist middleware to localStorage
Considered
Server-side cart sessions
Reason
A server cart needs an identity, which means either forcing sign-in or issuing anonymous session rows. Persisting locally keeps guest checkout frictionless; correctness is protected by re-validating line items and totals server-side at order creation.
Deployment
Deployment & Infrastructure
Deployed on Vercel at vedaglows.com, with Supabase providing the managed PostgreSQL instance, authentication and storage.
- ·Vercel hosting with automatic HTTPS on a custom domain
- ·Supabase managed PostgreSQL with migrations tracked in the repository
- ·Supabase Auth for customer sessions; Supabase Storage for assets
- ·Razorpay keys and Supabase credentials supplied as environment variables — no secrets in source
- ·Deploy configuration committed alongside the application
Learnings
What I Learned
Next Steps
What I Would Build Next
- →Stock reservation at checkout with atomic decrement on payment, to close the overselling window
- →Automated refund handling through the Razorpay refund API, wired to the existing refunded payment status
- →Transactional email alongside WhatsApp for customers who prefer it
- →Product reviews and ratings
- →A conversion funnel view in the admin panel — add to cart, checkout started, paid
- →Multi-product catalog with variants, once the range grows beyond the current kit