R
0

D2C eCommerce Platform

Live

VedaGlow

RoleFounder & Full Stack Commerce EngineerPeriodMarch 2026 — PresentLive Site Source
React 19ViteTanStack StartTypeScriptTailwind CSS v4Radix UIZustandTanStack QueryZodSupabasePostgreSQLRazorpayVercel
01

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.


02

Screenshots

What it looks like

Captured from the running product — not mockups. You can check any of it against the live site.

VedaGlow storefront showing the 28-Day Skin Reset kit with pricing and an add-to-cart action
The live storefront. Single-kit catalog with pricing and stock served from admin-editable product records.
Cart drawer with the starter kit added, showing quantity controls, subtotal, shipping and order total
Cart state held in a Zustand store persisted to localStorage. Totals are recalculated on every mutation and re-validated server-side before an order exists.
Checkout page showing shipping form with pincode field, coupon entry, and two payment options: cash on delivery with a partial advance, or full online payment via Razorpay
Checkout, end to end. The pincode field auto-fills city and state; the coupon box validates server-side; and the two payment paths are the split advance-plus-COD option and full online payment through Razorpay.

On a phone

VedaGlow storefront rendered on a phone viewport
Mobile storefront — the primary surface for Indian D2C traffic.
VedaGlow checkout rendered on a phone viewport showing the shipping form
Mobile checkout. Every step of the purchase path is usable at 390px.
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

03

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.


04

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

05

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.

Browser

React 19 · Tailwind v4 · Radix UI · Zustand cart persisted to localStorage

Rendering

TanStack Start — server-rendered routes with typed server functions

Server functions

orders · coupons · products · settings · admin, each behind auth middleware

Database

Supabase PostgreSQL — 10 tables, 27 RLS policies, RLS enabled on every table

Auth

Supabase Auth — customer accounts, with guest checkout supported alongside

Payments

Razorpay Checkout → server-side signature verification → order marked paid

Notifications

WhatsApp order-status deep links across six lifecycle states

Hosting

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

06

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

Completed

Server-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

Completed

Supabase 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

Completed

Authorization 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

Completed

A 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

Completed

Cart 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

Completed

A 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

Completed

Razorpay 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

Completed

Orders 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

Completed

A 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

Completed

Customers 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

Completed

A 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

07

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.


08

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.


09

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

10

Learnings

What I Learned

Payment state is the one thing in a store you cannot take the client's word for. Verifying the Razorpay signature server-side before marking an order paid is the difference between a payment system and a hopeful one.
Row-Level Security changed how I think about authorization. Enforcing it in the database means an application bug degrades into a failed query instead of a data leak.
Payment methods are a market decision as much as a technical one. Split advance plus cash on delivery converts in India in a way that card-first checkout does not, and it required designing for it rather than bolting it on.
Choosing WhatsApp over email for order updates was worth more to customers than any feature I could have built in the same time — the channel matters as much as the message.
Adding accounts without touching guest checkout took one migration and a deliberate decision. Retrofitting it after making accounts mandatory would have been far more expensive.

11

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