{"id":51113862,"url":"https://github.com/mage-os/module-rma","last_synced_at":"2026-06-24T20:00:57.922Z","repository":{"id":338237370,"uuid":"1157078608","full_name":"mage-os/module-rma","owner":"mage-os","description":"Return Merchandise Authorization (RMA) for Mage-OS / Magento","archived":false,"fork":false,"pushed_at":"2026-05-12T05:20:27.000Z","size":278,"stargazers_count":15,"open_issues_count":2,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T07:25:04.910Z","etag":null,"topics":["adobecommerce","ecommerce","extension","mage-os","mage-os-lab","magento","magento2","returns","rma"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mage-os.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-13T12:02:04.000Z","updated_at":"2026-05-12T05:20:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mage-os/module-rma","commit_stats":null,"previous_names":["mage-os-lab/module-rma","mage-os/module-rma"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/mage-os/module-rma","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mage-os%2Fmodule-rma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mage-os%2Fmodule-rma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mage-os%2Fmodule-rma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mage-os%2Fmodule-rma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mage-os","download_url":"https://codeload.github.com/mage-os/module-rma/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mage-os%2Fmodule-rma/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34747387,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adobecommerce","ecommerce","extension","mage-os","mage-os-lab","magento","magento2","returns","rma"],"created_at":"2026-06-24T20:00:56.986Z","updated_at":"2026-06-24T20:00:57.906Z","avatar_url":"https://github.com/mage-os.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MageOS_RMA\n\nReturn Merchandise Authorization (RMA) module for Magento / MageOS.\n\n## Installation\n\n### Via Composer\n\n```bash\ncomposer require mage-os/module-rma\n```\n\n### Enable the module\n\n```bash\nbin/magento module:enable MageOS_RMA\nbin/magento setup:upgrade\nbin/magento cache:flush\n```\n\n\u003e The `setup:upgrade` command creates the database tables and inserts default data (status, reason, resolution type, item condition) via Data Patches.\n\n## Configuration\n\nModule settings are located at **Stores \u003e Configuration \u003e Sales \u003e RMA - Return Management**.\n\n### General\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| Enable RMA | Yes/No | No | Enable or disable the RMA feature (scope: website) |\n| Increment ID Prefix | Text | `RMA-` | Prefix for the return increment ID (e.g. `RMA-000001`) |\n| Return Period (Days) | Numeric | `30` | Number of days after order placement within which a return can be requested |\n\n### Policy\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| Auto-Approve Returns | Yes/No | No | When enabled, new return requests are automatically approved |\n| Allowed Order Statuses | Multiselect | `complete` | Only orders with these statuses can have a return request |\n\n### Email Notifications\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| Email Sender | Select | `General Contact` | Sender identity for RMA emails |\n| New RMA Email Template (Customer) | Select | — | Email template sent to the customer when a new return is created |\n| Status Change Email Template (Customer) | Select | — | Email template sent to the customer on return status change |\n| New RMA Email Template (Admin) | Select | — | Email template sent to the admin when a new return is created |\n| Admin Notification Email | Text (email) | — | Email address to receive admin notifications about returns |\n\n### Attachments\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| Allowed File Extensions | Text | `jpg,jpeg,png,gif,webp,mp4,mov,pdf,doc,docx,zip` | Comma-separated list of allowed file extensions |\n| Maximum File Size (MB) | Numeric | `10` | Maximum allowed file size in megabytes per single file |\n| Maximum Files Per Upload | Numeric | `5` | Maximum number of files allowed per RMA creation or comment |\n\n## Admin Area\n\n### Menu\n\nModule entries are located under **Sales \u003e RMA**:\n\n- **RMA Requests** — main grid with all return requests\n- **Statuses** — manage the return lifecycle statuses\n- **Reasons** — manage return reasons\n- **Resolution Types** — manage resolution types (refund, replacement, etc.)\n- **Item Conditions** — manage item conditions (opened, sealed, etc.)\n\n### Creating an RMA from admin\n\n1. Go to **Sales \u003e RMA \u003e RMA Requests**, click **Add New RMA**\n2. Search and select an order in the **Order** field (only shows orders from websites with RMA enabled)\n3. Customer fields (name, email) and store are automatically filled from the order\n4. Select reason and resolution type\n5. In the **Items to Return** section, select the order items to include in the return, specifying quantity and condition for each\n6. Save — the system automatically generates an increment ID (e.g. `RMA-000001`) and sends notification emails\n\n### Edit RMA\n\nIn edit mode, the form shows order and item information as read-only. You can modify status, reason, resolution type and admin notes.\n\nChanging the status automatically sends a notification email to the customer.\n\n### Attachments\n\nThe RMA edit page displays a unified **Attachments** section above the comments timeline. This section shows all attachments associated with the RMA, regardless of whether they were uploaded at RMA creation or within a comment.\n\n- Each attachment has a **download** link and a **delete** button\n- Deleting an attachment from the unified section also removes it from the corresponding comment in the timeline (and vice versa)\n- The section updates dynamically when new comments with attachments arrive via polling\n\n### Comments / Chat\n\nThe RMA edit page includes a **Comments** section that enables communication between admin and customer.\n\n- Admin can write comments visible to the customer or **internal notes** (not visible to the customer) via the \"Visible to Customer\" checkbox\n- Internal notes display an **Internal Note** badge in the timeline\n- Admin can attach files to comments via drag \u0026 drop or file picker\n- Comments update in real time via AJAX polling with progressive backoff (10s → 30s → 60s)\n- Polling pauses when the browser tab is not visible and resumes when it becomes active again\n- Messages can be sent with **Ctrl+Enter** in addition to the button\n\n## Frontend — Customer\n\n### My Returns\n\nA **My Returns** link appears in the customer account sidebar. The page shows all of the customer's returns sorted by date descending, with pagination.\n\nTable columns:\n\n| Column | Description |\n|---|---|\n| RMA # | Return increment ID (e.g. `RMA-000001`) |\n| Order # | Associated order increment ID |\n| Status | Current return status (translated per store view) |\n| Created At | Creation date |\n| Action | **View** link to the detail page |\n\nThe **Request Return** button at the top leads to the creation form.\n\n### RMA Detail\n\nThe page displays return information and the items table:\n\n- **General information**: increment ID, order, status, reason, preferred resolution, creation and update dates\n- **Items table**: product name, SKU, requested quantity, item condition\n- **Attachments section**: all attachments uploaded across the RMA lifecycle (creation and comments) displayed in a unified list with download links. The section is always present and updates dynamically when new comments with attachments arrive\n- All labels (status, reason, resolution, condition) are translated according to the current store view\n\n#### Customer Comments / Chat\n\nBelow the detail section there is a comments area that allows the customer to communicate with support:\n\n- The customer only sees comments marked as visible (admin internal notes are not shown)\n- Admin messages display a **Support** badge\n- Customers can attach files to comments via drag \u0026 drop or file picker\n- Attachments are also shown inline within each comment for context\n- Same real-time polling mechanism as the admin side (backoff 10s → 30s → 60s, pauses on hidden tab)\n- Submit with **Ctrl+Enter** or the **Send** button\n\n### Creating an RMA from the customer area\n\n1. From **My Returns**, click **Request Return**\n2. Select an order from the dropdown (only eligible orders are shown — see Eligibility section)\n3. On order change, available items are loaded via AJAX\n4. For each item: check the checkbox, specify quantity and condition\n5. Select reason and preferred resolution\n6. Optionally attach files via drag \u0026 drop or file picker (allowed extensions and size limits are configurable — see Attachments configuration)\n7. Click **Submit Return Request**\n\nIf arriving from the **Request Return** button on the order detail page, the order is pre-selected and the dropdown is disabled.\n\n### \"Request Return\" button on order detail\n\nOn the customer order detail page (**Sales \u003e My Orders \u003e View Order**), a **Request Return** button appears in the action bar if the order is eligible for a return. Clicking it leads to the creation form with the order pre-selected.\n\n## Frontend — Guest\n\nGuests (orders without an account) can request a return from the guest order detail page:\n\n1. Access the guest order detail via **Orders and Returns** (order number + email/ZIP)\n2. If the order is eligible, the **Request Return** button appears\n3. The form is identical to the logged-in customer form, but without the order dropdown (the order is already determined)\n4. After submission, the return is created with `customer_id = null`\n\n\u003e Guests do not have a \"My Returns\" section — they can only create returns from the order detail page.\n\n## Order Eligibility for RMA\n\nAn order is eligible for a return request if **all** of these conditions are met:\n\n1. **RMA enabled** — the module is enabled for the order's website (`isEnabled()`)\n2. **Allowed status** — the order status is among those configured in \"Allowed Order Statuses\"\n3. **Return period** — the order date is within the period configured in \"Return Period (Days)\". If the period is `0`, returns are always allowed (no time limit)\n4. **Available items** — the order has at least one item with remaining returnable quantity (qty ordered − qty already requested in other RMAs \u003e 0). Virtual items, downloadable items, and parent items of configurable/bundle products are excluded\n\nThe logic is centralized in the `Service\\OrderEligibility` service with the following methods:\n\n| Method | Description |\n|---|---|\n| `isOrderEligible(OrderInterface $order): bool` | Checks all 4 conditions |\n| `getEligibleItems(OrderInterface $order): array` | Returns items with available quantity |\n| `getCustomerEligibleOrders(int $customerId, int $storeId): Collection` | Customer orders matching conditions 1-3 |\n\n## Repositories and Service Contracts\n\nEach entity exposes a repository with interfaces in `Api/`:\n\n| Interface | Implementation | Methods |\n|---|---|---|\n| `RMARepositoryInterface` | `Model\\RMARepository` | `get`, `getByIncrementId`, `save`, `delete`, `deleteById`, `getList` |\n| `StatusRepositoryInterface` | `Model\\StatusRepository` | `get`, `save`, `delete`, `deleteById`, `getList` |\n| `ReasonRepositoryInterface` | `Model\\ReasonRepository` | `get`, `save`, `delete`, `deleteById`, `getList` |\n| `ResolutionTypeRepositoryInterface` | `Model\\ResolutionTypeRepository` | `get`, `save`, `delete`, `deleteById`, `getList` |\n| `ItemConditionRepositoryInterface` | `Model\\ItemConditionRepository` | `get`, `save`, `delete`, `deleteById`, `getList` |\n| `ItemRepositoryInterface` | `Model\\ItemRepository` | `get`, `save`, `delete`, `deleteById`, `getList` |\n| `CommentRepositoryInterface` | `Model\\CommentRepository` | `get`, `save`, `delete`, `deleteById`, `getList` |\n\nAll `getList` methods support `SearchCriteriaInterface` for filtering, sorting and pagination.\n\n## Business Events\n\nThe module dispatches custom events in `RMARepository::save()` to allow other modules to react to return lifecycle changes.\n\n### How dispatching works\n\n1. **Creation**: when a new RMA is saved, `rma_created_after` fires\n2. **Status change**: when the status changes, **two** events fire in sequence:\n   - `rma_status_change_after` (generic, fires on any transition)\n   - A specific semantic event based on the new status (e.g. `rma_approved_after`)\n\n### Events table\n\n| Event | When it fires | Available data |\n|---|---|---|\n| `rma_created_after` | New RMA created | `rma` |\n| `rma_status_change_after` | Any status change | `rma`, `old_status_id`, `new_status_id` |\n| `rma_approved_after` | Status \u0026rarr; Approved | `rma`, `old_status_id`, `new_status_id` |\n| `rma_rejected_after` | Status \u0026rarr; Rejected | `rma`, `old_status_id`, `new_status_id` |\n| `rma_shipped_by_customer_after` | Status \u0026rarr; Shipped by Customer | `rma`, `old_status_id`, `new_status_id` |\n| `rma_received_after` | Status \u0026rarr; Received by Admin | `rma`, `old_status_id`, `new_status_id` |\n| `rma_canceled_after` | Status \u0026rarr; Canceled by Customer | `rma`, `old_status_id`, `new_status_id` |\n| `rma_resolved_after` | Status \u0026rarr; Resolved | `rma`, `old_status_id`, `new_status_id` |\n\n### Example: Observer to book a carrier when a return is approved\n\n**`etc/events.xml`** in your custom module:\n\n```xml\n\u003cconfig xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Event/etc/events.xsd\"\u003e\n    \u003cevent name=\"rma_approved_after\"\u003e\n        \u003cobserver name=\"my_module_book_carrier\"\n                  instance=\"MyVendor\\MyModule\\Observer\\BookCarrierPickup\"/\u003e\n    \u003c/event\u003e\n\u003c/config\u003e\n```\n\n**`Observer/BookCarrierPickup.php`**:\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace MyVendor\\MyModule\\Observer;\n\nuse Magento\\Framework\\Event\\Observer;\nuse Magento\\Framework\\Event\\ObserverInterface;\nuse MageOS\\RMA\\Api\\Data\\RMAInterface;\n\nclass BookCarrierPickup implements ObserverInterface\n{\n    public function execute(Observer $observer): void\n    {\n        /** @var RMAInterface $rma */\n        $rma = $observer-\u003egetData('rma');\n        $oldStatusId = $observer-\u003egetData('old_status_id');\n        $newStatusId = $observer-\u003egetData('new_status_id');\n\n        // Your carrier booking logic here\n        // $rma-\u003egetCustomerEmail(), $rma-\u003egetOrderId(), etc.\n    }\n}\n```\n\n### Example: Generic observer to log all status changes\n\n```xml\n\u003cevent name=\"rma_status_change_after\"\u003e\n    \u003cobserver name=\"my_module_log_status_change\"\n              instance=\"MyVendor\\MyModule\\Observer\\LogStatusChange\"/\u003e\n\u003c/event\u003e\n```\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace MyVendor\\MyModule\\Observer;\n\nuse Magento\\Framework\\Event\\Observer;\nuse Magento\\Framework\\Event\\ObserverInterface;\nuse Psr\\Log\\LoggerInterface;\n\nclass LogStatusChange implements ObserverInterface\n{\n    public function __construct(\n        protected readonly LoggerInterface $logger\n    ) {\n    }\n\n    public function execute(Observer $observer): void\n    {\n        $rma = $observer-\u003egetData('rma');\n\n        $this-\u003elogger-\u003einfo('RMA status changed', [\n            'rma_id' =\u003e $rma-\u003egetEntityId(),\n            'increment_id' =\u003e $rma-\u003egetIncrementId(),\n            'old_status_id' =\u003e $observer-\u003egetData('old_status_id'),\n            'new_status_id' =\u003e $observer-\u003egetData('new_status_id'),\n        ]);\n    }\n}\n```\n\n## REST API\n\nAll endpoints require an admin integration token (`Authorization: Bearer \u003ctoken\u003e`) and are protected by ACL resources.\n\n### RMA Entity\n\n| Method | Endpoint | ACL | Description |\n|---|---|---|---|\n| `GET` | `/V1/rma/:entityId` | `MageOS_RMA::rma_manage` | Get RMA by ID |\n| `GET` | `/V1/rma/increment-id/:incrementId` | `MageOS_RMA::rma_manage` | Get RMA by increment ID |\n| `POST` | `/V1/rma` | `MageOS_RMA::rma_manage` | Create RMA |\n| `PUT` | `/V1/rma/:entityId` | `MageOS_RMA::rma_manage` | Update RMA |\n| `DELETE` | `/V1/rma/:entityId` | `MageOS_RMA::rma_manage` | Delete RMA |\n| `GET` | `/V1/rma/search` | `MageOS_RMA::rma_manage` | Search RMAs (SearchCriteria) |\n\n### RMA Items\n\nItems are managed as a **sub-resource** of the parent RMA, following the same pattern as Magento core (e.g. `OrderInterface` / `OrderItemRepositoryInterface`). The `GET /V1/rma/:entityId` endpoint returns the RMA header only — to retrieve items, use the dedicated endpoints below.\n\n| Method | Endpoint | ACL | Description |\n|---|---|---|---|\n| `GET` | `/V1/rma/:rmaId/items` | `MageOS_RMA::rma_manage` | List items for an RMA (SearchCriteria) |\n| `POST` | `/V1/rma/:rmaId/items` | `MageOS_RMA::rma_manage` | Add item to an RMA |\n| `DELETE` | `/V1/rma/:rmaId/items/:itemId` | `MageOS_RMA::rma_manage` | Remove item from an RMA |\n\n### RMA Comments\n\nComments are managed as a **sub-resource** of the parent RMA, same as items above.\n\n| Method | Endpoint | ACL | Description |\n|---|---|---|---|\n| `GET` | `/V1/rma/:rmaId/comments` | `MageOS_RMA::rma_manage` | List comments for an RMA (SearchCriteria) |\n| `POST` | `/V1/rma/:rmaId/comments` | `MageOS_RMA::rma_manage` | Add comment to an RMA |\n\n### Lookup Entities\n\nEach lookup entity (status, reason, resolution type, item condition) exposes full CRUD endpoints.\n\n#### Statuses\n\n| Method | Endpoint | ACL |\n|---|---|---|\n| `GET` | `/V1/rma/status/:entityId` | `MageOS_RMA::rma_status` |\n| `POST` | `/V1/rma/status` | `MageOS_RMA::rma_status` |\n| `PUT` | `/V1/rma/status/:entityId` | `MageOS_RMA::rma_status` |\n| `DELETE` | `/V1/rma/status/:entityId` | `MageOS_RMA::rma_status` |\n| `GET` | `/V1/rma/status/search` | `MageOS_RMA::rma_status` |\n\n#### Reasons\n\n| Method | Endpoint | ACL |\n|---|---|---|\n| `GET` | `/V1/rma/reason/:entityId` | `MageOS_RMA::rma_reason` |\n| `POST` | `/V1/rma/reason` | `MageOS_RMA::rma_reason` |\n| `PUT` | `/V1/rma/reason/:entityId` | `MageOS_RMA::rma_reason` |\n| `DELETE` | `/V1/rma/reason/:entityId` | `MageOS_RMA::rma_reason` |\n| `GET` | `/V1/rma/reason/search` | `MageOS_RMA::rma_reason` |\n\n#### Resolution Types\n\n| Method | Endpoint | ACL |\n|---|---|---|\n| `GET` | `/V1/rma/resolution-type/:entityId` | `MageOS_RMA::rma_resolution_type` |\n| `POST` | `/V1/rma/resolution-type` | `MageOS_RMA::rma_resolution_type` |\n| `PUT` | `/V1/rma/resolution-type/:entityId` | `MageOS_RMA::rma_resolution_type` |\n| `DELETE` | `/V1/rma/resolution-type/:entityId` | `MageOS_RMA::rma_resolution_type` |\n| `GET` | `/V1/rma/resolution-type/search` | `MageOS_RMA::rma_resolution_type` |\n\n#### Item Conditions\n\n| Method | Endpoint | ACL |\n|---|---|---|\n| `GET` | `/V1/rma/item-condition/:entityId` | `MageOS_RMA::rma_item_condition` |\n| `POST` | `/V1/rma/item-condition` | `MageOS_RMA::rma_item_condition` |\n| `PUT` | `/V1/rma/item-condition/:entityId` | `MageOS_RMA::rma_item_condition` |\n| `DELETE` | `/V1/rma/item-condition/:entityId` | `MageOS_RMA::rma_item_condition` |\n| `GET` | `/V1/rma/item-condition/search` | `MageOS_RMA::rma_item_condition` |\n\n## GraphQL API\n\nThe module provides GraphQL queries and mutations for headless frontend integration.\n\n### Queries\n\n#### customerReturns\n\nReturns a paginated list of returns for the logged-in customer. Requires customer token.\n\n```graphql\nquery {\n    customerReturns(pageSize: 10, currentPage: 1) {\n        items {\n            rma_id\n            increment_id\n            order_number\n            status { code label }\n            created_at\n        }\n        total_count\n        page_info { page_size current_page total_pages }\n    }\n}\n```\n\n#### customerReturn\n\nReturns a single return by ID for the logged-in customer. Requires customer token.\n\n```graphql\nquery {\n    customerReturn(rma_id: 1) {\n        rma_id\n        increment_id\n        order_number\n        status { id code label }\n        reason { id code label }\n        resolution_type { id code label }\n        items {\n            product_name\n            product_sku\n            qty_requested\n            condition { code label }\n        }\n        comments {\n            comment_id\n            author_type\n            author_name\n            comment\n            created_at\n        }\n    }\n}\n```\n\n#### guestReturn\n\nReturns a single return for a guest order, authenticated by order number and email.\n\n```graphql\nquery {\n    guestReturn(order_number: \"000000001\", email: \"guest@example.com\", rma_id: 1) {\n        rma_id\n        increment_id\n        status { code label }\n        items { product_name qty_requested }\n    }\n}\n```\n\n#### returnComments\n\nReturns a paginated list of customer-visible comments for a return. Requires customer token.\n\n```graphql\nquery {\n    returnComments(rma_id: 1, pageSize: 50, currentPage: 1) {\n        items {\n            comment_id\n            author_type\n            author_name\n            comment\n            created_at\n        }\n        total_count\n    }\n}\n```\n\n### Mutations\n\n#### createCustomerReturn\n\nCreates a return for a customer order. Requires customer token.\n\n```graphql\nmutation {\n    createCustomerReturn(input: {\n        order_id: 1\n        reason_id: 1\n        resolution_type_id: 1\n        items: [\n            { order_item_id: 1, qty_requested: 1, condition_id: 1 }\n        ]\n    }) {\n        return {\n            rma_id\n            increment_id\n            status { code label }\n        }\n    }\n}\n```\n\n#### createGuestReturn\n\nCreates a return for a guest order, authenticated by order number and email.\n\n```graphql\nmutation {\n    createGuestReturn(input: {\n        order_number: \"000000001\"\n        email: \"guest@example.com\"\n        reason_id: 1\n        resolution_type_id: 1\n        items: [\n            { order_item_id: 1, qty_requested: 1, condition_id: 1 }\n        ]\n    }) {\n        return {\n            rma_id\n            increment_id\n            status { code label }\n        }\n    }\n}\n```\n\n#### addReturnComment\n\nAdds a comment to a return. Requires customer token.\n\n```graphql\nmutation {\n    addReturnComment(input: {\n        rma_id: 1\n        comment: \"When will my refund be processed?\"\n    }) {\n        comment_id\n        author_type\n        author_name\n        comment\n        created_at\n    }\n}\n```\n\n### Authentication\n\n| Operation | Auth |\n|---|---|\n| `customerReturns`, `customerReturn`, `returnComments`, `addReturnComment` | Customer token (`Authorization: Bearer \u003ccustomer_token\u003e`) |\n| `createCustomerReturn` | Customer token — ownership of the order is verified |\n| `guestReturn`, `createGuestReturn` | No token — authenticated by `order_number` + `email` |\n\n### Types\n\nAll lookup fields (status, reason, resolution_type, condition) return a `ReturnLookupValue` with `id`, `code` and `label`. The `label` is store-localized based on the current store view header (`Store`).\n\n## Hyvä Theme Compatibility\n\nThis module is **Hyvä-ready** out of the box. It includes both Luma (default) and Hyvä templates within the same module — no separate compatibility module is needed.\n\n### How it works\n\nThe module ships `hyva_` prefixed layout XML files alongside the standard Luma layout files. When running under a Hyvä theme, Magento automatically processes these `hyva_*` handles **after** the regular handles. The Hyvä layouts remove the Luma blocks and replace them with blocks pointing to Alpine.js + Tailwind CSS templates.\n\n### What's included\n\n| Luma layout | Hyvä layout | Purpose |\n|---|---|---|\n| `customer_account.xml` | `hyva_customer_account.xml` | \"My Returns\" sidebar link |\n| `rma_customer_history.xml` | `hyva_rma_customer_history.xml` | Returns list page |\n| `rma_customer_view.xml` | `hyva_rma_customer_view.xml` | Return detail + comments |\n| `rma_customer_create.xml` | `hyva_rma_customer_create.xml` | Create return form |\n| `rma_guest_create.xml` | `hyva_rma_guest_create.xml` | Guest return form |\n| `sales_order_view.xml` | `hyva_sales_order_view.xml` | \"Request Return\" button (customer) |\n| `sales_guest_view.xml` | `hyva_sales_guest_view.xml` | \"Request Return\" button (guest) |\n\n### Templates\n\nHyvä templates are located under `view/frontend/templates/hyva/` and use:\n\n- **Alpine.js** for all interactive behavior (AJAX item loading, comments polling, form validation)\n- **Tailwind CSS** utility classes for styling\n- **Native `fetch()`** instead of jQuery `$.ajax()`\n- **`hyva.getCookie('form_key')`** for CSRF token retrieval instead of jQuery Cookie\n\nThe comments system implements the same smart polling mechanism as the Luma version (10s → 30s → 60s backoff, Visibility API pause/resume) using plain Alpine.js.\n\n### Tailwind Build\n\nHyvä automatically discovers and purges Tailwind classes from module templates. After installing or updating this module, rebuild Tailwind from your Hyvä theme:\n\n```bash\ncd app/design/frontend/\u003cVendor\u003e/\u003ctheme\u003e/web/tailwind\nnpm run build\n```\n\n## Status Codes\n\nStatus codes are defined as constants in `Model\\RMA\\StatusCodes`:\n\n| Constant | Value |\n|---|---|\n| `NEW_REQUEST` | `new_request` |\n| `NEED_DETAILS` | `need_details` |\n| `APPROVED` | `approved` |\n| `REJECTED` | `rejected` |\n| `SHIPPED_BY_CUSTOMER` | `shipped_by_customer` |\n| `RECEIVED_BY_ADMIN` | `received_by_admin` |\n| `CANCELED_BY_CUSTOMER` | `canceled_by_customer` |\n| `RESOLVED` | `resolved` |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmage-os%2Fmodule-rma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmage-os%2Fmodule-rma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmage-os%2Fmodule-rma/lists"}