AstroBaaS

Orders & fulfilment

Returns & Exchanges (Self-Service RMA Portal)

Free — GPL coresize Lplanned, not built

Generated from docs/plan/core/returns-exchanges/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.

Returns are a customer service burden — merchants manually handle RMA requests via email. This feature provides a self-service RMA portal so customers create return requests without calling support.

The problem

Every return starts with an email asking ‘How do I return this?’. I have to manually issue RMA numbers, print labels, and track returns in a spreadsheet. A self-service portal would cut support tickets by 40%.

What it does

  • Add returns collection: {id, order_id, order_item_id, reason (defective|wrong_size|wrong_color|damaged|not_as_described|other), status (requested|approved|shipped|received|inspected|refunded|completed), created_at, created_by_customer_id, notes}
  • Storefront feature: customer clicks ‘Return Item’ from order detail, selects reason, submits return request
  • Staff approves/denies returns from admin panel with optional comments
  • Core generates RMA number and includes in approval email to customer
  • Approved return shows: RMA number, return shipping label (if enabled), instructions, deadline to ship back
  • Staff marks return as ‘received’, inspects items, then issues refund
  • Track refund status separately: approved → issued → completed
  • Display return history in customer account (storefront) and order detail (admin)

What it deliberately does NOT do

Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.

  • Automatic refund issuance — that is payment processing, requires verification of received items; staff must approve before refund triggers
  • Return shipping label generation from carriers (FedEx, UPS) — that is shipping-labels-carrier-sync (paid module); core only stores label URL if provided
  • Advanced return logic (exchange-first, store-credit routing, restocking fees) — that is exchange-first-returns (paid module); core only handles basic return state

Data model

returns collection: {id, order_id, order_item_id, reason, status, created_at, created_by_customer_id, approved_at, approved_by_user_id, rma_number, shipping_label_url, refund_amount_minor, refund_status}. Migration: new collection. orders.return_status field (none, pending_approval, approved, returned, completed).

API

  • POST /api/customers/:customerId/returns — create return request
  • GET /api/customers/:customerId/orders/:orderId/returns — list returns for order
  • GET /api/admin/returns — list all return requests with status
  • PATCH /api/admin/returns/:returnId/status — update return status (approve, deny, mark received)
  • POST /api/admin/returns/:returnId/refund — issue refund

Every route added here must also appear in src/pages/openapi.json.ts — a test fails the build if it does not.

Admin

Admin panel ‘Returns’ section shows: (1) pending requests with customer name, order #, items, reason; (2) action buttons ‘Approve’ / ‘Deny’; (3) approved returns with RMA number, shipping label link (if available), refund button; (4) completed returns with refund amount and date.

The seam — why this is core

Core owns return entity, RMA numbering, approval workflow, and basic state machine (requested → approved → received → refunded). Paid module (exchange-first-returns) owns exchange-first logic, store-credit routing, and advanced rules. Paid module (shipping-labels-carrier-sync) owns return shipping labels.

Core owns return status machine, refund state, and RMA interface; paid modules own advanced exchange logic, store-credit routing, and carrier label generation

Dependencies

  • Existing orders, order_items, customers collections
  • email layer (to send RMA approval/denial emails)
  • Existing storefront (to show ‘Return Item’ button)
  • payments system (to issue refunds)

Acceptance checks

Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.

  • Customer submits return request for damaged item; confirm returns collection stores reason=‘damaged’, status=‘requested’
  • Staff approves return; confirm RMA number is generated and return status changes to ‘approved’
  • Verify approval email is sent to customer with RMA number and return instructions
  • Staff marks return as ‘received’; confirm status=‘received’
  • Staff issues refund; confirm refund is processed and status=‘completed’
  • Verify return appears in customer account dashboard with RMA number and status

Risks

Fraudulent returns (customer claims item is defective when it’s not) can be hard to prevent without photo verification. Implement optional photo upload on return request for staff review. Refund issuance without proper verification can cause chargebacks; enforce ‘received’ status before refund can be issued.

Commercial context

Suggested pricefree (core)
Rival anchor6 apps (ReturnGO, Return Prime, Redo, Xcotton, Rich Returns, Yanet); all freemium with core returns tracking free

The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.


Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full catalogue and ../../AI-GUIDE.md for how to work on this repository.