=== TillFoundry Returns Desk for WooCommerce ===
Contributors: lijnam
Tags: woocommerce, returns, rma, refunds, exchanges
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.3.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Self-service returns for WooCommerce, with paid partial-refund, exchange and store-credit tools in a separate add-on.

== Description ==

WooCommerce can refund an order from the admin, but it has no customer-facing returns workflow. Returns Desk adds a real self-service returns portal inside My Account so customers can open a return without emailing support, and store owners can action it from one place.

A logged-in customer opens an order, ticks the exact items and quantities they want to send back, chooses a reason per item, and submits. Returns Desk issues an RMA number, records the request, moves it through Requested, Approved, Rejected and Received, and emails the customer and the store at each step. Resolving or closing a return — the step that records the refund, store credit, exchange or keep-item decision — is a Returns Desk Pro add-on capability. The return window, eligible order statuses, reasons and policy text are all configurable.

= Free features (free forever) =

* A self-service returns portal in My Account for classic and shortcode themes.
* A per-item request form with a quantity selector capped at the unreturned quantity. Per-item selection is free and always will be.
* An RMA number on every request, with a configurable prefix.
* Return statuses: Requested, Approved, Rejected, Received and Cancelled, with a customer-facing timeline. The free workflow does not include the Resolved status; it is registered for continuity with the Pro add-on but cannot be reached in the free build. Marking a return Resolved and closing it out requires the separate Returns Desk Pro add-on.
* Automated customer and admin emails on request and on every status change.
* A configurable goodwill return window and eligible order statuses.
* EU/UK statutory withdrawal mode: a second, legally-required 14-day clock alongside the goodwill window and a withdrawal flag on the request form.
* A `[returns_desk]` shortcode for block themes and page builders.
* Full translation support and an opt-in debug log.

= Pro features (Returns Desk Pro add-on) =

These capabilities ship in the separate **Returns Desk Pro** add-on. The free download contains the free returns workflow only, plus a link to the add-on; installing the add-on and entering your Pro licence key on the free plugin's **Licence** tab enables the capabilities below.

* Per-item partial returns and partial refunds created through WooCommerce's own refund API.
* Closing and resolving a return: move an approved or received return to the Resolved status once the refund, exchange, store credit or keep-item decision is made.
* Exchanges: create a linked draft replacement order for an approved exchange.
* Store credit: issue a single-use, email-restricted WooCommerce coupon as credit.
* Bulk approve and refund from the returns list.
* Policy rules: category and product exclusions, required photo evidence, keep-item resolutions and refund caps.
* Customer photo and PDF evidence with secure validation.
* Internal merchant notes and a full audit log on every return.
* A returns dashboard with request volume, status funnel, average resolution time and refund totals.
* Streamed CSV export with spreadsheet-formula protection.

= Agency features (Returns Desk Pro add-on) =

These capabilities ship in the separate **Returns Desk Pro** add-on at the Agency tier.

* Multisite and multi-store support with per-site settings, branding and RMA sequences.
* A central returns dashboard across client sites, with each row labelled in its own currency.
* Role-based approval workflows with a dedicated approve capability.
* The EU/UK compliance helper: model withdrawal form, durable-medium acknowledgment log and an exportable withdrawal register.

= Pro and Agency =

TillFoundry Returns Desk for WooCommerce is free. Pro ($39) and Agency ($99) add 11 more capabilities.

The free plugin works on its own — nothing above is a trial, and nothing switches off when a licence is missing. What the paid tiers add:

* Per-item partial returns and partial refunds
* Exchanges and store credit
* Bulk approve/refund from the returns list
* Policy rules (window, category, photo evidence)
* Attachments and merchant notes
* Returns dashboard and CSV export
* Multisite / multi-store support
* Per-store policies and branding
* Central returns dashboard across client sites
* Role-based approval workflows
* …and 1 more, listed on the paid versions' page

See everything each tier includes: https://tillfoundry.com/product/returns-desk-pro/

= Built the WordPress way =

* WooCommerce High-Performance Order Storage (HPOS) compatible.
* Fails safe if WooCommerce goes away: with WooCommerce deactivated the plugin loads, shows an admin notice and registers nothing that would half-work, rather than fataling.
* No custom database tables; returns are portable, exportable custom posts.
* No tracking and no outbound calls except to the licence server for activation and validation.
* Customer photos stay in the WordPress media library.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/`, or install the ZIP through **Plugins → Add New → Upload Plugin**.
2. Activate the plugin through the **Plugins** screen in WordPress.
3. Open **Returns Desk → Settings** (the Returns Desk menu, next to WooCommerce) and review the general, policy, email, compliance and advanced tabs.
4. Install and activate WooCommerce. Returns Desk is safe without it, but the returns workflow needs it.
5. If you have purchased Returns Desk Pro, paste your licence key into the **Licence** field on the settings screen and save. That one field is the single place the key is entered, whether it is a free, Pro or Agency key: the Returns Desk Pro add-on registers no licence screen of its own, and it reads the key this tab saves.

== Frequently Asked Questions ==

= Does this plugin require WooCommerce? =

WooCommerce is required for the returns workflow itself. Without it, Returns Desk still loads safely, shows an admin notice and exposes its settings, but it registers no return post type, endpoint, REST route, email or scheduled job, so nothing half-works.

= Is per-item selection a paid feature? =

No. Selecting exact items and quantities on the request form is free and will stay free. The separate Returns Desk Pro add-on adds per-item partial refunds, exchanges, store credit, policy rules, attachments, merchant notes, bulk actions, the dashboard, CSV export, and resolving or closing returns (moving them to the Resolved status).

= I bought Returns Desk Pro. Where do I enter my licence key? =

On the free plugin's **Licence** tab: **Returns Desk → Settings → Licence**. There is one key and one place to enter it. The add-on registers no licence screen of its own, so its key is entered and saved on that tab together with the free plugin's, and the add-on reads the same stored key. Paste the key exactly as it was issued and save; when the store accepts it the page shows **Active**, the plan the key was sold as, and a **Licence for: Returns Desk Pro (add-on)** row naming the product the key belongs to.

A key that was issued for a different product is rejected with the store's own message naming that product, and nothing is saved — so a mistyped or wrongly bought key can never leave the site in a half-activated state.

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

Yes. Returns Desk declares compatibility with WooCommerce's custom order tables and reads and writes every order through the WooCommerce CRUD API, so behaviour is identical with HPOS enabled or disabled.

= How does the statutory withdrawal mode work? =

When enabled, customers in the configured regions see a withdrawal option with its own 14-day clock running from delivery (falling back to the order completion date when delivery is unknown). The withdrawal is recorded with a deadline computed at 23:59:59 in the site timezone. The model withdrawal form and acknowledgment register are part of the Agency tier.

= What happens if I refund from WooCommerce directly? =

Returns Desk reconciles native WooCommerce refunds so an item cannot be refunded twice, and caps every return refund at the order's remaining refundable total.

= Does the plugin send my data anywhere? =

The only outbound request is to `https://tillfoundry.com`, and only to activate, validate or deactivate your licence. Nothing is sent when no licence key is entered. When installed from the WordPress.org plugin directory (this build is being submitted for review), updates come from WordPress.org; otherwise the free build is downloaded from the store product page (https://tillfoundry.com/product/returns-desk/).

= Where are the debug logs? =

Enable **Debug logging** in the settings, then open **WooCommerce → Status → Logs** and select the `returns-desk` source. Without WooCommerce, entries go to the PHP error log instead.

== Screenshots ==

1. The per-item return request form in the customer’s My Account, with the quantity capped at the unreturned quantity.
2. The customer’s returns list, with the status, what happens next, and the request they submitted.
3. The return editor: approve, reject, receive and refund per item, with the customer-facing timeline.
4. The Returns Desk settings: who may return what, within which window, and how refunds resolve.

== External Services ==

This plugin connects to **TillFoundry** at `https://tillfoundry.com`, the service that issues and validates Returns Desk licences. It is not an automatic update server: when installed from the WordPress.org plugin directory (this build is being submitted for review), updates come from WordPress.org; otherwise the free build is downloaded from the store, so no update request is made to TillFoundry.

Nothing at all is sent to that service until you enter a licence key on **Returns Desk → Settings → Licence** and save it. With no key stored, the plugin makes no outbound request, and the free workflow (returns portal, RMA, statuses, emails and windows) runs entirely on your site.

Once a key is stored, the plugin calls these endpoints:

* **`POST https://tillfoundry.com/api/v1/activate`** — fires when you save a licence key, to activate it for this installation.
* **`POST https://tillfoundry.com/api/v1/validate`** — fires on the licence check (cached, and re-checked when the cached status expires, when you visit the settings screen, and when you test a key), to confirm the licence is still valid and to read its tier.
* **`POST https://tillfoundry.com/api/v1/deactivate`** — fires when you click **Deactivate** on the settings screen, to release the site's activation slot.

Every licence request sends the same fields:

* `license_key` — the key you entered.
* `site_url` — the site's WordPress address (`site_url()`).
* `home_url` — the site's home address (`home_url()`).
* `plugin_slug` — the product the key is being checked for: `returns-desk`, or `returns-desk-pro` when the Returns Desk Pro add-on is installed and the key was issued for it. A paid key belongs to the add-on, so the plugin asks about `returns-desk-pro` as well, and remembers the slug the key was accepted for so later checks present the same one.
* `plugin_version` — the installed Returns Desk version.
* `wp_version` — your WordPress version.
* `php_version` — your PHP version.
* `is_multisite` — `yes` or `no`.
* `locale` — the site locale, for example `en_US`.

The requests also carry the plugin slug and version and the site URL in HTTP headers. As with any HTTP request, the server that answers sees the connecting IP address in its normal request logs.

The service is operated by TillFoundry. Its terms and privacy policy, including the full list of data the licence check receives, are published at:

* Terms of Service: https://tillfoundry.com/terms/
* Privacy Policy: https://tillfoundry.com/privacy-policy/

A licence request never breaks your store. It is a short request that runs only when a licence is saved or re-checked, and a failure, a timeout or an unreachable server never takes the page down. The plugin keeps a previously validated licence alive while the service is unreachable, and it degrades gracefully — the free workflow keeps running and the paid features stay dormant.

== Changelog ==

= 0.3.0 =
* Maintenance release. No customer-facing feature is added: the requested photo-review ("review with photos") block is deliberately not built, because the free returns workflow already runs without it and the free tree is being prepared for the WordPress.org directory, which is the distribution channel that matters, not feature parity. No free capability moves behind the licence, and the ungated intake workflow (per-item selection, RMA, statuses, emails, windows and the statutory withdrawal clock) is unchanged.
* The `= Pro and Agency =` breakdown now appears exactly as generated from the tier data, including the bulk-action bullet, so the free listing points at the paid versions with the copy the release gate expects.
* Version metadata alignment: the plugin header `Version:`, the `RETURNS_DESK_VERSION` constant, this readme's `Stable tag`, the changelog and the upgrade notice all report 0.3.0, and the archive is rebuilt as `returns-desk-0.3.0.zip`. No feature, setting, hook, stored value or paid capability changed.

= 0.2.1 =
* Corrected a false update claim. The 0.2.0 notes below said the free plugin was a WordPress.org directory release and received directory-delivered updates. It is not listed in the WordPress.org plugin directory — `https://api.wordpress.org/plugins/info/1.0/returns-desk.json` returns `{"error":"Plugin not found."}` — so it takes no directory updates. The readme now states the store-download truth: the free build is delivered as a download from this store (https://tillfoundry.com/product/returns-desk/), and a later free release is published on that page.
* Version metadata alignment: the plugin header `Version:`, the `RETURNS_DESK_VERSION` constant, this readme's `Stable tag`, the changelog and the upgrade notice all report 0.2.1, and the archive is rebuilt as `returns-desk-0.2.1.zip`. No feature, setting, hook, stored value or paid capability changed.

= 0.2.0 =
* Free-build packaging release. The free plugin is packaged under the slug `returns-desk` for the `lijnam` account. It is not listed in the WordPress.org plugin directory, so it takes no directory updates; the free build is delivered as a download from this store. This release packages the current reviewed free tree; the self-hosted updater stays in the separate Returns Desk Pro add-on and is not shipped in this free build, and the licence client that lets a Pro key activate from the free Licence tab stays. No feature, setting, hook, stored value or paid capability changed, and the free intake workflow (per-item selection, RMA, statuses, emails, windows and the statutory withdrawal clock) is unchanged.
* Version metadata alignment: the plugin header `Version:`, the `RETURNS_DESK_VERSION` constant, this readme's `Stable tag`, the changelog and the upgrade notice all report 0.2.0, and the archive is rebuilt as `returns-desk-0.2.0.zip`.

= 0.1.8 =
* **A licence save now activates once.** Pasting a key and pressing Save made the site call the licence server dozens of times in a few seconds until it was rate-limited, stacked a notice per attempt, and could leave the key unsaved. The sanitiser re-entered itself through the option write, and WordPress passes an empty value when another form is saved — which was read as "remove the licence". Both are guarded now.

= 0.1.7 =
* Release republish: the free download is rebuilt and republished from the reviewed source tree so the artifact served from the store is generated from the build that carries the 0.1.6 licence fix. The 0.1.6 client offers the add-on slug `returns-desk-pro` alongside `returns-desk`, retries on `plugin_mismatch`, saves the key when a candidate validates and remembers the accepted slug; the free download was reported as still serving the pre-fix 0.1.5 build, so this release re-cuts the artifact, the plugin header and the `RETURNS_DESK_VERSION` constant as 0.1.7. The free workflow stays free and ungated, and no feature, setting, hook, stored value or paid capability changed.
* The free download continues to ship the free returns workflow only and to declare only free feature keys; no paid class is reachable from the free tree.

= 0.1.6 =
* Licence fix: a Returns Desk Pro or Agency key now activates from the free plugin's Licence tab. The store issues every paid key against the add-on's slug `returns-desk-pro`, while the client behind the tab presented only its own slug `returns-desk`, so the store answered "issued for a different plugin", the key was never saved and the paid features stayed locked. The client now offers `returns-desk` first and, when the Returns Desk Pro add-on is installed, `returns-desk-pro` as well; it retries when the store answers `plugin_mismatch`, saves the key when one candidate validates, and remembers the slug the key was accepted for.
* Validation, verification and deactivation then present the remembered slug, and a key stored by an earlier version with no recorded slug is offered to every candidate once so it migrates on the next validation. An accepted `returns-desk-pro` key adds a "Licence for: Returns Desk Pro (add-on)" row beside Status and Plan on the Licence tab. An unreachable licence server keeps its existing grace-period behaviour.
* The archive is rebuilt as `returns-desk-0.1.6.zip`; the free download continues to ship the free returns workflow only and to declare only free feature keys, and no paid class is reachable from the free tree. No setting, hook or stored value changed, so there is nothing to migrate.

= 0.1.5 =
* Release metadata alignment: the plugin header `Version:`, the `RETURNS_DESK_VERSION` constant, this readme's `Stable tag`, the changelog and the upgrade notice now all report the same 0.1.5 release, and the archive is rebuilt as `returns-desk-0.1.5.zip`. No feature, setting, hook or stored value changed, so there is nothing to migrate.
* The free download continues to ship the free returns workflow only and to declare only free feature keys; no paid class is reachable from the free tree.

= 0.1.4 =
* Version metadata alignment: the plugin header `Version:`, the `RETURNS_DESK_VERSION` constant, this readme's `Stable tag`, the changelog and the upgrade notice now all report the same 0.1.4 release, so the directory listing, the display version and the ZIP name describe one version. The archive is rebuilt as `returns-desk-0.1.4.zip`. No feature, setting, hook or stored value changed, so there is nothing to migrate.
* Packaging fix: the distributable now excludes the per-file infection client diff artefacts (`infection-client-after.*`) that the 0.1.3 archive shipped by mistake. They contained internal host paths and mutant source and were never intended for release.

= 0.1.3 =
* Version metadata alignment: the plugin header `Version:`, the `RETURNS_DESK_VERSION` constant and this readme's `Stable tag` now all report the same 0.1.3 release, so the listing and the ZIP it points at describe one version. No feature, setting, hook or stored value changed, so there is nothing to migrate.

= 0.1.1 =
* Adopted the brand-first display name: the plugin is now "TillFoundry Returns Desk for WooCommerce" in the header and readme title. The text domain stays `returns-desk`, matching the WordPress.org directory slug, so the just-in-time loader resolves language packs from `wp-content/languages/plugins/returns-desk-<locale>.mo`. No feature, setting, hook or stored value changed, so there is nothing to migrate.
* The translation template stays `languages/returns-desk.pot`, aligned with the `returns-desk` domain.

= 0.1.0 =
* Initial release: self-service returns portal, per-item requests, RMA numbers, statuses, emails, configurable window and the statutory withdrawal clock. The Pro and Agency capabilities (partial refunds, exchanges, store credit, policy rules, attachments, notes, bulk actions, dashboard, CSV export, multisite, central dashboard, role approvals and withdrawal compliance) ship in the separate Returns Desk Pro add-on.

== Upgrade Notice ==

= 0.3.0 =
* Maintenance release only: no feature, setting, hook or stored value changes. The plugin header, the internal version constant, the readme Stable tag, the changelog and this notice now agree on 0.3.0.

= 0.2.1 =
* Readme correction only: this build removes the false claim that the free plugin receives updates from the WordPress.org plugin directory. The free build is downloaded from the store. No settings, hooks or stored values change.

= 0.2.0 =
* Free-build packaging release. The plugin is not listed in the WordPress.org plugin directory, so install or update from this store's download page to get the same free returns workflow. No settings, hooks or stored values change.

= 0.1.7 =
* Release republish only: the free download is rebuilt from the reviewed source tree, and the header, the internal version constant, the readme Stable tag, the changelog and this notice now agree on 0.1.7. No settings, hooks or stored values change.

= 0.1.6 =
* Licence fix: Pro and Agency keys issued against the Returns Desk Pro add-on now activate from the free plugin's Licence tab instead of being refused. Update, then enter or re-check your key (Licence tab) so the client records the slug it validates against. No settings or stored values change.

= 0.1.5 =
* Release metadata alignment only: the plugin header, the internal version constant, the readme Stable tag, the changelog and the upgrade notice now agree on 0.1.5. No settings, hooks or stored values change.

= 0.1.4 =
* Version metadata alignment and a packaging fix only: the header, constant, Stable tag, changelog and upgrade notice now agree on 0.1.4, and the internal infection client diff artefacts are no longer shipped in the ZIP. No settings, hooks or stored values change.

= 0.1.3 =
* Version metadata alignment only: the plugin header, the internal version constant and the readme Stable tag now agree on 0.1.3. No settings, hooks or stored values change.

= 0.1.1 =
* Brand-name change only: the display name is now "TillFoundry Returns Desk for WooCommerce". The text domain remains `returns-desk`, matching the directory slug. No settings, hooks or stored values change.

= 0.1.0 =
* Initial release.
