Skip to main content

Returns Module

The RMA (Return Merchandise Authorization) engine – customer initiation, admin arbitration, refund processing, and automated restocking.

RMA Razorpay Inventory


📖 Table of Contents


Base Route : /api/v1/returns

Customer endpoints require a valid access token (USER role). Admin endpoints require ADMIN role.


Endpoints Overview

MethodEndpointAccessDescription
Customer
POST/:orderId/initiatePrivate (Bearer)Initiate a return request for a delivered order
GET/mePrivate (Bearer)Fetch authenticated user's return history
Admin
GET/adminAdminFetch all return requests (arbitration queue)
PATCH/admin/:returnId/arbitrateAdminApprove or reject a return request (with reason)
POST/admin/:returnId/processAdminExecute Razorpay refund and restock inventory

Rate limits: standardLimiter (100/15min) on all endpoints.


Return State Machine

Business rules:

  • Returns can only be initiated within 7 days of order delivery.
  • Certain product categories (INNERWEAR) are non‑returnable (hygiene policy).
  • Fragile items require uploaded images as proof (Cloudinary).

Customer Endpoints

POST /:orderId/initiate

Initiate a return request for a specific delivered order. The user can request return for specific items (partial returns allowed). Images are required for fragile items.

Headers:

  • Authorization: Bearer <access_token>
  • Content-Type: multipart/form-data (if images included) or application/json

URL Parameter:

  • orderId – valid MongoDB ObjectId of the delivered order.

Request body (JSON / form-data):

FieldTypeRequiredDescription
itemsarrayYesList of items to return
items[].productIdstringYesProduct ID being returned
items[].quantityintegerYesQuantity to return (≤ purchased quantity)
items[].reasonstringYesDEFECTIVE, WRONG_ITEM, SIZE_ISSUE, NOT_LIKED
items[].customerNotestringNoAdditional details (max 500 chars)
imagesfile arrayConditionalRequired if any item has isFragile: true (max 5 images)

Example request (JSON without images):

{
"items": [
{
"productId": "64a7b...",
"quantity": 1,
"reason": "DEFECTIVE",
"customerNote": "The bangle arrived cracked."
}
]
}

Response (201 Created):

{
"success": true,
"statusCode": 201,
"message": "Return request submitted successfully",
"data": {
"_id": "64a7c...",
"returnNumber": "RET-9A4B2",
"status": "PENDING",
"items": [...],
"createdAt": "2026-05-22T10:30:00.000Z"
},
"timestamp": "2026-05-22T10:30:00.000Z"
}

Business validation:

  • Order must be DELIVERED (not PROCESSING or SHIPPED).
  • Return window: currentDate ≤ order.deliveredAt + 7 days.
  • Hygiene products (itemType: "INNERWEAR") cannot be returned (returns 403 Forbidden).

GET /me

Fetch the authenticated user's return request history (paginated).

Headers:
Authorization: Bearer <access_token>

Query parameters:

ParamTypeDefaultDescription
pageinteger1Page number
limitinteger10Items per page (max 50)
statusstringFilter by PENDING, APPROVED, REJECTED, COMPLETED

Response (200 OK):

{
"success": true,
"statusCode": 200,
"message": "Returns retrieved successfully",
"data": {
"returns": [
{
"_id": "...",
"returnNumber": "RET-9A4B2",
"status": "COMPLETED",
"items": [...],
"refundAmount": 450,
"createdAt": "2026-05-20T12:00:00.000Z"
}
],
"meta": { "total": 5, "limit": 10, "page": 1 }
},
"timestamp": "2026-05-22T10:35:00.000Z"
}

Admin Endpoints

GET /admin

Fetch all return requests across the platform for the arbitration queue. Supports filtering by status.

Headers:
Authorization: Bearer <admin_access_token>

Query parameters:

ParamTypeDefaultDescription
pageinteger1Page number
limitinteger20Items per page (max 100)
statusstringFilter by return status

Response (200 OK): Paginated list of returns with customer details, order reference, and images.


PATCH /admin/:returnId/arbitrate

Approve or reject a pending return request. If rejecting, an adminRejectionReason is required (Zod validation).

Headers:
Authorization: Bearer <admin_access_token>

URL Parameter:

  • returnId – valid MongoDB ObjectId of the return.

Request body:

{
"status": "APPROVED"
}

Or for rejection:

{
"status": "REJECTED",
"adminRejectionReason": "Item does not match the original condition"
}

Response (200 OK): Returns the updated return object.

After approval, the return is ready for refund processing via /process.

POST /admin/:returnId/process

Execute the refund (via Razorpay) and automatically restock the returned items. This is a non‑reversible action.

Headers:
Authorization: Bearer <admin_access_token>

URL Parameter:

  • returnId – valid MongoDB ObjectId (status must be APPROVED).

Request body: None (empty).

Response (200 OK):

{
"success": true,
"statusCode": 200,
"message": "Refund processed and inventory restocked",
"data": {
"status": "COMPLETED",
"refundId": "refund_xyz123",
"refundAmount": 450
},
"timestamp": "2026-05-22T11:00:00.000Z"
}

What happens server‑side:

  • Calls Razorpay refund API (full or partial).
  • Increments currentStock for each returned product.
  • Updates return status to COMPLETED.
  • Sends email notification to customer.

Error (409): If refund fails (e.g., insufficient balance in Razorpay), status becomes FAILED and requires manual intervention.


Validation Rules (Zod)

EndpointFieldRules
/initiateitemsarray, min 1 item, required
items[].productIdvalid ObjectId
items[].quantityinteger ≥ 1
items[].reasonenum ["DEFECTIVE","WRONG_ITEM","SIZE_ISSUE","NOT_LIKED"]
items[].customerNotestring, max 500 chars, optional
/admin/:returnId/arbitratestatusenum ["APPROVED","REJECTED"], required
adminRejectionReasonrequired if status = "REJECTED" (min 10 chars)
/admin/:returnId/processNo body

All schemas use .strict() – extra fields are rejected.


Error Responses

StatusCodeExample messageWhen
400BAD_REQUEST"Admin rejection reason is required when rejecting a return"Reject without reason
400BAD_REQUEST"The 7-day return policy window has expired"Return initiated too late
401UNAUTHORIZED"Authentication required"Missing/invalid token
403FORBIDDEN"Hygiene Policy Violation: This product cannot be returned"Innerwear item
403FORBIDDEN"You do not have permission to perform this action"Non‑admin on admin route
404NOT_FOUND"Order not found"Invalid order ID
409CONFLICT"Return already processed"Trying to process an already completed return
409CONFLICT"Return request already exists for this item"Duplicate return on same order item

Runbook

For manual testing with Thunder Client / Postman, see ../../testing/return-runbook.md. The runbook covers:

  • Initiating a return (within 7 days)
  • Admin approval/rejection
  • Processing refund and restock
  • Edge cases: hygiene policy, 7-day TTL, missing rejection reason


Seamless returns, automated refunds – the Reshma‑Core RMA engine.