Back

2026

MiidayShop

Custom ecommerce store with Razorpay · built by Vikrant Kumar

A custom ecommerce platform for one brand, with its own storefront, admin panel and payments, so it doesn't have to rent a store from Shopify.

Next.jsReactTypeScriptNestJSPostgreSQLPrismaRazorpayTailwind CSS

The problem

Small D2C brands in India usually sell on hosted platforms that take a monthly fee and a cut of every sale, and they don't fully own their store's data or checkout. I wanted miiday to have its own store end to end: a storefront where customers browse, add to cart, apply coupons and pay with Razorpay or COD, and an admin panel where the owner manages products, variants, stock, orders, coupons, banners, reviews, staff and sales reports. Both apps run on one backend.

Decisions and why

One API behind two separate frontends

The storefront and admin panel are independent Next.js apps that use the same NestJS API, with shared TypeScript types in a monorepo package. Customers and staff get very different UIs, but there is only one place where business rules live, so a price or stock rule can't drift between the two apps.

The server decides every price

The client never sends a price or a total. At checkout the API reloads the cart, checks stock and product status, applies the coupon, shipping and GST itself, and freezes those numbers on the order. If an order is fully discounted to ₹0, the API settles it directly instead of sending a zero-amount payment to Razorpay, which would reject it and leave an order that could never be paid.

No stock deducted before the money arrives

A Razorpay order is created as PENDING. Stock is not touched and the coupon is not used up until payment is confirmed. An abandoned checkout never locks inventory or burns a limited-use coupon. COD orders are confirmed right away in a single database transaction.

Short-lived access tokens with rotating refresh tokens

The access token lives only in memory (never localStorage). The refresh token sits in an httpOnly cookie, is stored hashed in the database, and is replaced on every use. A stolen token stops working quickly, and logout actually revokes the session on the server.

Every stock change is logged

Stock changes from orders, cancellations, restocks or manual fixes all write an inventory adjustment row with a reason. The owner can see exactly why a count changed, and low-stock items show up on the admin dashboard.

The hardest part

Confirming payments exactly once. A Razorpay payment can be reported twice: by the browser right after checkout and by Razorpay's webhook, which retries until it gets a 200. Both paths check the HMAC signature (on the raw request body for webhooks) and then call a single confirmPayment function. That function marks the payment paid, deducts stock, redeems the coupon, confirms the order and clears the cart in one transaction. If the payment is already PAID it does nothing, so retries and races can't deduct stock twice or send two confirmation emails. One trade-off I accepted on purpose: because stock is only taken after payment, two buyers can both pay for the last unit. When that happens the API logs it as an oversell instead of letting stock go negative, so the owner can handle it.