orangecat/module-returns

Customer-facing RMA and return request management for Magento 2 B2B stores

Maintainers

Package info

github.com/olivertar/m2_returns

Type:magento2-module

pkg:composer/orangecat/module-returns

Statistics

Installs: 19

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

0.0.2 2026-06-07 18:28 UTC

This package is auto-updated.

Last update: 2026-06-07 18:54:50 UTC


README

Customer-facing RMA and return request management for Magento 2 B2B stores.

Module: Orangecat_Returns Version: 1.0.0 License: OSL-3.0 Author: Oliverio Gombert olivertar@gmail.com

Table of Contents

  1. Overview
  2. Theme Compatibility
  3. Requirements
  4. Installation
  5. What Gets Installed
  6. Return Eligibility Rules
  7. Configuration
  8. Store Admin Guide
  9. Buyer Guide (Frontend)
  10. Developer Guide
  11. REST API
  12. Frontend Routes Reference
  13. DevOps & Integrator Notes

Overview

Orangecat_Returns adds a full return-request (RMA) workflow to Magento 2. Customers submit return requests from their account area; admins review, update status, assign a resolution, and add internal notes. Every status change triggers an automatic email to the customer.

The module handles:

  • Per-product returnability toggle via the is_returnable product attribute
  • Configurable return window (days) measured from order creation or latest shipment date
  • Optional partial returns — customers can select individual items and quantities
  • Four configurable lookup catalogs: Statuses, Reasons, Conditions, Resolutions — all with per-store label translations
  • Increment ID generation in RMA000000001 format
  • Automated email notifications to admins on new requests and to customers on status changes
  • Full Hyvä and Luma theme support; Breeze Evolution CSS included

Position in the Orangecat B2B Dependency Chain

Orangecat_Returns has no dependency on other Orangecat modules. It stands alone and can be deployed alongside any combination of the Orangecat B2B suite.

Orangecat_Core (via composer: orangecat/core)

Orangecat_Returns     ← this module (independent)

Return Eligibility Rules

A return request can only be created when all of the following are true:

  1. The module is enabled in configuration.
  2. The order is in complete or closed state.
  3. The return window has not expired (configurable; 0 = no limit).
  4. At least one item in the order has is_returnable = Yes on its product.
  5. Available quantity for each selected item is greater than zero (all previous requests count, even rejected ones).

Theme Compatibility

Theme Status Notes
Luma Supported Standard .phtml templates. Less styles via view/frontend/web/css/source/_module.less.
Hyvä Supported Dedicated *_hyva.phtml templates with Tailwind CSS. Loaded via hyva_* layout handles. Module CSS injected via hyva_default.xml.
Breeze Evolution Partial No dedicated Breeze templates; falls back to Luma .phtml. Breeze-compatible Less styles provided in view/frontend/web/css/breeze/_default.less.

To add proper Breeze template support: create view/frontend/templates/account/returns_list_breeze.phtml, return_view_breeze.phtml, and the equivalent create templates, then wire them in breeze_returns_* layout handles.

Requirements

Dependency Version / Notes
PHP 8.1+
Magento 2.4.x
Orangecat_Core composer: orangecat/core
Magento_Catalog core
Magento_Sales core
Magento_Customer core
Magento_Eav core — used by AddIsReturnableAttribute data patch
Magento_Email core
Magento_Ui core
Magento_Backend core

Installation

This module is a git submodule of the B2B SDK. To add it:

git submodule add git@github.com:olivertar/m2_returns.git app/code/Orangecat/Returns
git submodule update --init app/code/Orangecat/Returns

Then, inside the PHP container (reward shell):

bin/magento module:enable Orangecat_Returns
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush

What Gets Installed

Database Tables

return_status

The configurable status catalog.

Column Type Notes
status_id int unsigned, PK, auto-increment
code varchar(50), NOT NULL Unique machine code (e.g. pending).
label varchar(255), NOT NULL Default display label.
sort_order int Display order in dropdowns.
is_active smallint 1 = active, 0 = hidden.

return_status_store

Per-store label translations for statuses.

Column Type Notes
status_id int unsigned, PK+FK Cascades on delete.
store_id smallint unsigned, PK+FK Cascades on delete.
label varchar(255), NOT NULL Translated label for this store.

return_reason

The configurable reason catalog.

Column Type Notes
reason_id int unsigned, PK, auto-increment
label varchar(255), NOT NULL
is_other smallint 1 = renders a free-text textarea on the form.
sort_order int
is_active smallint

return_reason_store

Per-store label translations for reasons. Same structure as return_status_store.

return_condition

The configurable item-condition catalog.

Column Type Notes
condition_id int unsigned, PK, auto-increment
label varchar(255), NOT NULL
sort_order int
is_active smallint

return_condition_store

Per-store label translations for conditions. Same structure as return_status_store.

return_resolution

The configurable resolution catalog — the outcome assigned by admin when processing a request (e.g. Refund, Exchange).

Column Type Notes
resolution_id int unsigned, PK, auto-increment
label varchar(255), NOT NULL
sort_order int
is_active smallint

return_resolution_store

Per-store label translations for resolutions. Same structure as return_status_store.

return_request

The return request header — one record per submitted RMA.

Column Type Notes
request_id int unsigned, PK, auto-increment
increment_id varchar(50), NOT NULL Human-readable ID, e.g. RMA000000001.
order_id int unsigned, NOT NULL, FK References sales_order.entity_id. Cascades on delete.
order_increment_id varchar(50), NOT NULL Snapshot of the order increment ID.
customer_id int unsigned, nullable FK to customer. Nullable for future guest support.
customer_email varchar(255), NOT NULL Snapshot of the customer email at submission time.
store_id smallint unsigned, NOT NULL Store the order was placed on.
status varchar(50), NOT NULL, default pending Current status code.
resolution varchar(255), nullable Resolution label assigned by admin.
admin_notes text, nullable Internal notes — not visible to the customer.
customer_comments text, nullable Customer's freeform comment submitted with the request.
created_at timestamp Set on INSERT.
updated_at timestamp Updated on every save.

Indexes: customer_id, order_id, status.

return_request_item

One record per line item in a return request.

Column Type Notes
item_id int unsigned, PK, auto-increment
request_id int unsigned, NOT NULL, FK References return_request.request_id. Cascades on delete.
order_item_id int unsigned, NOT NULL References the original sales_order_item.
product_id int unsigned, NOT NULL Product ID at submission time.
product_name varchar(255), NOT NULL Snapshot of the product name.
product_sku varchar(255), NOT NULL Snapshot of the product SKU.
qty_requested decimal(12,4), NOT NULL Quantity the customer wants to return.
reason varchar(255), NOT NULL Reason label stored as text (snapshot).
reason_other text, nullable Free-text filled when reason is_other = 1.
condition_label varchar(255), nullable Item condition label stored as text (snapshot).

Index: request_id.

EAV Attributes

Attribute Code Entity Type Input Scope Default Notes
is_returnable catalog_product int boolean Global 0 (No) Appears in product edit under the Returns attribute group. Applies to simple, configurable, virtual, bundle, grouped products.

Important: Products with is_returnable = No (the default) are silently excluded from all return forms. You must explicitly set is_returnable = Yes on each product you want to allow returns for. For configurable products, the attribute is checked on the child simple product selected in the order.

Data Patches

Class What It Installs
InstallDefaultStatuses 10 statuses: Pending, Authorized, Partially Authorized, Denied, Return Received, Return Partially Received, Approved, Rejected, Processed and Closed, Closed.
InstallDefaultReasons 6 reasons: Wrong Item, Defective Product, Not As Described, Changed My Mind, Arrived Too Late, Other (is_other=1).
InstallDefaultConditions 3 conditions: Unopened, Opened, Damaged.
InstallDefaultResolutions 3 resolutions: Exchange, Refund, Store Credit.
AddIsReturnableAttribute Adds the is_returnable boolean attribute to catalog_product.

Configuration

Navigate to Stores > Configuration > Orangecat > Returns.

General Configuration

Label Config path Default Description
Enable Returns Module returns/general/enabled Yes Master toggle. Disables all frontend routes and hides the "My Returns" account link when off.
Allow Partial Returns returns/general/allow_partial Yes When disabled, the customer must return the full order; item selection checkboxes are hidden.
Return Window (days) returns/general/return_days_limit 30 Number of days after the reference date during which a return may be submitted. Set to 0 for no limit.
Calculate Return Period From returns/general/return_period_start Order Creation Date order = order created_at; shipment = latest shipment created_at. When set to shipment, orders with no shipments cannot be returned.
Return Address returns/general/return_address Shown to customers on the return form and confirmation pages. Store-level scope.
Return Instructions returns/general/return_instructions Shown to customers alongside the return address. Store-level scope.
Admin Notification Emails returns/general/notification_emails Comma-separated list of email addresses notified when a new return request is submitted.
returns/general/enabled
returns/general/allow_partial
returns/general/return_days_limit
returns/general/return_period_start
returns/general/return_address
returns/general/return_instructions
returns/general/notification_emails

Store Admin Guide

Navigating to Returns

All returns management is under Orangecat B2B > Returns in the admin menu.

Manage Return Requests

Path: Orangecat B2B > Returns > Manage Returns

The grid displays all submitted return requests with columns for Increment ID, Order, Customer Email, Status, Store, and Created date. Use the column filters and search to locate specific requests.

Viewing a request:

  1. Click a row or use Edit in the Actions column.
  2. The detail page shows the request header (status, resolution, admin notes) and the Items tab listing every line item with its reason, condition, and requested quantity.

Processing a request:

  1. Open the return request edit page.
  2. Change Status to reflect the current stage (e.g. Authorized, Denied).
  3. Optionally set a Resolution (e.g. Refund).
  4. Add Admin Notes for internal tracking — these are not emailed to the customer.
  5. Click Save. If the status changed, the customer receives an automatic status-update email.

Admins can only update status, resolution, and admin_notes. Item details are immutable after submission.

Managing Statuses

Path: Orangecat B2B > Returns > Statuses

Create, edit, or deactivate return statuses. Each status has a machine code (must be unique), a display label, a sort_order, and an is_active flag. Per-store label translations are managed on the status edit form.

Managing Reasons

Path: Orangecat B2B > Returns > Reasons

Each reason can optionally have Is Other = Yes, which causes a free-text textarea to appear on the frontend return form when that reason is selected.

Managing Conditions

Path: Orangecat B2B > Returns > Conditions

Item conditions reported by the customer (e.g. Unopened, Damaged). Supports per-store label translations.

Managing Resolutions

Path: Orangecat B2B > Returns > Resolutions

The set of outcomes admin can assign when processing a request (e.g. Exchange, Refund, Store Credit).

Enabling Returns on Products

Path: Catalog > Products > [Edit product] > Returns tab

Set Is Returnable to Yes on each product that should be eligible for returns. The attribute defaults to No — products not explicitly enabled are silently excluded from return forms.

Configuration

Path: Orangecat B2B > Returns > Settings (or Stores > Configuration > Orangecat > Returns)

See the Configuration section for a full field reference.

Buyer Guide (Frontend)

My Returns (Account Area)

Logged-in customers see a My Returns link in their account navigation (after Orders). The page lists all their submitted return requests with Increment ID, linked Order, Status, and submission date.

URL: /returns/account/index

Submitting a New Return

  1. Navigate to My Returns and click Create New Return, or go directly to /returns/create/search.
  2. Enter your Order ID or Order Increment ID to locate the order.
  3. If the order is eligible, you are taken to the return form at /returns/create/form?order_id=<id>.
  4. For each returnable item in the order:
    • Check the item (when partial returns are enabled).
    • Enter the quantity to return (must not exceed the available balance).
    • Select a Reason from the dropdown; if "Other" is chosen, fill in the free-text field.
    • Select the Condition of the item.
  5. Optionally add a Comment (general notes for the admin).
  6. Submit the form. On success, you are redirected to the request detail page showing the new RMAxxxxxxxxx increment ID.

Orders that are not in complete or closed state, or that are outside the return window, are rejected with an explanatory error message.

Viewing a Return Request

URL: /returns/account/view?id=<request_id>

Shows the request header (status, resolution if assigned, submission date, customer comments) and a table of requested items with their reasons, conditions, and quantities.

Developer Guide

Module Structure

Orangecat/Returns/
├── Api/                          # Service contracts
│   ├── Data/                     # Data interfaces (ReturnRequest, Item, Status, Reason, Condition, Resolution)
│   └── *RepositoryInterface.php  # One repository interface per entity
├── Block/
│   ├── Account/                  # ReturnsList, ReturnView, Link (customer account)
│   ├── Adminhtml/Returns/        # Admin edit/tabs/form blocks
│   └── Create/                   # Form, Search blocks (frontend create flow)
├── Controller/
│   ├── Account/                  # Index (list), View (detail)
│   ├── Adminhtml/                # CRUD controllers for Returns, Status, Reason, Condition, Resolution
│   └── Create/                   # Search, Form, Post (submit)
├── Helper/
│   └── Data.php                  # canReturn(), isProductReturnable(), getRequestedQty()
├── Model/
│   ├── Config.php                # Typed config accessors
│   ├── Email/Sender.php          # Email dispatch for both templates
│   ├── Config/Source/            # Dropdown sources for system.xml and forms
│   ├── ResourceModel/            # One resource model + collection per entity
│   └── ReturnRequest.php         # Main model; also Status, Reason, Condition, Resolution, Item
├── Observer/
│   ├── SendNewRequestNotification.php
│   └── SendStatusUpdateEmail.php
├── Setup/Patch/Data/             # 5 data patches (statuses, reasons, conditions, resolutions, EAV attribute)
├── Ui/Component/Listing/Column/  # Actions columns for all five admin grids
├── view/
│   ├── adminhtml/                # UI component XML, layout XML, templates
│   └── frontend/
│       ├── email/                # Two email templates
│       ├── layout/               # Luma + hyva_ layout handles
│       ├── templates/            # account/* and create/* — both Luma and Hyvä variants
│       └── web/css/              # source/_module.less (Luma), hyva/module.css, breeze/_default.less
└── etc/
    ├── db_schema.xml
    ├── di.xml / adminhtml/di.xml
    ├── events.xml
    ├── email_templates.xml
    ├── acl.xml
    ├── adminhtml/{menu,routes,system}.xml
    └── frontend/routes.xml

Key Classes

Service Contracts

Interface Implementation Description
ReturnRequestRepositoryInterface ReturnRequestRepository save(), getById(), getList(), delete(), deleteById(), getItemsByRequestId(). Dispatches return_request_created and return_request_status_changed events automatically on save.
StatusRepositoryInterface StatusRepository CRUD for return statuses.
ReasonRepositoryInterface ReasonRepository CRUD for return reasons.
ConditionRepositoryInterface ConditionRepository CRUD for item conditions.
ResolutionRepositoryInterface ResolutionRepository CRUD for resolutions.

Notable Models

Class Purpose
Model\Config Typed accessors for all returns/general/* config values. Inject instead of calling ScopeConfigInterface directly.
Helper\Data canReturn(OrderInterface) — full eligibility check. isProductReturnable(OrderItemInterface) — resolves to the child simple for configurables. getRequestedQty(OrderItemInterface) — sums all past requests (all statuses).
Model\Email\Sender Wraps TransportBuilder for both notification templates.

Increment ID Format

ReturnRequestRepository::save() sets the increment ID after the first INSERT using sprintf('RMA%09d', $requestId), producing values like RMA000000001.

Observers

Class Event Action
Observer\SendNewRequestNotification return_request_created Sends returns_new_request_admin email to all configured admin notification addresses.
Observer\SendStatusUpdateEmail return_request_status_changed Sends returns_status_update_customer email to the customer when status changes.

Both observers swallow exceptions silently to prevent email failures from breaking the save flow.

Plugins

This module registers no plugins.

Email Templates

Template ID File Trigger
returns_new_request_admin view/frontend/email/returns_new_request_admin.html New return request submitted (return_request_created event). Sent to admin notification addresses.
returns_status_update_customer view/frontend/email/returns_status_update_customer.html Return request status updated (return_request_status_changed event). Sent to the customer.

ACL Resources

Resource ID Title Location
Orangecat_Returns::returns Returns Under Magento_Backend::admin
Orangecat_Returns::returns_manage Manage Return Requests Under returns
Orangecat_Returns::status Manage Statuses Under returns
Orangecat_Returns::reason Manage Reasons Under returns
Orangecat_Returns::condition Manage Conditions Under returns
Orangecat_Returns::resolution Manage Resolutions Under returns
Orangecat_Returns::config Returns Configuration Under Magento_Config::config

Adding Custom Logic

  • Override eligibility: Add a plugin on Helper\Data::canReturn() or extend Model\Config to inject additional rules (e.g., block returns for specific customer groups or SKUs).
  • React to state changes: Observe return_request_created or return_request_status_changed events — both pass the ReturnRequestInterface instance in event data.
  • Custom lookup values: Add records directly to return_status, return_reason, return_condition, or return_resolution via a data patch that depends on the corresponding InstallDefault* patch.

REST API

This module does not expose any REST API endpoints. No webapi.xml is defined.

Frontend Routes Reference

Frontend front name: returns.

Route Controller Access
GET /returns/account/index Controller\Account\Index Logged-in customers
GET /returns/account/view Controller\Account\View Logged-in customers (?id=<request_id>)
GET /returns/create/search Controller\Create\Search Logged-in customers
GET /returns/create/form Controller\Create\Form Logged-in customers (?order_id=<id>)
POST /returns/create/post Controller\Create\Post Logged-in customers

All customer-facing controllers extend Magento\Customer\Controller\AbstractAccount and redirect unauthenticated visitors to the login page. All controllers redirect to /customer/account when the module is disabled.

DevOps & Integrator Notes

Deployment Checklist

# Run inside reward shell
bin/magento module:enable Orangecat_Returns
bin/magento setup:upgrade          # runs DB schema + data patches
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush

Minimum ACL for Integration Token

An integration used to manage returns programmatically needs:

  • Orangecat_Returns::returns_manage — read/update return requests
  • Orangecat_Returns::status — read/manage statuses
  • Orangecat_Returns::reason — read/manage reasons
  • Orangecat_Returns::condition — read/manage conditions
  • Orangecat_Returns::resolution — read/manage resolutions

Disabling Without Uninstalling

bin/magento module:disable Orangecat_Returns
bin/magento setup:upgrade
bin/magento cache:flush

When disabled: all frontend routes return a redirect to /customer/account; the admin menu disappears; existing data in all return_* tables is preserved.

Data Integrity Notes

  • Deleting a sales_order record cascades to return_request and then to return_request_item (FK with ON DELETE CASCADE).
  • Deleting a status, reason, condition, or resolution from the catalog tables does not cascade to existing request records. Item reasons and conditions are stored as text snapshots at submission time; status codes are stored as raw strings. Removing a catalog entry will not break existing requests but will remove it from dropdowns for new ones.
  • return_status.code has a unique constraint. Duplicate codes from custom patches will throw a DB-level exception during setup:upgrade.
  • The is_returnable product attribute defaults to 0 (No). Products without an explicit Yes value are excluded from all return forms — verify this is set before go-live.