=== Return OS — Returns, Refunds & RMA for WooCommerce ===
Contributors: returnos
Tags: returns, refunds, rma, return-management, woocommerce
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.5.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Returns, refunds and the EU withdrawal button for WooCommerce, guest checkout included. Free, works standalone.

== Description ==

Return OS turns "I'd like to send this back" into a workflow instead of an email thread.

Customers request a return from their own account, or, if they checked out as a guest, from a link we email them — picking the exact items and quantities, giving a reason, optionally attaching a photo. You see every request in one queue in wp-admin, approve or decline it, and issue a correct partial refund through WooCommerce in a single click.

**It is free, it is complete, and it works on its own.** No account, no key, no trial that expires. There is an optional connection to Return OS, a paid hosted service, described under External services below; nothing on this page depends on it.

= What you get =

* **A Returns tab on My Account.** Customers pick line items and quantities, choose a reason from a list you control, add a note and a photo.
* **Guest returns.** Most WooCommerce orders are guest checkouts. A "Start a return" page (created for you) asks for the order number and email and sends a return link to the address on the order, so only the person who placed it can use it.
* **The EU withdrawal button.** EU law has required a "Withdraw from contract here" function since 19 June 2026: no login, no reason, two steps, and an emailed confirmation with the customer's statement and the time. It is built in, on the page footer, in order emails and on orders, and on by default for stores based in the EU.
* **Return windows and rules.** Set how many days after completion a return is allowed, which order statuses qualify, and which product categories are excluded.
* **RMA numbers.** Every request gets an `RMA-{order}-{sequence}` code that customers and staff can quote.
* **One queue in wp-admin.** Filter by status, search by RMA, email or order number, see how old each request is.
* **Refunds that are actually correct.** Partial-quantity refunds on discounted, multi-tax orders come out right, because the amounts are read from WooCommerce's own stored line figures rather than recalculated. Shipping is refunded only when you tick it.
* **Cash on delivery and bank transfer are first-class.** When a gateway cannot refund through its API, the refund is still recorded against the order and the request moves to a "pay this customer" task, with the customer told how long it will take. It does not just show you an error.
* **Native WooCommerce emails.** Four emails — request received, approved, declined, and a new-request alert for you — as real `WC_Email` classes, so they inherit your store's branding and appear under WooCommerce → Settings → Emails like everything else.
* **Nothing happens twice.** Approving is guarded at the database level, and a refund already recorded for a request is never recorded again, however many times the button is pressed.
* **HPOS ready.** Works with both High-Performance Order Storage and the classic posts table.
* **Privacy tools.** Return requests appear in WordPress's own personal-data export, and erasing a customer anonymises their requests while keeping the financial record.

= What it does not do =

Being straight about this up front, because the alternative is a one-star review:

* No exchanges or store credit in the plugin on its own. It handles returns and refunds; exchanges and store credit come with the optional Return OS service.
* No shipping-label generation. You can write return instructions and a return address into the approval email.
* Multi-currency switchers, WooCommerce Subscriptions and Product Bundles change what an order means; the Status tab flags them, and refund amounts on affected orders should be checked by hand.

= Privacy =

**Used on its own, the plugin makes no external connections.** Nothing about your store, your orders or your customers is sent anywhere. Everything lives in your own database and uploads folder. The optional connection to Return OS is described under External services.

== External services ==

This plugin can connect your store to **Return OS** ([return.alze.agency](https://return.alze.agency/)), a paid hosted service by the same authors. It adds a branded returns page that customers can use without a WordPress account, plus exchanges, store credit and reporting across stores. **The connection is optional. Nothing is sent until you press "Connect to Return OS" on the plugin's Connect tab.**

Once you connect, data moves as follows:

* **When you connect**, the plugin sends `return.alze.agency` a newly created WooCommerce API key (consumer key and secret, read and write, named "Return OS" and revocable under WooCommerce → Settings → Advanced → REST API), a shared secret used to sign everything between the two, your store's REST address, and the plugin version. It also creates a WooCommerce webhook for order updates.
* **Every hour**, and when you save the plugin's settings, a check-in sends your store ID and address, the plugin version, the webhook's status, and an environment report: WooCommerce, WordPress and PHP versions, currency and tax settings, whether coupons are enabled, whether the EU withdrawal button is on, your active payment gateways and whether each supports refunds, and active plugins known to affect refund amounts.
* **Whenever an order changes**, WooCommerce's webhook sends Return OS its standard order record. That includes the customer's name, email address, phone number, billing and shipping addresses, the IP address and browser recorded at checkout, the order contents and the totals.
* **When a customer starts a return from My Account**, their browser posts the order number and billing email, signed with the shared secret, to your store's returns page at `portal.return.alze.agency`, so they are not asked to type them again.
* **Return OS calls back into your store** using the API key: it reads orders, issues the refunds you approve, and, if you turn them on, creates store-credit coupons and replacement orders. It also calls this plugin's own signed endpoints under `/wp-json/returnos/v1/` to look up an order for a customer on the returns page, and every so often to check that it can still reach them.

Deactivating the plugin stops the hourly check-in and pauses the webhook. Disconnecting deletes the API key and webhook and tells Return OS to erase the stored credentials. Deleting the plugin removes the key, the webhook and the credentials from your site, so the copy Return OS holds no longer works. Your return requests stay on your site either way.

* [Return OS terms of service](https://return.alze.agency/terms)
* [Return OS privacy policy](https://return.alze.agency/privacy)
* [Return OS data processing agreement](https://return.alze.agency/dpa)

== Installation ==

1. Install and activate the plugin.
2. Go to **WooCommerce → Returns → Settings** and set your return window, reasons and return address. Activation creates a "Start a return" page for guest returns and the EU withdrawal form; link it from your menu if you like.
3. Check the **Status** tab — it flags anything in your environment that would get in the way, such as Plain permalinks.

Requires WooCommerce 8.5 or newer, and pretty permalinks (any Permalinks setting other than "Plain").

== Frequently Asked Questions ==

= How do customers request a return in WooCommerce? =

Once the plugin is active, a **Returns** tab appears in My Account, and eligible orders get a "Request return" button on both the orders list and the order detail page. Customers choose the items and quantities they want to send back, pick a reason, and submit.

= Does this work with HPOS (High-Performance Order Storage)? =

Yes. The plugin declares HPOS compatibility and only ever reads orders through WooCommerce's own CRUD methods, so both storage modes behave identically.

= Can I refund to the original payment method? =

Yes, whenever the gateway supports it. Approving a return with "Record the refund now" ticked sends the refund through the gateway exactly as WooCommerce's own refund button does.

= What about cash on delivery, cheque or bank transfer orders? =

Those gateways cannot refund through an API, so the refund is recorded against the order for your bookkeeping and the request moves to "Awaiting payout". You transfer the money and click **Mark as paid**. The customer's approval email tells them to expect it within the number of business days you set.

= Are the refund amounts right on orders with coupons? =

Yes, and this is the part most worth checking in any returns plugin. Amounts come from each line item's stored after-discount total and its per-rate tax figures, scaled by the quantity being returned. A plugin that reads the pre-discount subtotal instead will over-refund every couponed order.

= Can a customer return part of an order, then part of it again later? =

Yes. Each request only offers the quantities that have not already been requested, and refund amounts are always calculated against what is still refundable on the live order.

= Do customers need an account? =

No. Customers with an account use the Returns tab on My Account. Guests use the "Start a return" page: they enter their order number and email, and a return link goes to the address on the order, valid for 24 hours. The EU withdrawal form on the same page needs no account either.

= Where is the EU withdrawal button? =

At the foot of every page, in the processing and completed order emails, and under the order on the order-received and view-order pages. It leads to a form on the "Start a return" page. Turn it on or off under WooCommerce → Returns → Settings. If you already link it from your own footer menu, remove the automatic footer link with `add_filter( 'returnos_withdrawal_footer_link', '__return_false' );`.

= Can I change the emails? =

Every email is a standard `WC_Email`, so subjects, headings and extra content are editable under **WooCommerce → Settings → Emails**. Templates can be overridden from your theme in `yourtheme/woocommerce/emails/`.

= Does the plugin send data anywhere? =

Not unless you ask it to. On its own it makes no outbound connections of any kind. If you connect it to the optional Return OS service from the Connect tab, it sends the data listed under External services above — and not before. You can disconnect at any time.

= What happens to my data if I delete the plugin? =

Your return requests and settings are removed only if you tick **Delete data on uninstall** in the settings first. Return requests are part of your store's financial history, so they are kept by default. A connection to Return OS, if you made one, is always removed: the API key, the webhook and the stored credentials.

== Screenshots ==

1. Every return request in one queue, filtered by status.
2. Reviewing a request: exact per-line refund amounts before anything is issued.
3. The customer picks items, quantities, a reason and a photo.
4. The customer's own returns, with RMA numbers and live status.
5. The Status tab, flagging anything in the environment that would get in the way.

== Changelog ==

= 1.5.0 =
* Guest returns: a "Start a return" page, created on activation and on update, where a customer without an account asks for a return link by order number and email. The link goes to the address on the order and works for 24 hours.
* The EU withdrawal button, required since 19 June 2026: a "Withdraw from contract here" link on every page, in order emails and on orders, a two-step form that needs no login, and an emailed confirmation with the statement and the time. On by default for stores based in the EU.
* Fixed: the Returns endpoint was registered on the site root, so a page at /returns/, usually the store's returns policy, showed the home page instead.
* Fixed: a customer email sent in the same request as an earlier one could go to the earlier customer's address.
* Order numbers pasted as "#1234" are now found.
* Connected stores whose hosted portal is closed can manage the requests that arrive in the plugin again, instead of seeing them read-only.
* The personal-data exporter and eraser also find requests on the customer's own orders that were filed under a different billing email.
* WooCommerce Multilingual is reported as a multi-currency plugin only when its multi-currency mode is on.

= 1.4.1 =
* Fixed: My Account's Returns tab matched the customer's login email against the billing email on each request. Customers whose two addresses differ saw no returns, and an account could see returns someone else had placed under its address. It now lists the returns on the customer's own orders.
* Deactivating the plugin now pauses its order webhook, so no order data leaves a switched-off plugin. Reactivating turns the webhook and the hourly check-in back on; before, the check-in never restarted.
* Fixed: the completed-order email offered guest customers a return link to My Account, which they cannot sign in to. Guests now get the link only when the hosted returns page is connected.
* Fixed: shop managers could open the Settings tab but not save it.
* Deleting the plugin now removes every connection setting, including the portal address, and with "Delete data on uninstall" ticked, the plugin's email settings too.
* The readme lists every connection the optional Return OS service makes, under External services.

= 1.4.0 =
* Connected stores can now offer store credit and exchanges in the hosted portal. Nothing changes for stores that do not use them, and both are off until you turn them on in Return OS.
* Added a way for Return OS to confirm whether a replacement order it created already exists, so a connection that drops halfway through cannot leave you with two of them.

= 1.3.2 =
* Order lookup no longer uses a meta query WooCommerce reports as unsupported, which logged an "incorrect usage" notice on stores using the classic orders table. It now reads each order's number the way the store itself reports it, so any numbering plugin works.

= 1.3.1 =
* Fixed: on stores with sequential order numbers, looking up an order could return a different order that happened to sit at that internal ID. Customers were shown an unrelated old order of their own and told nothing on it could be returned. The order number is now confirmed against the order, and stores whose numbering plugin keeps its numbers somewhere unusual are handled too.

= 1.3.0 =
* Signed-in customers are now handed straight to the hosted portal for the order they picked, instead of being asked for the order number and email the shop already knows. Guest customers are unaffected and still use the portal's lookup form.
* Fixed: a connected store showed a single generic "Start a return" button rather than the customer's own orders.
* Fixed: if a store's hosted portal lapsed, the plugin kept sending customers to it. It now returns to the built-in flow on the next hourly check-in.

= 1.2.0 =
* When a store is connected to Return OS, returns are now requested in the hosted customer portal — which guest customers can use, unlike the My Account page — and the "Request return" links point there automatically.
* While connected, the wp-admin queue becomes a read-only history and approvals and refunds happen in Return OS only. Two places able to refund one order is one too many. Disconnecting brings the built-in queue straight back, unchanged.
* The portal address is refreshed on every hourly check-in, so it keeps working if the store later moves to its own domain.

= 1.1.0 =
* Added an optional connection to the Return OS hosted service, on a new Connect tab. Nothing is sent anywhere until you press the button, and everything the plugin already did keeps working whether or not you connect it.
* Connecting creates a clearly named, revocable WooCommerce API key and an order-updates webhook, and repairs that webhook automatically if WooCommerce disables it.
* A copy of a connected site — a staging or backup clone — now detects itself and stays dormant rather than acting on the real store's behalf.
* Disconnecting or deleting the plugin removes the API key, the webhook and the stored credentials.

= 1.0.0 =
* First release: My Account return requests with item-level quantities, reasons and photos; wp-admin queue with approve, decline and refund; correct partial refunds on discounted and multi-tax orders; manual-payout flow for gateways without API refunds; four native WooCommerce emails; HPOS support; privacy exporter and eraser.

== Upgrade Notice ==

= 1.5.0 =
Adds guest returns and the EU withdrawal button, required since 19 June 2026, and fixes a store's own /returns/ page being hidden. Recommended for all stores, and essential for stores selling in the EU.

= 1.4.1 =
Fixes returns missing from My Account when a customer's login and billing emails differ, and pauses the order webhook while the plugin is deactivated. Recommended for all stores.

= 1.4.0 =
Adds store credit and exchanges for connected stores. Optional, and off until you enable them.

= 1.3.2 =
Completes the 1.3.1 order-lookup fix and removes an "incorrect usage" notice on stores using the classic orders table.

= 1.3.1 =
Fixes order lookup on stores with sequential order numbers, where customers could be shown the wrong order. Recommended for all connected stores.

= 1.3.0 =
Signed-in customers go straight from My Account to the portal without retyping their order details. Recommended for connected stores.

= 1.2.0 =
Connected stores now send customers to the hosted portal and manage returns there. If you have not connected, nothing changes.

= 1.1.0 =
Adds an optional connection to the Return OS hosted service. Nothing changes unless you choose to connect.

= 1.0.0 =
First release.
