PHPackages                             artaza/module-rma - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. artaza/module-rma

ActiveMagento2-module

artaza/module-rma
=================

Returns (RMA) for Magento Open Source, which has no native RMA: the customer requests a return from their account, and an external system pulls it and pushes the decision back over REST.

00PHP

Since Aug 14Pushed todayCompare

[ Source](https://github.com/martinartaza/magento_rma)[ Packagist](https://packagist.org/packages/artaza/module-rma)[ RSS](/packages/artaza-module-rma/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Artaza\_Rma
===========

[](#artaza_rma)

**Returns (RMA) for Magento Open Source**, which has no native RMA — that feature only ships with Adobe Commerce.

The customer requests a return from their account (or, as a guest, from the order lookup), and the request is exposed over REST so an external system can pull it, decide, and push the outcome back. The storefront then shows the customer where their return stands.

> **This module decides nothing.** It records the request, mirrors a status, and displays it. Every real decision — accept or reject, whether the goods go back to sellable stock, whether a credit note is issued — belongs to whatever system you plug in. That keeps the money and the stock in one place instead of split across two.

It ships **ERP-agnostic**: nothing here knows about any particular back office. It was built for the [Magento 2 Connector for Odoo 19](https://www.artaza.net), but the contract is plain REST and two dispatched events, so anything can drive it.

How it works
------------

[](#how-it-works)

Two entry points, and each one dispatches an event so you can hook in without touching this module.

 ```
flowchart TB
    C(["Customer — logged-in or guest"])

    subgraph MAG["Magento · Artaza_Rma"]
        direction TB
        F["Storefront formController/Order · Controller/Guest"]
        M["RmaManagementcreateRequest · updateStatus"]
        DB[("artaza_rmaartaza_rma_item")]
        A["My Returns · admin gridbadge + message"]
        F --> M --> DB --> A
    end

    E["External system (ERP)decides"]

    C -->|"1 · requests a return"| F
    M -.->|"2 · artaza_rma_request_created"| E
    DB -->|"3 · GET /V1/rma — cursor"| E
    E -->|"4 · POST /V1/rma/{id}/status"| M
    M -.->|"5 · artaza_rma_status_updated"| E
    A -->|"6 · sees where it stands"| C
```

      Loading ### The loop, in order

[](#the-loop-in-order)

 ```
sequenceDiagram
    participant C as Customer
    participant M as Magento (Artaza_Rma)
    participant E as External system

    C->>M: Requests a return (items + qty + reason)
    Note over M: items rebuilt SERVER-SIDE from the orderonly the quantity comes from the customer
    M->>M: status = requested, dispatchartaza_rma_request_created

    loop cursor pull
        E->>M: GET /V1/rma?updated_at >= cursor (ASC)
        M-->>E: returns updated since the cursor
    end

    Note over E: the human decides:accept · reject · inspect · resolve
    E->>M: POST /V1/rma/42/status{status, adminMessage, creditAmount, couponCode}
    M->>M: save + dispatch artaza_rma_status_updated
    M-->>C: badge + message in My Returns
```

      Loading ### Status machine

[](#status-machine)

`requested` is the only status born in Magento. Every other transition arrives from outside — this module records and displays them, it never decides.

 ```
stateDiagram-v2
    [*] --> requested: customer asks in Magento
    requested --> accepted: authorised
    requested --> rejected: refused, with a reason
    accepted --> in_transit: customer ships it back
    in_transit --> inspection: received
    inspection --> approved: passed
    inspection --> fraud: seal broken / tampered
    approved --> resolved_exchange: replacement sent
    approved --> resolved_credit: credit + coupon
    fraud --> returned: item sent back
    fraud --> held: kept in quarantine
    rejected --> [*]
    resolved_exchange --> [*]
    resolved_credit --> [*]
    returned --> [*]
    held --> [*]
```

      Loading A closed list. `requested` is the only one born in Magento; everything else arrives from outside.

StatusMeaning`requested`The customer opened the return`accepted`Return authorised`rejected`Refused — carries the reason shown to the customer (**terminal**)`in_transit`The customer shipped the goods back`inspection`Received, under review`approved`Inspection passed`fraud`Seal broken or tampered with`resolved_exchange`Replacement delivered (**terminal**)`resolved_credit`Credit issued, optionally with a coupon code (**terminal**)`returned`The same item was sent back to the customer (**terminal**)`held`Kept in quarantine (**terminal**)---

What you get
------------

[](#what-you-get)

**Storefront**

- A *Request a return* button on the order view, for **logged-in customers and guests**. Guest authorisation uses Magento's own mechanism (`Sales\Helper\Guest::loadValidOrder`), so no new way in is invented.
- A *My Returns* section in the customer account: list, detail, current status and the message the operator wrote. Status is shown as a coloured badge.
- The items are rebuilt **server-side** from the order; only the quantity comes from the customer.

**Admin**

- A returns grid and a detail view.

**REST**

EndpointPurpose`GET /V1/rma?searchCriteria…`Pull returns, typically by an `updated_at` cursor`POST /V1/rma/{rmaId}/status`Push the decision back: status, message, credit amount, couponBoth are guarded by the ACL resource `Artaza_Rma::manage`.

**Events** — `artaza_rma_request_created` and `artaza_rma_status_updated`, so another module can react (send a mail, notify the ERP) without patching this one.

---

Install
-------

[](#install)

```
composer require artaza/module-rma
bin/magento module:enable Artaza_Rma
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush
```

`setup:upgrade` creates `artaza_rma` and `artaza_rma_item` (the child table cascades on delete, and `updated_at` is indexed so a cursor pull stays cheap).

To let an external system in: *System ▸ Extensions ▸ Integrations*, create one, grant **`Artaza_Rma::manage`**, activate it and use its access token as `Authorization: Bearer `. After changing the ACL, **reauthorize** the integration or its calls keep coming back `401`.

---

Driving it
----------

[](#driving-it)

Pull what changed since your cursor:

```
GET /rest/all/V1/rma?searchCriteria[filter_groups][0][filters][0][field]=updated_at
                    &searchCriteria[filter_groups][0][filters][0][value]=2026-08-01 00:00:00
                    &searchCriteria[filter_groups][0][filters][0][condition_type]=gteq
                    &searchCriteria[sortOrders][0][field]=updated_at
                    &searchCriteria[sortOrders][0][direction]=ASC
```

Sort **ascending** by `updated_at`: that is what makes the cursor self-healing — a run that dies half-way resumes from the last record it actually absorbed instead of skipping ahead.

Push the decision back:

```
POST /rest/all/V1/rma/42/status

{
  "status": "resolved_credit",
  "resolution": "credit",
  "adminMessage": "Approved. Store credit available for your next purchase.",
  "creditAmount": 43558.79,
  "couponCode": "RMA42-XY7Q",
  "odooReference": "NC-B 0001-00000123"
}
```

Everything except `status` is optional. `adminMessage` is what the customer reads, so a rejection should always carry one. Re-sending the same status is idempotent — the natural key is the RMA's `increment_id`.

---

Theme notes
-----------

[](#theme-notes)

The storefront templates target **Hyvä**. On a Luma-based theme the controllers, the model and the REST API work unchanged, but you will want to restyle the templates. Module styles live in `view/frontend/web/css/module.css`, not in the theme's build, so nothing is lost on a `static-content:deploy`.

Requirements
------------

[](#requirements)

- Magento **2.4.x** (Open Source or Commerce)
- PHP **8.1+**

License
-------

[](#license)

[OSL-3.0](LICENSE.txt) — the same license as the Magento core.

Sebastian Artaza · [artaza.net](https://www.artaza.net) ·

###  Health Score

20

—

LowBetter than 12% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/7785503?v=4)[Sebastián Martín, Artaza Saade](/maintainers/martinartaza)[@martinartaza](https://github.com/martinartaza)

### Embed Badge

![Health badge](/badges/artaza-module-rma/health.svg)

```
[![Health](https://phpackages.com/badges/artaza-module-rma/health.svg)](https://phpackages.com/packages/artaza-module-rma)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
