Cross-Reference
◆
Seller Bank Verification — Current Auth Flow Reference
The current seller onboarding and bank verification pipeline is fully defined and documented in the
Auth Seller Visual Description Flowchart
(
docs/auth-docs/system-auth-flowchart.html).
Please refer to that file for the complete pipeline specification.
Seller Bank Verification — Quick Reference Summary
Stage 1 · Authenticated
🪪 KYC — PAN Verification
Runs after phone OTP issues onboarding cookies.
→ KYC_VERIFIED
POST /api/v1/auth/seller/signup/verify-pan
stores kycRegisterName.
Stage 2 · Cashfree
Bank / UPI Verification
POST /api/v1/auth/seller/signup/bank-details
starts Cashfree BAV for bank details or Cashfree reverse penny drop for UPI.
Status refresh endpoints update the same saved request.
Stage 3 · Admin
Bank + Trademark Review
Admin can list, refresh, approve or reject bank verification.
Trademark approval activates the seller only when bank verification is ready.
→ ACTIVE when bankReady
📄
Full Specification Location
The complete current onboarding pipeline — including the full Mermaid flowchart, API request/response pairs,
database fields, Cashfree status branches, and admin bank/trademark gates —
is defined in:
Section: Current Seller Bank Verification Pipeline · Triggered at: Seller Signup — Stage 4 (Bank Details)
docs/auth-docs/system-auth-flowchart.html
Section: Current Seller Bank Verification Pipeline · Triggered at: Seller Signup — Stage 4 (Bank Details)
Key DB Fields Set by This Pipeline
kycRegisterName — legal PAN name
bankVerificationStatus — PENDING / VALIDATION_INITIATED / REVERSE_PENNY_DROP_PENDING / VERIFIED / VALIDATION_FAILED
bankRegisterName — Cashfree bank-account name
upiRegisterName — Cashfree UPI name
cashfreeBeneficiaryId — payout beneficiary created later
accountSetupPhase — KYC_VERIFIED → BANK_VERIFIED → TRADEMARK_SUBMITTED → COMPLETED
Seller Features
Feature 05 — Seller Dashboard: Amazon Seller Central Clone + macOS Dock Animations
Dashboard loads summary cards from the current
GET /api/v1/seller/dashboard endpoint,
where the backend performs parallel Prisma reads. No Redis dashboard cache is implemented in the current code.
Includes a sortable product performance table (sales, units, views, stock, price columns).
Real-time updates via Socket.IO for new orders, messages, and ratings. Sidebar and bottom-nav icons
implement macOS Dock-style magnification on hover using Framer Motion (spring physics, translateY + scale).
Design is functionally superior to Amazon Seller Central.
Next.js + Framer Motion
NestJS
PostgreSQL
Prisma parallel reads
Socket.IO
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller navigates to /seller/dashboard]):::fe
A --> GUARD[JwtAuthGuard + RolesGuard SELLER\nSellerVerifiedGuard + AccountActiveGuard]:::be
GUARD -->|Fail| AUTH_ERR[Redirect to /seller/login\nor 403 Forbidden]:::err
GUARD -->|Pass| DASH_REQ[GET /api/v1/seller/dashboard]:::be
DASH_REQ --> PARALLEL[DashboardService runs Promise.all\nfor summary, wallet, counts and alerts]:::be
subgraph CARDS [DashboardService Parallel Reads]
PARALLEL --> C1[SellerOrder aggregate todaySales]:::db
PARALLEL --> C2[SellerOrder count todayOrders]:::db
PARALLEL --> C3[ConversationThread unread count]:::db
PARALLEL --> C4[SellerRating average and count]:::db
PARALLEL --> C5[WalletTransaction monthly revenue]:::db
PARALLEL --> C6[Product viewCount aggregate]:::db
PARALLEL --> C7[SellerDigitalWallet and grouped product/order counts]:::db
C1 & C2 & C3 & C4 & C5 & C6 & C7 --> RENDER_CARDS[Render dashboard summary, wallet and counts]:::fe
end
subgraph TABLE [Product Performance Table]
RENDER_CARDS --> PT[GET /api/v1/seller/products/performance\nDefault: newest first]:::be
PT --> PT_DB[(Query: all products for sellerId\nJoin: orderItems for sales\nJoin: productViews for views\nFilter: isDeleted = false)]:::db
PT_DB --> RENDER_TABLE[Render sortable table:\nProduct thumbnail + name + SKU\nListing Status + created date\nHighest Sales INR\nUnits Sold\nPage Views all-time\nCurrent Stock\nYour Price]:::fe
RENDER_TABLE --> SORT_HDR{Click column header}
SORT_HDR -->|Sales| S1[sort=sales_desc or asc]:::be
SORT_HDR -->|Units| S2[sort=units_desc or asc]:::be
SORT_HDR -->|Views| S3[sort=views_desc or asc]:::be
SORT_HDR -->|Stock| S4[sort=stock_desc or asc]:::be
SORT_HDR -->|Price| S5[sort=price_desc or asc]:::be
RENDER_TABLE --> ROW_CLICK[Click product row:\nSplit view left = buyer PDP\nRight = seller inventory metrics]:::fe
end
subgraph RT [Real-Time Socket.IO Events]
SOCKET[Socket.IO room: user:userId]:::ext
SOCKET --> EV1[seller:data-changed -> Dashboard refetches affected areas]:::fe
SOCKET --> EV2[threads:changed -> Unread message state updates]:::fe
SOCKET --> EV3[notifications:changed -> Notification popover/toasts refresh]:::fe
SOCKET --> EV4[message:created -> Active chat receives message]:::fe
end
📋 Full Flow Description▼
PAGE LOAD
→ GET /api/v1/seller/dashboard (requires JWT + SELLER role + ACTIVE status)
→ DashboardService resolves the seller profile id and runs parallel DB queries with Promise.all:
SUMMARY CARDS (6 cards):
Card 1 — Today's Sales: SUM(sellerProceeds) WHERE sellerId = me AND createdAt ≥ today_start AND status IN active order statuses
Card 2 — Today's Orders: COUNT(*) WHERE sellerId = me AND createdAt ≥ today_start
Card 3 — Unread Messages: COUNT(*) WHERE sellerId = me AND hasUnreadBySeller = true
Card 4 — Seller Rating: AVG(stars), COUNT(*) FROM SellerRating WHERE status = PUBLISHED
Card 5 — Monthly Revenue: SUM(amount) FROM WalletTransaction WHERE type = ORDER_CREDIT AND createdAt ≥ this_month_start
Card 6 — Product Impressions: SUM(viewCount) FROM Products WHERE sellerId = me AND status = ACTIVE
Additional counts — grouped product/order statuses, pending questions, total reviews, wallet balances
PRODUCT PERFORMANCE TABLE:
Columns: ① Product (thumbnail + title clickable + SKU) ② Listing Status + created date ③ Highest Sales (₹) ④ Units Sold ⑤ Page Views ⑥ Stock ⑦ Your Price (₹)
Sorting: click column header → toggle ASC/DESC → GET /api/v1/seller/products/performance?sort=sales&dir=desc
Product row click → /seller/products/{id} — split view: left = buyer product detail page, right = seller inventory metrics
SIDEBAR NAVIGATION (macOS Dock Animation — verified against Amazon Seller Central structure):
Main sections (mirrors Amazon's top-nav categories as sidebar items):
→ Catalog: Product Listing | Save as Draft
→ Inventory: Manage Inventory | Low-Stock Alerts
→ Orders: Manage Orders (Pending / Unshipped / Shipped / Delivered tabs)
→ Advertising: A+ Content Manager | Deals | Coupons | Promotions
→ Reports: Selling Reports | Business Analytics
→ Performance: Ratings & Feedback | Account Health
→ Brands: Brand/Trademark Management
→ Communications: Messages | Notifications | Q&A
→ Payments: Payouts & Wallet
→ Settings: Profile | Bank Accounts
Dock animation: icons translateY(-8px) + scale(1.25) via Framer Motion spring physics (stiffness=300, damping=20)
Adjacent icons also magnify with decreasing intensity (exact macOS Dock behaviour)
SOCKET.IO REAL-TIME:
→ seller:data-changed → refetch seller dashboard/order/wallet areas as needed
→ threads:changed and message:created → update message state
→ notifications:changed → refresh notification popover/toast state
Seller Features
Updated
Feature 06 — Seller Product Listing: 6-Step Multi-Step Form with Full Field Specification
Comprehensive multi-step product creation form. Bullet Points accept rich text HTML per field
(DOMPurify server-side XSS prevention). Item Dimensions are REQUIRED.
SKU uniqueness validated in real-time on blur. HSN Code auto-fills GST rate.
Minimum 2 images required with one designated as main. Published products are indexed through
SearchIndexService with only product name, category, and SKU. SEO data is auto-generated
if not provided by seller.
Next.js
NestJS
PostgreSQL
Cloudinary
Algolia
SearchIndexService
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller opens Product Listing Form]):::fe
subgraph S1 [Step 1 — Core Product Details]
A --> FORM1[/Item Name max 200 chars\nBrand Name dropdown\nProduct Description rich text HTML\nBullet Points rich text fields min 3\nTarget Gender dropdown\nMaterial + Color + Manufacturer\nCountry of Origin + Dangerous Goods\nItem Weight kg\nItem Dimensions LxWxH cm REQUIRED\nSKU unique platform-wide\nHSN Code 4-8 digits auto-fills GST\nItem Condition dropdown\nTotal Stock required/]:::fe
FORM1 --> SKU_BLUR[On blur: GET /api/v1/seller/products/sku-check\nReal-time uniqueness validation]:::be
SKU_BLUR -->|Taken| SKU_ERR[Inline error: SKU already in use]:::err
SKU_BLUR -->|Free| SKU_OK[Green checkmark shown]:::ok
FORM1 --> HSN_BLUR[On blur: GET /api/v1/seller/products/hsn-lookup\nAuto-fill GST rate field]:::be
end
subgraph S2 [Step 2 — Images]
S1 --> IMG_UP[Upload 2 to 10 images to Cloudinary\nDirect upload via Cloudinary widget\npublic_id returned to backend\nExactly one image = main image\nDrag-and-drop reorder supported]:::fe
IMG_UP -->|Less than 2 images| IMG_ERR[Minimum 2 images required]:::err
IMG_UP -->|No main selected| MAIN_ERR[Mark one image as main]:::err
end
subgraph S3 [Step 3 — Video Optional]
S2 --> VID_UP[Upload one product demo video to Cloudinary\npublic_id stored on product record\nDisplayed on product detail page]:::fe
end
subgraph S4 [Step 4 — Pricing and Offers]
S3 --> PRICE_FORM[/Stock total available units\nYour Price must be less than or equal to MRP\nMRP Maximum Retail Price/]:::fe
PRICE_FORM --> PRICE_VALIDATE[Frontend Zod:\nPrice less than or equal to MRP\nStock must be a positive integer]:::fe
PRICE_VALIDATE -->|Invalid| PRICE_ERR[Inline validation errors]:::err
end
subgraph S5 [Step 5 — Variants Optional]
S4 --> VAR_TOGGLE{Add variants?}
VAR_TOGGLE -->|Yes| VAR_FORM[/Per variant:\nSize + Color + Material override\nNumber of items override\nStock per variant\nPrice per variant lte MRP\nMRP per variant/]:::fe
VAR_TOGGLE -->|No| SUBMIT_STEP
end
subgraph S6 [Step 6 — SEO]
VAR_FORM --> SEO_FORM[/SEO Title meta title\nMeta Description\nKeywords tags\nIf left blank: auto-generated\nfrom title + description + brand + category/]:::fe
SEO_FORM --> SUBMIT_STEP
end
SUBMIT_STEP --> FORM_ACTIONS{Form Action}
FORM_ACTIONS -->|Cancel| DISCARD[Discard all unsaved changes]:::warn
FORM_ACTIONS -->|Save as Draft| DRAFT[(POST /api/v1/seller/products/draft\nstatus = DRAFT\nNo Algolia indexing yet)]:::db
FORM_ACTIONS -->|Submit| FINAL_SUBMIT[POST /api/v1/seller/products\nMultipart body with all form data]:::be
FINAL_SUBMIT --> SANITIZE[Server-side DOMPurify\nAll rich-text HTML fields sanitized]:::be
SANITIZE --> SAVE[(Save Product record:\nLinked to sellerId\nImage public_ids array stored\nVideo public_id stored\nstatus = ACTIVE)]:::db
SAVE --> ALGOLIA[SearchIndexService upserts Algolia record\nIndex name: products\nFields: productName, category, sku]:::be
ALGOLIA --> DONE([Product live on marketplace]):::ok
📋 Full Flow Description▼
STEP 1 — CORE PRODUCT DETAILS
Full field specification:
• Item Name — max 200 characters
• Brand Name — dropdown from seller's approved brand list
• Product Description — rich text HTML editor (DOMPurify sanitized server-side)
• Bullet Points — 2–3 input fields by default; each field = one bullet point; accepts rich text HTML and plain text; XSS prevented with DOMPurify; seller can click "Add Bullet Point" (each new field is another bullet); first 3 are REQUIRED, additional are optional
• Target Gender — Male / Female / Unisex
• Material — text input
• Number of Items per Order — numeric
• Color — predefined dropdown + custom color option
• Manufacturer — text input
• Manufacturer Contact Info — text input
• Country of Origin — dropdown (country list)
• Dangerous Goods — "Not Applicable" default (Battery, Chemical, etc.)
• Item Weight — numeric (kg)
• Item Dimensions — L × W × H (cm) — REQUIRED — not optional
• SKU — unique platform-wide; real-time uniqueness check on blur
• HSN Code — 4–8 digits; auto-fills GST rate on valid entry
• Item Condition — New / Replace / Reuse
• Stock — total available units (required)
STEP 2 — IMAGES
→ Minimum 2, maximum 10 images
→ All uploaded to Cloudinary — public_id stored on product record
→ Exactly one image must be designated as main image
→ Drag-and-drop reorder supported (order stored as array index)
→ Images editable in edit mode; displayed on product detail page in correct order
STEP 3 — VIDEO (OPTIONAL)
→ One product demo video uploaded to Cloudinary
→ Cloudinary public_id stored in product record
→ Displayed on product detail page
STEP 4 — OFFERS / PRICING
→ Stock: total available units (must be kept updated)
→ Price: must be ≤ MRP (validated by Zod)
→ MRP: Maximum Retail Price
STEP 5 — VARIANTS (OPTIONAL — but if used, at least one required)
→ Size / Color (per variant) / Material override / Number of items override
→ Stock per variant / Price per variant (≤ MRP per variant) / MRP per variant
STEP 6 — SEO
→ If seller leaves SEO fields blank → backend auto-generates from: product title + description + bullet points + brand + category
→ Uses NestJS SeoService to generate optimized meta title and description
FORM ACTIONS
Cancel → discard all unsaved changes
Save as Draft → POST /api/v1/seller/products/draft → status = DRAFT (not indexed in Algolia)
Submit → POST /api/v1/seller/products → DOMPurify sanitize → save to DB → SearchIndexService upserts Algolia record (index: "products", fields: productName, category, sku)
🔌 API Request / Response Reference▼
GET /api/v1/seller/products/sku-check?sku=SKU123
{
"method": "GET",
"endpoint": "/api/v1/seller/products/sku-check",
"query": { "sku": "RAHULCRAFT-TEE-XL-BLU" }
}
{
"status": 200,
"body": {
"available": true,
"sku": "RAHULCRAFT-TEE-XL-BLU"
}
}
// If taken:
{
"status": 200,
"body": { "available": false }
}
POST /api/v1/seller/products
{
"method": "POST",
"endpoint": "/api/v1/seller/products",
"headers": { "Cookie": "access_token=httpOnly" },
"body": {
"itemName": "Premium Cotton T-Shirt",
"brandId": "brand_rahulcraft_uuid",
"description": "<p>Premium cotton...</p>",
"bulletPoints": [
"<p>100% organic cotton</p>",
"<p>Pre-shrunk fabric</p>",
"<p>Available in 10 colors</p>"
],
"sku": "RAHULCRAFT-TEE-XL-BLU",
"price": 799,
"mrp": 999,
"stock": 50,
"weight": 0.25,
"dimensions": { "l": 30, "w": 20, "h": 5 },
"imagePublicIds": [
"products/img_main_abc",
"products/img_side_xyz"
],
"mainImageIndex": 0,
"hsnCode": "61091000",
"itemCondition": "New"
}
}
{
"status": 201,
"body": {
"productId": "prod_uuid_xyz",
"status": "ACTIVE",
"slug": "premium-cotton-t-shirt-rahulcraft",
"message": "Product listed successfully.",
"algoliaIndexed": false,
"note": "SearchIndexService upserts Algolia record with productName, category, sku"
}
}
Seller Features
Feature 07 — Seller Inventory Management: Active, Paused, Delete + Low-Stock Alert
Inventory table for all seller products with six filter/sort options. Pause sets effective stock to zero for buyers
while preserving actual internal stock. Inventory stock updates trigger Stock-Back Alert in-app notifications when stock moves from 0 to positive.
Delete is always soft (isDeleted = true). Algolia is updated through SearchIndexService.
Low-stock indicator shown when stock ≤ 5 units.
Next.js
NestJS
PostgreSQL
SearchIndexService
Socket.IO
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller opens Inventory]):::fe
A --> LOAD[GET /api/v1/seller/products\nWHERE isDeleted = false]:::be
LOAD --> DB[(Query with joins:\nVariants for stock totals\nOrderItems for sales\nProductViews for view counts)]:::db
DB --> TABLE[Render inventory table:\nStatus badge + created date\nThumbnail + name + SKU\nSales + Units + Stock + Views + Price\nStock shown orange if lte 5 units\nActions: Edit Pause Resume Delete]:::fe
TABLE --> FILT{Filter/Sort}
FILT -->|A-Z| F1[Sort name ASC]:::be
FILT -->|Z-A| F2[Sort name DESC]:::be
FILT -->|Oldest| F3[Sort createdAt ASC]:::be
FILT -->|Newest| F4[Sort createdAt DESC]:::be
FILT -->|Active only| F5[WHERE status = ACTIVE]:::be
FILT -->|Paused only| F6[WHERE status = PAUSED]:::be
TABLE --> EDIT_ACT[Edit -> Open product form pre-filled\nPATCH /api/v1/seller/products/:id\nSearchIndexService upsert/remove]:::be
TABLE --> PAUSE_BTN[Seller clicks Pause]:::fe
PAUSE_BTN --> PAUSE_CONFIRM{Confirm dialog}
PAUSE_CONFIRM -->|Cancel| TABLE
PAUSE_CONFIRM -->|Confirm| PAUSE_API[PATCH /api/v1/seller/products/:id/pause]:::be
PAUSE_API --> PAUSE_DB[(status = PAUSED\neffectiveStock = 0\nActual stock preserved\nStock-Back subscribers NOT notified)]:::db
PAUSE_DB --> ALGOLIA_RM[SearchIndexService removeProduct\nProduct hidden from search]:::be
ALGOLIA_RM --> PAUSE_OK[Status badge = Paused\nBuyers see Out of Stock]:::ok
TABLE --> RESUME_BTN[Seller clicks Resume]:::fe
RESUME_BTN --> RESUME_API[PATCH /api/v1/seller/products/:id/resume]:::be
RESUME_API --> RESUME_DB[(status = ACTIVE\neffectiveStock = actual stock\nProduct visible again)]:::db
RESUME_DB --> ALGOLIA_ADD[SearchIndexService upsertProduct]:::be
RESUME_DB --> ALERT_BATCH[Stock-back notification happens when inventory update moves stock from 0 to positive\nCreate Notification rows\nSocket.IO notificationsChanged\nSet notified = true]:::be
ALGOLIA_ADD & ALERT_BATCH --> RESUME_OK[Status = Active\nSubscribers notified]:::ok
TABLE --> DEL_BTN[Seller clicks Delete]:::fe
DEL_BTN --> DEL_CONFIRM{Confirm delete?}
DEL_CONFIRM -->|Confirm| DEL_API[DELETE /api/v1/seller/products/:id]:::be
DEL_API --> DEL_DB[(isDeleted = true\ndeletedAt = NOW\nRemoved from all buyer queries)]:::db
DEL_DB --> ALGOLIA_DEL[SearchIndexService removeProduct]:::be
ALGOLIA_DEL --> DEL_OK[Product gone from marketplace]:::ok
TABLE --> ROW_CLICK[Click product row:\nSplit view — buyer PDP + seller metrics]:::fe
📋 Full Flow Description▼
INVENTORY TABLE COLUMNS:
① Listing Status: badge (Active/Paused) + created date
② Product Details: main image thumbnail, title (clickable to split view), SKU
③ Performance (compact): Sales (₹ total), Units Sold, Stock, Page Views, Price
④ Stock: number — orange badge if ≤ 5 units (low-stock indicator)
⑤ Your Price (₹)
⑥ Actions: [Edit] [Pause/Resume] [Delete]
FILTERS: A–Z | Z–A | Oldest | Newest | Active Only | Paused Only
ACTION: EDIT
→ Opens 6-step product listing form (pre-filled with existing data)
→ PATCH /api/v1/seller/products/{id}
→ SearchIndexService upserts or removes the Algolia product record depending on public status
ACTION: PAUSE
→ Confirm dialog → PATCH /api/v1/seller/products/{id}/pause
→ Set Product.listingStatus = PAUSED, effectiveStock = 0 (buyer queries see zero stock)
→ Actual stock value preserved internally (not lost)
→ Remove from Algolia index (product still findable in seller inventory but hidden from search)
→ Buyer sees: "Out of Stock" on product page
→ Stock-Back Alert subscribers are NOT notified on pause (only on resume)
ACTION: RESUME
→ PATCH /api/v1/seller/products/{id}/resume
→ Set status = ACTIVE, effectiveStock = actual stock
→ Re-add to Algolia index through SearchIndexService when public
→ Stock-back buyer notifications are sent by inventory stock updates when effectiveStock moves from 0 to positive
ACTION: DELETE (Soft Delete)
→ Confirm dialog: "Remove this listing? Buyers won't see it anymore."
→ DELETE /api/v1/seller/products/{id}
→ UPDATE Product SET isDeleted = true, deletedAt = NOW()
→ Remove from Algolia index through SearchIndexService
→ Product disappears from all buyer-facing pages
→ Existing orders referencing this product show "Unavailable Product"
ROW CLICK:
→ /seller/products/{id} — split view: Left = buyer product detail page, Right = inventory metrics panel (sales + units + views + stock history chart)
Seller Features
Updated
Feature 08 — Seller Orders: PENDING_SELLER_APPROVAL → Accept/Reject → Fulfillment Pipeline
Amazon-style order management. Every new order arrives as PENDING_SELLER_APPROVAL — seller has 24 hours to
accept or reject (with mandatory rejection reason). Orders auto-accept after 24 hours of inaction.
Status progression: PENDING_SELLER_APPROVAL → PROCESSING → SHIPPED → OUT_FOR_DELIVERY → DELIVERED.
Real-time notifications via Socket.IO + email + SMS at each status change.
Next.js
NestJS
PostgreSQL
Razorpay Refund API
OrdersService timer + RealtimeService
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Buyer completes Razorpay payment\nWebhook: payment.captured]):::ext
A --> ORDER_CREATE[(Create Order:\nstatus = PENDING_SELLER_APPROVAL\nEscrow: funds held in platform balance)]:::db
ORDER_CREATE --> SELLER_NOTIFY[Notification row + RealtimeService\nseller:data-changed and notifications:changed\nNew order — accept or reject in 24h]:::be
SELLER_NOTIFY --> BUYER_CONFIRM[Buyer sees: Order Pending Approval\nPayment confirmed — awaiting seller acceptance]:::fe
subgraph SELLER_ORDERS [Seller Orders Dashboard]
TAB1[Tab 1 — PENDING_SELLER_APPROVAL\nOrders awaiting accept/reject\n24h countdown shown per order]:::fe
TAB2[Tab 2 — UNSHIPPED PROCESSING\nAccepted orders — enter tracking]:::fe
TAB3[Tab 3 — SHIPPED\nOrders with tracking number added]:::fe
TAB4[Tab 4 — DELIVERED + COMPLETE\nDelivered + closed orders history]:::fe
end
ORDER_CREATE --> TAB1
TAB1 --> DECISION{Seller Decision within 24 hours}
DECISION -->|ACCEPT| ACCEPT[(Order.status = PROCESSING\nnotify buyer: accepted)]:::db
DECISION -->|REJECT — must provide reason| REJECT_FORM[/Enter rejection reason\nRequired field/]:::fe
REJECT_FORM --> REJECT_API[PATCH /api/v1/seller/orders/:id/reject\nbody: rejectionReason]:::be
REJECT_API --> REJECT_DB[(Order.status = CANCELLED\nrejectionReason saved\nInitiate Razorpay refund)]:::db
REJECT_DB --> REFUND[POST /v1/refunds\nRefund processed 5-7 business days\nBuyer notified: order cancelled]:::ext
DECISION -->|No action after 24h| AUTO_ACCEPT[OrdersService setInterval every 60s\nautoAcceptDuePendingOrders\nOrder.status = PROCESSING]:::be
ACCEPT --> TAB2
AUTO_ACCEPT --> TAB2
TAB2 --> SHIP_FORM[/Enter tracking number\nSelect courier partner\nMark as SHIPPED/]:::fe
SHIP_FORM --> SHIP_API[PATCH /api/v1/seller/orders/:id/ship\nbody: trackingNumber, courierName, courierTrackingUrl]:::be
SHIP_API --> SHIP_DB[(Order.status = SHIPPED\nShipping details saved\nBuyer notified with tracking link)]:::db
SHIP_DB --> TAB3
TAB3 --> DELIVERY{Delivery event}
DELIVERY -->|OUT_FOR_DELIVERY webhook| OFD[(Order.status = OUT_FOR_DELIVERY)]:::db
OFD --> DELIVERED_WEBHOOK[Logistics webhook: ORDER_PICKED_UP\nOR manual mark as delivered]:::ext
DELIVERED_WEBHOOK --> PICKED[(Order.status = DELIVERED\nReserve release triggered)]:::db
PICKED --> LEDGER[(OrderAccountingService.releaseDeliveredProceeds:\nwallet_available += sellerProceeds x 0.80\nwallet_reserve decremented by released amount\n20 percent remains reserved)]:::db
LEDGER --> SELLER_CREDIT[Seller wallet release notification]:::ok
PICKED --> TAB4
subgraph STATUS_TRACK [Order Status Timeline]
ST[PENDING_SELLER_APPROVAL\n-> PROCESSING\n-> SHIPPED\n-> OUT_FOR_DELIVERY\n-> DELIVERED\n-> RETURN_INITIATED if buyer requests return\n-> RETURN_ACCEPTED or RETURN_REJECTED\n-> RETURN_SHIPPED\n-> REFUND_PROCESSING\n-> REFUNDED or CLOSED]:::fe
end
📋 Full Flow Description▼
ORDER CREATION (automatic — triggered by Razorpay payment.captured webhook)
→ Order created with status: PENDING_SELLER_APPROVAL
→ Buyer sees: "Order Pending Approval — Payment confirmed. Awaiting seller acceptance."
→ Seller receives: Socket.IO event + email + SMS: "You have a new order. Please accept or reject within 24 hours."
TAB 1 — PENDING_SELLER_APPROVAL
→ Each order card shows: item details, buyer name (masked), quantity, order total, 24h countdown timer
SELLER ACCEPTS: PATCH /api/v1/seller/orders/{id}/accept
→ Order.status = PROCESSING → Buyer notified: "Your order has been accepted by the seller."
SELLER REJECTS (mandatory rejection reason):
→ PATCH /api/v1/seller/orders/{id}/reject { rejectionReason: "Item currently unavailable" }
→ Order.status = CANCELLED → Razorpay refund initiated
→ Buyer notified: "Your order was cancelled by the seller. Refund will be processed within 5–7 business days."
→ Refund reason: seller's rejection reason shown to buyer
AUTO-ACCEPT (24h no action):
→ OrdersService starts an in-process timer on module init and checks due pending orders every 60 seconds
→ Finds orders WHERE status = PENDING_SELLER_APPROVAL AND createdAt < NOW() - 24h
→ Auto-sets status = PROCESSING → seller notified of auto-acceptance
TAB 2 — UNSHIPPED (PROCESSING)
→ Seller sees accepted orders waiting to be shipped
→ Enters tracking number + courier partner → PATCH /api/v1/seller/orders/{id}/ship
→ Order.status = SHIPPED → Buyer notified with tracking link
TAB 3 — SHIPPED
→ Awaiting delivery confirmation
→ OUT_FOR_DELIVERY status set by logistics webhook or seller update
→ ORDER_PICKED_UP/DELIVERED webhook → triggers escrow unlock
TAB 4 — DELIVERED / COMPLETE
→ Delivered orders shown here
→ 80/20 ledger update executed on delivery:
wallet_total_sales += order amount (after platform fee deduction)
wallet_available += order amount × 0.80
wallet_reserve += order amount × 0.20
FULL STATUS PROGRESSION:
PENDING_SELLER_APPROVAL → PROCESSING → SHIPPED → OUT_FOR_DELIVERY → DELIVERED → [RETURN_INITIATED → RETURN_ACCEPTED/RETURN_REJECTED → RETURN_SHIPPED → REFUND_PROCESSING → REFUNDED] → CLOSED
🔌 API Request / Response Reference▼
PATCH /api/v1/seller/orders/:id/accept
{
"method": "PATCH",
"endpoint": "/api/v1/seller/orders/order-uuid/accept",
"headers": { "Cookie": "access_token=httpOnly" }
}
{
"status": 200,
"body": {
"orderId": "order-uuid",
"status": "PROCESSING",
"message": "Order accepted. Please ship promptly."
}
}
PATCH /api/v1/seller/orders/:id/reject
{
"method": "PATCH",
"endpoint": "/api/v1/seller/orders/order-uuid/reject",
"headers": { "Cookie": "access_token=httpOnly" },
"body": {
"rejectionReason": "Item currently out of stock at warehouse."
}
}
{
"status": 200,
"body": {
"orderId": "order-uuid",
"status": "CANCELLED",
"refundInitiated": true,
"refundEta": "5-7 business days"
}
}
Seller Features
Feature 09 — A+ Content Manager: Exact Amazon Seller Central Clone
Mirrors Amazon's A+ Content Manager accessed via Advertising → A+ Content Manager in Seller Central.
Available exclusively to sellers with an approved brand (trademark) — matching Amazon's Brand Registry requirement.
Drag-and-drop block editor with image, heading, text, and video blocks (equivalent to Amazon's Basic A+ modules).
Live preview renders the exact buyer-facing output. All media stored on Cloudinary with public_id linked to the
product record. Content displays below product description as a cohesive, banner-like layout — visually connected
as one unit, identical to Amazon's A+ content section. Supports EN / HI / GU.
Research verified: Amazon A+ Content boosts conversions by up to 8% (Basic) and up to 20% (Premium).
Next.js + DnD Kit
NestJS
PostgreSQL
Cloudinary
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller opens A+ Content Manager]):::fe
A --> LOAD_PRODUCTS[GET /api/v1/seller/products?status=ACTIVE\nDropdown: select product to add A+ content]:::be
LOAD_PRODUCTS --> SELECT{Select product}
SELECT -->|View existing A+| VIEW[GET /api/v1/seller/aplus/:productId\nRender read-only preview]:::be
SELECT -->|Edit existing A+| EDIT[Load block editor\npre-filled with saved blocks\nfrom product.aPlusContent]:::fe
SELECT -->|Delete| DEL[DELETE /api/v1/seller/aplus/:productId\nRemove all A+ blocks from product]:::be
SELECT -->|Create new| CREATE[/Step 1: Enter Content Name + Language\nEN English / HI Hindi / GU Gujarati/]:::fe
CREATE --> EDITOR[Step 2 — Block Editor\nAdd content blocks in order:]:::fe
subgraph BLOCKS [Available Content Blocks — drag-and-drop reorder]
EDITOR --> B1[Image Block\nFull-width or side-by-side\nOptional caption text\nUpload to Cloudinary]:::fe
EDITOR --> B2[Heading Block\nH2 or H3 level\nSection heading text]:::fe
EDITOR --> B3[Text Block\nRich text paragraph\nFormatting: bold italic lists]:::fe
EDITOR --> B4[Video Block\nProduct demo or brand video\nUpload to Cloudinary]:::fe
B1 & B2 & B3 & B4 --> REORDER[Drag-and-drop to reorder blocks\nOrder stored as array index]:::fe
end
EDITOR --> MEDIA_UP[Image/Video upload:\nCloudinary upload widget\npublic_id returned to frontend\nStored in block.mediaPublicId]:::ext
REORDER --> PREVIEW[Step 3 — Live Preview\nRenders exactly as buyer will see it\nCohesive banner-like layout\nAll blocks visually connected\nIdentical to Amazon A+ display]:::fe
PREVIEW --> ACTIONS{Action}
ACTIONS -->|Save Draft| DRAFT[(POST /api/v1/seller/aplus\nbody: productId, contentName, language, blocks \nstatus = DRAFT\nLinked to product + seller account)]:::db
ACTIONS -->|Publish| PUBLISH[(status = PUBLISHED\nA+ content live on product detail page\nDisplayed below product description)]:::db
ACTIONS -->|Delete| DELETE_CONFIRM[Confirm -> remove all A+ blocks]:::warn
PUBLISH --> PDP[Product Detail Page:\nA+ content renders below description\nCohesive banner layout\nAll media loaded from Cloudinary public_ids\nBuyer sees seamless branded experience]:::ok
📋 Full Flow Description▼
SELLER-SIDE WORKFLOW (mirrors Amazon A+ Content Manager exactly):
Step 1 — SELECT PRODUCT
→ Dropdown/search shows seller's active products
→ Options per product: View, Edit, Delete
→ Select product to create new A+ content
Step 2 — CONTENT NAME + LANGUAGE
→ Enter content title (internal label for seller)
→ Select language: EN (English) / HI (Hindi) / GU (Gujarati)
Step 3 — BLOCK EDITOR
Seller adds content blocks in any order:
• Image Block: Full-width OR side-by-side layout; optional caption text; uploaded to Cloudinary; public_id stored in block record
• Heading Block: H2 or H3 section heading
• Text Block: Rich text paragraph (bold, italic, lists)
• Video Block: Product demo or brand video; uploaded to Cloudinary; public_id stored
Drag-and-drop reorder: blocks can be repositioned freely; order stored as array index in database
Step 4 — LIVE PREVIEW
→ Real-time preview renders exactly as it will appear on the buyer's product detail page
→ Cohesive, banner-like layout — all blocks visually connected as one unit
→ Identical to Amazon's A+ content visual style
→ Edit and preview mode both display all uploaded media correctly from Cloudinary
Step 5 — SAVE / PUBLISH / DELETE
→ Save Draft: stores all block data without making it buyer-visible
→ Publish: A+ content goes live on product detail page
→ Delete: removes all A+ content blocks from product
MEDIA STORAGE RULES (A+ Content):
→ Every image and video block uploaded to Cloudinary
→ Cloudinary public_id stored in block record
→ A+ content (with all media references in order) stored on individual product's data within seller's account data
→ A+ content displayed ONLY on the selected product's detail page
→ Preview mode and edit mode both display all media correctly
PRODUCT DETAIL PAGE DISPLAY:
→ A+ content renders BELOW product description
→ Displayed as cohesive, banner-like layout
→ All blocks visually connected as one branded unit
→ Identical in appearance to Amazon's A+ content section
Seller Features
Feature 11 — Brand / Trademark Management: Register, View, 48-Hour Admin Verification
Sellers manage current
TradMark records through /api/v1/seller/brand.
The API returns all trademarks for the seller, allows unlocked trademarks to update category, logo,
description, and website through PATCH /api/v1/seller/brand/:id, and allows rejected submissions
to be resubmitted through POST /api/v1/seller/brand/resubmit. Admin trademark approval activates
the seller only when bank verification is ready.
Next.js
NestJS
PostgreSQL
Cloudinary
Admin Panel
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller opens Brand Management]):::fe
A --> LOAD[GET /api/v1/seller/brand\nLoad current TradMark records]:::be
LOAD --> STATUS{TradMark.tradeMarkStatus?}
STATUS -->|PENDING_APPROVAL| WAIT_UI[Show pending status:\nSubmission date + expected approval\nProgress: Submitted -> Under Review -> Approved]:::warn
STATUS -->|REJECTED| RESUBMIT_UI[Show rejection reason\nForm to resubmit with new brand name]:::err
STATUS -->|ACTIVE| BRAND_VIEW[Show brand dashboard:\nBrand name + category + logo\nLinked products count\nLocked brands cannot be updated]:::ok
BRAND_VIEW --> ACTIONS{Actions}
ACTIONS -->|Update unlocked trademark| LOGO_UP[PATCH /api/v1/seller/brand/:id\nbody: categoryId, logoPublicId, logoUrl, description, websiteUrl]:::be
ACTIONS -->|Update description/details| DESC_UP[PATCH /api/v1/seller/brand/:id\nsame UpdateBrandDto fields]:::be
ACTIONS -->|View Linked Products| PRODUCTS[GET /api/v1/seller/products?brandId=xxx\nList all products under this brand]:::be
RESUBMIT_UI --> RESUBMIT_FORM[/Enter new Brand Name\nOptional category, logo, description, website/]:::fe
RESUBMIT_FORM --> RESUBMIT_SUBMIT[POST /api/v1/seller/brand/resubmit\nbody: name, categoryId, logoPublicId, logoUrl, description, websiteUrl]:::be
RESUBMIT_SUBMIT --> UNIQUE_CHK[(Check new name uniqueness\ncase-insensitive)]:::db
UNIQUE_CHK -->|Taken| NAME_ERR[409 — Brand name already registered]:::err
UNIQUE_CHK -->|Free| RESUBMIT_DB[(Update rejected TradMark:\ntradeMarkStatus = PENDING_APPROVAL\nsubmittedAt + reviewDueAt\nUser accountSetupPhase = TRADEMARK_SUBMITTED)]:::db
subgraph ADMIN [Admin Brand Review]
ADMIN_LIST[Admin views pending brand submissions\nsorted by submittedAt]:::ext
ADMIN_LIST --> ADMIN_DEC{Decision}
ADMIN_DEC -->|Approve| APPR[(TradMark.tradeMarkStatus = ACTIVE\nUser ACTIVE only if bankReady\nEmail notification)]:::ok
ADMIN_DEC -->|Reject| REJ[(TradMark.tradeMarkStatus = REJECTED\nrejectMessage stored\nUser accountSetupPhase = BANK_VERIFIED)]:::err
end
Seller Features
Feature 19 — Deals & Coupons: Current Seller Promotion Builder + Coupon Checkout Validation
Current implementation from
/seller/promotions. Sellers create Lightning Deal or Best Deal records
with product-level deal prices and a minimum discount check. Status is refreshed from database timestamps when
promotion lists are read; there is no BullMQ deal-revert worker in the current code. Coupons are created by seller,
scoped to all products, category, or specific products, then validated in buyer checkout through
/api/v1/buyer/coupons/validate. Checkout creates a CouponRedemption reservation during
payment order creation and applies or releases it during payment verification.
Next.js
NestJS
PostgreSQL
CouponRedemption reservation
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller opens Deals and Coupons\nSidebar: Advertising section]):::fe
A --> TABS{Choose type}
subgraph DEALS [Deals — Lightning Deal and Best Deal]
TABS -->|Deals| DEAL_LIST[GET /api/v1/seller/deals\nList tabs: Active / Scheduled / Ended\nSorted by startDate]:::be
DEAL_LIST --> DEAL_ACTIONS{Action}
DEAL_ACTIONS -->|Create| DEAL_STEP1[Step 1 — Select Product:\nChoose one eligible active product\nDeal eligibility check:\nMust have approved brand trademark\nMinimum 15 percent discount required\nPrice must be lower than current price]:::fe
DEAL_STEP1 --> DEAL_STEP2[Step 2 — Set Deal Terms:\nDeal type: Lightning Deal 4-12 hours\nor Best Deal 1-14 days\nDeal price or discount percent\nMinimum deal quantity units\nStart date-time selection\nEnd date auto-set for Lightning Deals\nDeal badge preview shown]:::fe
DEAL_STEP2 --> DEAL_STEP3[Step 3 — Review + Submit:\nOriginal price vs deal price comparison\nEstimated units & revenue shown\nSubmit deal for activation]:::fe
DEAL_STEP3 --> DEAL_SAVE[(POST /api/v1/seller/deals\nstatus = DRAFT / SCHEDULED / ACTIVE\nDealProduct rows store originalPrice and dealPrice\nStatus refreshed by timestamp on list reads)]:::db
DEAL_ACTIONS -->|Edit active deal| EDIT_RESTRICT[Edit restriction:\nCannot increase discount percentage\nCan only decrease discount amount\nor move end date earlier\nAnti-abuse protection]:::warn
DEAL_ACTIONS -->|Delete| DEL_DEAL[(Soft delete isDeleted = true\nDeal price removed from product immediately\nAnalytics preserved)]:::db
DEAL_ACTIONS -->|View| VIEW_DEAL[Performance view:\nUnits sold at deal price\nRevenue generated\nConversion uplift percentage\nDeal duration & reach]:::fe
end
subgraph COUPONS [Coupons — Checkout Code UX]
TABS -->|Coupons| COUP_LIST[GET /api/v1/seller/coupons\nList tabs: Active / Scheduled / Expired]:::be
COUP_LIST --> COUP_ACTIONS{Action}
COUP_ACTIONS -->|Create| COUP_STEP1[Step 1 — Coupon Code + Type:\nCoupon code: custom seller-defined OR auto-generated\nCode uniqueness validated platform-wide\nType: Percentage off / Fixed amount off INR\nDiscount value entry]:::fe
COUP_STEP1 --> COUP_STEP2[Step 2 — Applicability + Limits:\nApply to: All seller products / Specific products / Category\nMin order value required\nMax discount cap if percentage type\nStart date & End date\nMax total redemptions\nMax uses per customer]:::fe
COUP_STEP2 --> COUP_STEP3[Step 3 — Review + Create:\nCoupon budget impact preview\nGreen clip badge preview shown]:::fe
COUP_STEP3 --> COUP_SAVE[(POST /api/v1/seller/coupons\nStatus = DRAFT / SCHEDULED / ACTIVE\nBuyer enters code at checkout)]:::db
COUP_ACTIONS -->|Edit| COUP_EDIT[Cannot increase discount while active\nCan decrease discount or shorten validity\nCan reduce max redemptions]:::warn
COUP_ACTIONS -->|Cancel or Delete| COUP_DEL[(Coupon.isActive = false\nStatus CANCELLED or soft deleted\nFuture validation rejects it)]:::db
COUP_ACTIONS -->|View| COUP_VIEW[Seller list shows status, scope,\nredemption limits, date window,\nand discount settings]:::fe
end
subgraph BUYER_APPLY [Buyer Clips and Applies Coupon]
CLIP[Buyer enters seller coupon code\non checkout order summary]:::fe
CLIP --> CHECKOUT[Coupon preview applies if cart qualifies]:::fe
CHECKOUT --> VALIDATE[POST /api/v1/buyer/coupons/validate\nbody: code]:::be
VALIDATE --> CODE_CHK[(Find coupon WHERE isActive = true\nAND startDate <= NOW <= endDate)]:::db
CODE_CHK -->|Not found or expired| INVALID[Return 404 COUPON_NOT_FOUND]:::err
CODE_CHK -->|Found| REDEMPT_CHK[(Check redemptions < maxRedemptions\nCheck buyer use count < maxPerCustomer\nCheck cartTotal >= minOrderValue)]:::db
REDEMPT_CHK -->|Limits exceeded| EXCEEDED[Return 422 COUPON_LIMIT_REACHED]:::err
REDEMPT_CHK -->|Valid| APPLY[Discount returned to checkout\nCouponRedemption RESERVED during payment create\nAPPLIED after payment verify and order transaction]:::ok
end
📋 Full Flow Description▼
DEALS — CURRENT IEMS SELLER IMPLEMENTATION
Promotion page creation: select seller product, choose deal type, enter deal price, quantity limit, and date window.
DEAL TYPES:
• Lightning Deal — stored as DealType.LIGHTNING_DEAL with startAt and endAt.
• Best Deal — stored as DealType.BEST_DEAL with seller-selected date window.
DEAL VALIDATION IN CODE:
• Minimum discount: 15% below current product price (required — cannot submit below this threshold)
• Deal price must be strictly lower than the non-deal product price
• Product must belong to the seller and be ACTIVE or OUT_OF_STOCK
• Duplicate product/variant combinations are rejected
DEAL EDIT RESTRICTION (anti-abuse):
• ENDED and CANCELLED deals cannot be edited
• Deal status moves from SCHEDULED to ACTIVE and then ENDED when list endpoints refresh statuses
• Deal price is stored in DealProduct rows; buyer-facing deal display can be wired from those rows when marketplace deal badging is added
COUPONS — CURRENT IEMS SELLER IMPLEMENTATION
Creation form captures code, discount type/value, scope, date window, min order value, cap, and redemption limits.
COUPON UX IN CURRENT CODE:
• Buyers enter a coupon code in the checkout order summary
• The frontend calls
POST /api/v1/buyer/coupons/validate for a preview discount
• Coupon code: custom (seller-defined) OR auto-generated; platform-wide uniqueness enforced
• Types: Percentage Off | Fixed Amount Off (INR)
• Applicability: All seller products | Specific products | Specific category
• Constraints: min order value, max discount cap (%), start/end dates, max total uses, max per customer
• Edit restriction: active coupons cannot have discount increased; can decrease or shorten validity
• Redemption limits are enforced against CouponRedemption rows
• Buyer applies at checkout → POST /api/v1/buyer/coupons/validate → payment create reserves a CouponRedemption → payment verify applies it
COMMON RULES:
→ Seller funds both deals and coupons from their own margin
→ Platform does not subsidize seller promotions
→ Expired deals/coupons move to "Ended" tab (visible 30 days for analytics)
→ Delete always soft (isDeleted = true) — analytics preserved
→ Deals and coupons can be stacked on the same product (each tracked separately)
Seller Features
Feature 10 — Seller Rating & Feedback: Post-Delivery Prompt + Public Profile Display
Current code derives seller ratings when a buyer submits an eligible product review for a delivered order.
Product reviews update SellerRating and refresh SellerProfile.averageRating. Seller can list ratings and reply
from the seller ratings page.
Next.js
NestJS
PostgreSQL
BuyerReviewsService
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Buyer opens delivered order detail]):::fe
A --> RATE_FORM[/Buyer adds product review:\n1-5 stars + optional text\n/]:::fe
RATE_FORM --> SUBMIT[POST /api/v1/buyer/reviews\nbody: sellerOrderId, orderItemId, productId, stars, title, comment]:::be
SUBMIT --> AUTH_CHK[(Verify: order.buyerId = currentUser\norder.status = DELIVERED\nNo existing rating for this orderId)]:::db
AUTH_CHK -->|Not eligible| ERR[403 — not eligible to rate]:::err
AUTH_CHK -->|Eligible| SAVE[(Save SellerRating:\nstars, feedback, buyerId, sellerId, orderId\nCreate public record)]:::db
SAVE --> UPDATE_AVG[(Recalculate seller averageRating:\nAVG stars + total count\nUpdate SellerProfile.avgRating)]:::db
UPDATE_AVG --> RT[Notification row: RATING_RECEIVED\nSeller dashboard reflects new average on refetch]:::be
UPDATE_AVG --> PUBLIC[Rating appears on:\nSeller profile page\nProduct detail page seller info section\nDashboard summary card]:::ok
subgraph SELLER_RESPONSE [Seller Response to Review]
PUBLIC --> SELLER_VIEW[Seller opens Ratings section\nGET /api/v1/seller/ratings\nList: all reviews with star + text + buyer]:::be
SELLER_VIEW --> REPLY_BTN[Seller clicks Reply on any review]:::fe
REPLY_BTN --> REPLY_FORM[/Enter public response\nMax 1000 chars/]:::fe
REPLY_FORM --> REPLY_SUBMIT[PATCH /api/v1/seller/ratings/product-reviews/:id/reply\nor PATCH /api/v1/seller/ratings/:id/reply\nbody: replyText]:::be
REPLY_SUBMIT --> REPLY_SAVE[(Save reply on SellerRating record\nPublicly visible below review)]:::db
end
Seller Features
Feature 21 — Product Q&A: Buyer Asks, Seller Answers, Public on Approval
Buyers submit questions on the product detail page. Seller receives in-app notification and email.
Seller answers from their dashboard. Answered Q&A pairs become publicly visible on the product detail page.
Unanswered questions visible in seller's Q&A management panel with a 48-hour response SLA indicator.
Next.js
NestJS
PostgreSQL
RealtimeService + Notifications
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Authenticated buyer asks a question\non product detail page]):::fe
A --> Q_SUBMIT[POST /api/v1/qa\nbody: productId, questionText]:::be
Q_SUBMIT --> Q_SAVE[(Save Question:\nbuyerId, productId, questionText\nstatus = UNANSWERED)]:::db
Q_SAVE --> NOTIFY_SELLER[Notification row + RealtimeService\nnotifications:changed and seller:data-changed\nNew question on product]:::be
NOTIFY_SELLER --> SELLER_QA[Seller opens QandA Management\nGET /api/v1/seller/questions\nFilter: Unanswered / Answered / All\nSorted by: oldest first]:::be
SELLER_QA --> TABLE[Table columns:\nQuestion text + buyer name masked\nProduct name + asked date\nSLA indicator: orange if more than 24h\nred if more than 48h\nAnswer status badge]:::fe
TABLE --> ANSWER_FORM[/Seller clicks Answer:\nEnter answer text\nMax 2000 characters/]:::fe
ANSWER_FORM --> ANSWER_SUBMIT[POST /api/v1/seller/questions/:id/answer\nbody: answerText]:::be
ANSWER_SUBMIT --> ANSWER_SAVE[(Save answer on Question record\nstatus = ANSWERED\nPublicly visible on PDP)]:::db
ANSWER_SAVE --> NOTIFY_BUYER[Notify buyer:\nIn-app + email: Your question was answered]:::be
ANSWER_SAVE --> PDP_UPDATE[QandA section on product detail page\nshows question + answer publicly]:::ok
TABLE --> REPORT_BTN[Seller can filter by status and productId\nAdmin can moderate through /api/v1/admin/questions]:::warn
Seller Features
Feature 24 — Seller–Buyer Messaging: WhatsApp-Style Real-Time Chat via Socket.IO
Real-time bidirectional messaging between buyers and sellers. Behaves like a modern social media DM
(WhatsApp / Instagram Messages). Both parties can send text and images. Images stored on Cloudinary with
public_id in each message record. Seller's dashboard unread badge updates in real-time via Socket.IO.
Message history paginated (50 per page, oldest-first within thread). Sellers manage all threads
from a unified inbox sorted by last message time.
Next.js
NestJS
PostgreSQL
Socket.IO (real-time)
Cloudinary
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller opens Messages section]):::fe
A --> INBOX[GET /api/v1/seller/messages/threads\nAll threads sorted: last message DESC\nUnread count per thread shown]:::be
INBOX --> THREAD_LIST[Render inbox:\nEach row: buyer avatar + name + last msg preview\nUnread indicator dot\nTimestamp]:::fe
THREAD_LIST --> OPEN_THREAD[Seller clicks on thread\nGET /api/v1/seller/messages/threads/:id/messages\npageSize=50 oldest first]:::be
OPEN_THREAD --> MARK_READ[(Mark all messages in thread as read\nhasUnreadBySeller = false\nDashboard unread count decrements)]:::db
MARK_READ --> CHAT_UI[Render chat UI:\nMessages in WhatsApp-style bubbles\nSeller messages right-aligned\nBuyer messages left-aligned\nTimestamp per message]:::fe
subgraph SEND_MSG [Seller Sends Message]
CHAT_UI --> TEXT_INPUT[/Type message text\nPaper clip icon for image/]:::fe
TEXT_INPUT --> SEND_TEXT[POST /api/v1/seller/messages\nbody: threadId, text]:::be
SEND_TEXT --> MSG_SAVE[(Save ChatMessage:\nsenderId = sellerId\nreceiverID = buyerId\nmessageText, timestamp)]:::db
MSG_SAVE --> EMIT_BUYER[RealtimeService emits message:created\nand threads:changed to buyer user room]:::ext
end
subgraph SEND_IMG [Seller Sends Image]
CHAT_UI --> IMG_PICK[Seller selects image file]:::fe
IMG_PICK --> CLOUDINARY_UP[Upload to Cloudinary\npublic_id returned]:::ext
CLOUDINARY_UP --> SEND_IMG_MSG[POST /api/v1/seller/messages\nbody: threadId, imagePublicId, cloudinaryUrl]:::be
SEND_IMG_MSG --> IMG_SAVE[(Save ChatMessage:\nimagePublicId + cloudinaryUrl\ntimestamp, senderId)]:::db
IMG_SAVE --> EMIT_IMG[Socket.IO emit image message to buyer]:::ext
end
subgraph INCOMING [Buyer Message Arrives]
BUYER_SEND[Buyer sends message or image]:::fe
BUYER_SEND --> SAVE_BUYER[(Save ChatMessage:\nsenderId = buyerId\nreceiverID = sellerId)]:::db
SAVE_BUYER --> EMIT_SELLER[RealtimeService emits message:created\nthreads:changed and notifications:changed]:::ext
EMIT_SELLER --> NOTIF[Notification record stored\nseller popover and toast refresh]:::be
end
🔌 API Request / Response Reference▼
POST /api/v1/seller/messages (send text)
{
"method": "POST",
"endpoint": "/api/v1/seller/messages",
"headers": { "Cookie": "access_token=httpOnly" },
"body": {
"threadId": "thread-uuid-abc",
"text": "Hello, I have a question about sizing."
}
}
{
"status": 201,
"body": {
"messageId": "msg-uuid-xyz",
"threadId": "thread-uuid-abc",
"senderId": "seller-uuid",
"text": "Hello, I have a question about sizing.",
"timestamp": "2026-04-24T12:30:00Z"
}
}
POST /api/v1/seller/messages (send image)
{
"method": "POST",
"endpoint": "/api/v1/seller/messages",
"headers": { "Cookie": "access_token=httpOnly" },
"body": {
"threadId": "thread-uuid-abc",
"imagePublicId": "chat/seller_img_abc123",
"cloudinaryUrl": "https://res.cloudinary.com/.../img.jpg"
}
}
{
"status": 201,
"body": {
"messageId": "msg-uuid-img",
"imagePublicId": "chat/seller_img_abc123",
"cloudinaryUrl": "https://res.cloudinary.com/.../img.jpg",
"timestamp": "2026-04-24T12:31:00Z"
}
}
Finance
Feature 25 — Seller Payout & Wallet: Reserve Ledger + Cashfree Payouts
Current wallet code reserves seller proceeds at payment capture, then moves 80% from reserve to available
on delivery while 20% remains reserved. Payouts require
bankVerificationStatus = VERIFIED
and a verified bank/UPI instrument. PayoutService creates or reuses a Cashfree beneficiary
and dispatches the payout immediately from the request flow. Minimum withdrawal Rs.500, max 2 payouts per day.
Failed or reversed Cashfree transfers re-credit available balance.
Next.js
NestJS
PostgreSQL (never cached)
Cashfree Payout API
RealtimeService
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Payment capture / checkout verification]):::ext
A --> RESERVE[(OrderAccountingService.reserveSellerProceeds\nwallet_total_sales += SellerProceeds\nwallet_reserve += SellerProceeds\nWalletTransaction ORDER_CREDIT\npolicy = 100_reserved_until_delivery)]:::db
RESERVE --> RESERVED_NOTIFY[Notification: Sale reserved in wallet]:::be
RESERVED_NOTIFY --> DELIVERED[Seller marks order delivered]:::be
DELIVERED --> RELEASE[(OrderAccountingService.releaseDeliveredProceeds\nMove 80 percent from reserve to available\nKeep 20 percent in reserve\nWalletTransaction RESERVE_RELEASE)]:::db
subgraph WALLET_UI [Seller Wallet Dashboard — Never Cached]
RELEASE --> OPEN[Seller opens Payouts section\nGET /api/v1/seller/wallet\nAlways fresh DB read — never Redis cached]:::be
OPEN --> WALLET_DATA[(Load:\nwallet_total_sales\nwallet_available = balance - pending\nwallet_reserve\nwallet_withdrawn)]:::db
WALLET_DATA --> RENDER[Render Wallet Dashboard:\nAvailable Balance card\nTotal Earned all-time\nTotal Withdrawn all-time\nPending Withdrawals\nTransaction history paginated 20/page\nRequest Withdrawal button]:::fe
RENDER --> TXN_TYPES[Transaction types in history:\nORDER_CREDIT — green\nWITHDRAWAL_INITIATED — orange\nWITHDRAWAL_COMPLETE — blue\nWITHDRAWAL_FAILED — red\nREFUND_DEBIT — red]:::fe
end
subgraph WITHDRAWAL [Withdrawal Request Flow]
RENDER --> WITHDRAW_BTN[Seller clicks Request Withdrawal]:::fe
WITHDRAW_BTN --> VERIFY_CHK[(Check: sellerProfile.bankVerificationStatus = VERIFIED\nand SellerBankAccountDetails.isVerified = true)]:::db
VERIFY_CHK -->|Not VERIFIED| NOT_VERIFIED[403 BANK_NOT_VERIFIED\nComplete bank verification first]:::warn
VERIFY_CHK -->|VERIFIED| AMOUNT_FORM[/Enter withdrawal amount\nMin: INR 500\nMax: wallet_available balance\nSelect bank account/]:::fe
AMOUNT_FORM --> PRE_CHECKS[(Validate:\nAmount gte 500\nAmount lte wallet_available\npayoutsToday less than 2\nBankAccount.isVerified = true)]:::db
PRE_CHECKS -->|Fail| VALIDATION_ERR[422 specific error:\nInsufficient balance OR\nBelow minimum OR\nDaily limit reached]:::err
PRE_CHECKS -->|Pass| HOLD[(Atomic: deduct from wallet_available\nCreate PayoutRequest status=PENDING\nInsert WalletTransaction type=WITHDRAWAL_INITIATED)]:::db
HOLD --> DISPATCH[Ensure Cashfree beneficiary\nCreate Cashfree payout transfer immediately\nReturn payout response]:::be
end
subgraph PROCESSING [Cashfree Payout Status]
DISPATCH --> CF[Cashfree createBeneficiary if needed\nCashfree createPayout transfer\nStore transfer ids and status codes]:::ext
CF --> RESULT{Cashfree status}
RESULT -->|processed| COMPLETE[(PayoutRequest.status = COMPLETED\nwallet_withdrawn += amount\nWalletTransaction WITHDRAWAL_COMPLETE\nNotify seller success)]:::ok
RESULT -->|pending / processing| PROCESSING_STATE[(PayoutRequest.status = PROCESSING\nSeller can call POST /api/v1/seller/payouts/refresh)]:::warn
RESULT -->|failed / reversed| FAILED[(PayoutRequest.status = FAILED or REVERSED\nwallet_available += amount\nWalletTransaction WITHDRAWAL_FAILED\nNotify seller failure)]:::err
end
subgraph REFUND_IMPACT [Refund Impact on Wallet]
REF([Refund approved for order]):::be
REF --> REF_CHK[(Check if seller already credited\nfor this order)]:::db
REF_CHK -->|Already credited| DEBIT[(Debit wallet_reserve -= refundAmount\nIf reserve insufficient: goes negative\nInsert WalletTransaction REFUND_DEBIT\nNotify seller)]:::db
REF_CHK -->|Not credited| SKIP[No action required\nBuyer refunded from platform]:::ok
end
subgraph EXIT [Account Deletion — Final Settlement]
EXIT_PROT[Current deletion gate:\nBlock if wallet.available > 0\nRetire email, phone, brand names\nSoft-delete user\nRevoke sessions\nRemove stored Cashfree beneficiary ids when possible]:::warn
end
📋 Full Flow Description▼
WALLET CREDIT AND RELEASE:
→ Checkout/payment verification calls OrderAccountingService.reserveSellerProceeds.
→ Reserve stage:
wallet_total_sales += sellerProceeds
wallet_reserve += sellerProceeds
WalletTransaction ORDER_CREDIT metadata.policy = 100_reserved_until_delivery
→ Delivery stage calls OrderAccountingService.releaseDeliveredProceeds.
→ Release stage:
wallet_available += sellerProceeds × 0.80
wallet_reserve decrements by the released 80 percent
20 percent remains reserved
WalletTransaction RESERVE_RELEASE metadata.policy = 80_available_20_reserve
WALLET DASHBOARD:
→ GET /api/v1/seller/wallet — NEVER cached (always fresh DB read)
→ Returns: Available Balance | Pending Payouts | Total Earned | Total Withdrawn
→ Transaction history (20 per page): date | type | amount | balance after
→ Payout history: Request Date | Bank (masked ××××1234) | Amount | Status | Processed Date | Failure Reason
PAYOUT GATE — Bank Verification Check:
→ sellerProfile.bankVerificationStatus is validated on every payout request
→ SellerBankAccountDetails.isVerified must be true
→ Payout instrument can be verified bank details or verified UPI details
WITHDRAWAL REQUEST VALIDATION:
→ Amount ≥ ₹500 (minimum withdrawal) → 422 PAYOUT_MIN_AMOUNT if below
→ Amount ≤ wallet_available → 422 PAYOUT_INSUFFICIENT if exceeded
→ payoutsToday < 2 → 429 PAYOUT_DAILY_LIMIT "Max 2 withdrawals per day"
→ BankAccount.isVerified = true → 403 BANK_NOT_VERIFIED otherwise
PAYOUT PROCESSING (current request flow):
→ Ensure or create Cashfree beneficiary using verified bank/UPI instrument
→ Call Cashfree transfer API immediately after PayoutRequest is created
→ Success: COMPLETED, wallet_withdrawn increments on status finalization, seller notified
→ Pending/processing: seller can call POST /api/v1/seller/payouts/refresh
→ Failed/reversed: payout status changes to FAILED/REVERSED, wallet_available is re-credited, seller notified
RETURN IMPACT ON WALLET:
→ Refund approved → deduct from wallet_reserve
→ If wallet_reserve insufficient → goes negative (debt record created)
→ Future earnings fill the debt before crediting to wallet_available
🔌 API Request / Response Reference▼
GET /api/v1/seller/wallet
{
"method": "GET",
"endpoint": "/api/v1/seller/wallet",
"headers": { "Cookie": "access_token=httpOnly" }
}
{
"status": 200,
"body": {
"wallet": {
"totalSales": 85000,
"available": 58000,
"reserve": 17000,
"withdrawn": 10000,
"pendingPayout": 0
},
"verificationStatus": "VERIFIED",
"transactions": [
{
"date": "2026-04-24T10:00:00Z",
"type": "ORDER_CREDIT",
"amount": 800,
"balanceAfter": 58000,
"orderId": "order-uuid"
}
],
"pagination": { "page": 1, "total": 42 }
}
}
POST /api/v1/seller/payouts/request
{
"method": "POST",
"endpoint": "/api/v1/seller/payouts/request",
"headers": { "Cookie": "access_token=httpOnly" },
"body": {
"amount": 5000,
"mode": "IMPS",
"idempotencyKey": "seller-withdrawal-5000-001"
}
}
{
"status": 201,
"body": {
"payoutId": "payout-uuid-xyz",
"amount": 5000,
"status": "PROCESSING",
"cashfreeTransferId": "transfer-id",
"message": "Payout sent to Cashfree for processing."
}
}
Exit Protocol
Feature 03 — Seller Account Deletion: Current OTP + Soft Delete Flow
Current seller deletion requires an authenticated seller session and email OTP. After OTP verification,
AccountDeletionService blocks deletion only when wallet.available is still greater than zero,
removes stored Cashfree beneficiary ids when possible, records DeletionRequest.status = RESOLVED,
retires email, phone, and seller brand names, soft-deletes the user, and revokes all sessions.
Next.js
NestJS
PostgreSQL
Cashfree beneficiary cleanup
TokenService revoke-all
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ext fill:#f3e8ff,stroke:#9333ea,color:#4c1d95,font-size:12px
classDef err fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller initiates account deletion\nfrom Profile Settings]):::fe
A --> REASON[/Select deletion reason and optional detail/]:::fe
REASON --> OTP_REQUEST[POST /api/v1/auth/seller/delete/request\nGenerate deletion OTP\nTTL 60 seconds\nSend to verified email]:::be
OTP_REQUEST --> WARN_SCREEN[/Display privacy policy warning:\nEmail & phone will be permanently retired\nCannot create a new account with same credentials\nUser must acknowledge before OTP entry/]:::fe
WARN_SCREEN --> OTP_INPUT[/Enter 6-digit OTP\nConfirm deletion intent/]:::fe
OTP_INPUT --> VERIFY_OTP[(Verify OTP hash + expiry)]:::db
VERIFY_OTP -->|Invalid| OTP_ERR[OTP error]:::err
VERIFY_OTP -->|Valid| SUBMIT_REQUEST[DELETE /api/v1/auth/seller/delete/verify\nbody: otp + reason + reasonDetail]:::be
SUBMIT_REQUEST --> GATE1[(Check seller wallet.available)]:::db
GATE1 -->|Balance exists| FAIL1[Block deletion\nWithdraw available wallet first]:::err
GATE1 -->|Zero balance| SOFT_DELETE[(DeletionRequest RESOLVED\nSet User.isDeleted = true\nSet userProfileStatus = DELETED\nAll sessions invalidated\nRetire email, phone, brand names)]:::db
SOFT_DELETE --> CASHFREE[Remove stored Cashfree beneficiary ids when possible]:::ext
CASHFREE --> DONE([Account deleted\nCredentials permanently retired]):::ok
subgraph RETIRED [Retired Credential Enforcement]
REUSE[Attempt signup or login with retired email/phone]:::fe
REUSE --> RET_CHK[(Check retired_credentials table)]:::db
RET_CHK -->|Match found| HTTP410[Return HTTP 410 Gone:\nThis credential was associated with\na deleted account. Per Privacy Policy,\ndeleted credentials are permanently retired.\nContact support if this is an error.]:::err
end
📋 Full Flow Description▼
CURRENT EXIT PROTOCOL:
Step 1 — OTP Confirmation:
→ Privacy policy warning displayed: email/phone permanently retired
→ POST /api/v1/auth/seller/delete/request sends a 6-digit OTP to verified email (TTL 60s)
→ OTP verified before deletion request submitted
Step 2 — Wallet Gate:
→ AccountDeletionService checks seller wallet.available
→ If available balance is greater than zero, deletion is blocked until withdrawal/settlement is complete
SOFT DELETION:
→ DELETE /api/v1/auth/seller/delete/verify { otp, reason, reasonDetail }
→ DeletionRequest upserted with status = RESOLVED
→ User.isDeleted = true, userProfileStatus = DELETED
→ All sessions invalidated immediately via TokenService
→ Email, phone, and seller brand names added to retired_credentials table
→ Stored Cashfree beneficiary ids are removed when the provider call succeeds
→ Brand/products still retained in DB for order history integrity (isDeleted = true, invisible to buyers)
RETIRED CREDENTIAL MESSAGE (HTTP 410):
"This email address (or phone number) was previously associated with a deleted account on this platform.
Per our Privacy Policy, deleted credentials are permanently retired and cannot be used to create or access any account.
If you believe this is an error, please contact support."
Current Code Sync
Current Seller Implementation — Promotions, Notifications, API Testing, Admin Compliance
This section documents the working code paths currently implemented in the monorepo. It keeps the existing
feature diagrams intact while syncing the production route names and data flow: seller promotions live under
/seller/deals and /seller/coupons, seller navigation includes a Deals & Coupons page,
and manual screenshot testing is available from /seller/api-testing.
Next.js Seller UI
NestJS Seller APIs
Prisma/Postgres
Low Redis usage
flowchart TD
classDef fe fill:#dbeafe,stroke:#3b82f6,color:#1e40af,font-size:12px
classDef be fill:#dcfce7,stroke:#16a34a,color:#166534,font-size:12px
classDef db fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-size:12px
classDef ok fill:#d1fae5,stroke:#059669,color:#064e3b,font-size:12px
classDef warn fill:#fff7ed,stroke:#ea580c,color:#7c2d12,font-size:12px
A([Seller dashboard shell]):::fe --> NAV[Sidebar routes:
Dashboard, Products, Inventory, Orders,
Messages, Product Q&A, Deals & Coupons,
Payouts, A+ Content, Brand, Ratings,
Profile, API Testing]:::fe
NAV --> PROMO_PAGE[Seller promotions page\n/seller/promotions]:::fe
PROMO_PAGE --> LOAD[Load products, categories, deals, coupons]:::fe
LOAD --> PRODUCTS[GET /api/v1/seller/products?limit=100]:::be
LOAD --> CATS[GET /api/v1/seller/products/categories]:::be
LOAD --> DEALS[GET /api/v1/seller/deals]:::be
LOAD --> COUPONS[GET /api/v1/seller/coupons]:::be
PROMO_PAGE --> CREATE_DEAL[POST /api/v1/seller/deals]:::be
CREATE_DEAL --> DEAL_RULES{Validate}
DEAL_RULES -->|Seller-owned active product| DEAL_DB[(Deal + DealProduct)]:::db
DEAL_RULES -->|Variant mismatch or price invalid| DEAL_ERR[Return proper validation error]:::warn
DEAL_DB --> DEAL_STATUS[Status resolved from startAt/endAt:
DRAFT, SCHEDULED, ACTIVE, ENDED, CANCELLED]:::ok
PROMO_PAGE --> CREATE_COUPON[POST /api/v1/seller/coupons]:::be
CREATE_COUPON --> COUP_RULES{Validate}
COUP_RULES -->|Unique code and valid scope| COUP_DB[(Coupon + CouponProduct)]:::db
COUP_RULES -->|Duplicate or invalid scope| COUP_ERR[Return proper validation error]:::warn
DEALS --> CANCEL_DEAL[PATCH /api/v1/seller/deals/:id/cancel]:::be
COUPONS --> CANCEL_COUPON[PATCH /api/v1/seller/coupons/:id/cancel]:::be
CANCEL_DEAL --> SOFT[Soft state retained for analytics]:::db
CANCEL_COUPON --> SOFT
NAV --> TEST[Seller API testing page\n/seller/api-testing]:::fe
TEST --> FIXTURES[Auto-load live fixtures:
seller product, categories, deal, coupon]:::be
TEST --> RUN[Run GET/POST/PATCH endpoints with confirmation for mutations]:::ok
PRODUCTS --> CACHE[Category/country/HSN metadata uses in-process cache
Free Redis preserved for OTP, lockout, checkout, queues]:::ok
📋 Current Seller Route Reference▼
PROMOTIONS ROUTES
GET /api/v1/seller/deals
POST /api/v1/seller/deals
GET /api/v1/seller/deals/:id
PATCH /api/v1/seller/deals/:id
PATCH /api/v1/seller/deals/:id/cancel
DELETE /api/v1/seller/deals/:id
GET /api/v1/seller/coupons
POST /api/v1/seller/coupons
GET /api/v1/seller/coupons/:id
PATCH /api/v1/seller/coupons/:id
PATCH /api/v1/seller/coupons/:id/cancel
DELETE /api/v1/seller/coupons/:id
SELLER API TESTING
/seller/api-testing is a role-protected Postman-style UI. It loads live fixtures and runs manual endpoints for screenshots.
Mutating endpoints ask for confirmation before calling the API.
NOTIFICATION / REALTIME RULE
Seller notifications are displayed from the nav popover and toast system. Order/message related notifications should be
marked read when the seller opens the matching context, such as pending orders or messages.
REDIS FREE PLAN RULE
Seller catalog metadata does not use Redis. ProductsService uses in-process cache for category, country, and HSN lookups.
Redis remains for OTP, lockout, checkout idempotency, pending payment state, and queue infrastructure.