=== WPNest Returns for WooCommerce ===
Contributors: wpnest
Tags: woocommerce, returns, refunds, rma, eu-directive
Requires at least: 6.2
Tested up to: 7.0
Requires PHP: 7.4
Requires Plugins: woocommerce
Stable tag: 1.3.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Return button, per-item wizard, customer emails, and PDF confirmations for WooCommerce — built around EU Directive 2023/2673.

== Description ==

WPNest Returns for WooCommerce gives any WooCommerce store a complete customer-facing return workflow plus a one-stop admin to handle requests, refunds and customer communication — all without leaving WordPress.

The plugin is built around two EU laws:

* **Directive (EU) 2023/2673** — mandatory "return" button for online stores selling to EU consumers, effective **19 June 2026**.
* **Directive 2011/83/EU** ("Consumer Rights Directive") — right of withdrawal, durable-medium confirmation, model withdrawal form.

You install the plugin, activate it, and your shop is compliant. No external services are called at runtime, no licensing, no phoning home.

= How customers use it =

1. From their order in **My Account → Orders**, customers click "Return this order".
2. A dedicated `/returns/` page opens a 3-step wizard:
   * **Items** — pick which products and quantities to return (return 1 of 3 if they bought 3).
   * **Reasons** — choose from a structured list per item (too small, damaged, doesn't match the description, "other" with details, etc.).
   * **Summary** — review, confirm, submit.
3. They receive a confirmation email with a generated PDF for their records, and the order moves to a new "Return Pending" status.

Guests who checked out without an account use the same wizard after verifying their email + order key on `/returns/`.

= How admins handle returns =

A dedicated "Return Requests" admin screen under WooCommerce gives a one-stop detail view per request:

* Customer info (name, email, phone, full billing/shipping address, payment method).
* Returned items with calculated refundable balance per line.
* Status quick actions — Accept, Reject, Mark refunded — with optional internal note and customer email.
* **Issue refund** form using WooCommerce's native `wc_create_refund` — full or partial, per-item, with optional restock.
* Order notes — add a customer-visible message or a private admin note, straight from the panel.
* Status history timeline — every transition logged with user attribution.

The admin never has to open the WooCommerce order edit screen to finish a return.

= Key features (Free) =

* **Return button on My Account** and on the single-order view — uses standard WooCommerce action hooks; works with virtually every theme.
* **Dedicated `/returns/` page** — auto-created on activation; doubles as the customer wizard host and the guest entry point.
* **Guest return path** — email + order-key verification with 15-minute one-shot token, rate-limited at 5 attempts per hour per IP.
* **Per-item wizard with structured reasons** — 8 default reason codes, filterable so Pro / 3rd-party can add more.
* **4 custom WooCommerce order statuses** (`wc-rfw-pending`, `wc-rfw-accepted`, `wc-rfw-rejected`, `wc-rfw-refunded`) — visible in the Orders list, dropdowns, and bulk actions.
* **Customer emails** sent automatically:
  * On submission — confirmation with PDF attachment.
  * On every status change — short notice with the new state and next steps. Per-change "skip notification" override is available in the detail panel, and a global toggle lives in Settings.
* **Confirmation PDFs** generated locally with bundled tFPDF (LGPL). Files are stored with random-token filenames in a directory hardened with `.htaccess Deny from all` + `web.config`, and served only through an authenticated REST stream endpoint.
* **Customer-facing model return form (PDF)** — a generic EU Annex I(B) model form ships in English and Polish; admins can upload their own branded PDF per language via the WordPress Media Library.
* **Eligibility rules** — return window in days, excluded order statuses, excluded product IDs, customized-product flag, optional digital-with-waiver opt-out at checkout, WooCommerce Subscriptions bypass.
* **Per-item refund form** (admin) — wraps `wc_create_refund`, validated against remaining refundable quantity/amount, idempotent within a 60-second retry window.
* **Order notes integration** — adds notes (private or customer-visible) directly from the detail page; refunds also leave an audit trail order note.
* **Status history timeline** — every transition logged to a dedicated table with user attribution.
* **HPOS compatible** — declares `custom_order_tables` compatibility.
* **GDPR-aware** — IP addresses stored as HMAC-SHA256 hashes; PDFs are not publicly browsable; uninstall opt-in for data deletion.
* **Translation-ready** — English source strings; bundled Polish (pl_PL) translation.

= Where Pro extends this Free plugin =

A Pro extension is planned. The Free plugin already wires the filters/actions Pro will hook into, so no Free changes will be needed when Pro launches:

* HTML email templates with brand colour / logo customisation.
* Refund deadline tracker (Art. 13 CRD +14 days) with reminder emails.
* Per-category exclusion rules and customer attachment uploads.
* Courier label integration (Apaczka.pl, InPost, DPD) — out-of-the-box prepaid return labels.
* SMS notifications via SMSAPI.
* Webhooks to Slack / Discord / generic JSON.
* Analytics dashboard (return rate, top reasons, refund times).
* Bulk actions in the request list.

Free is **complete** for compliance and a Zalando-style return experience — Pro is purely a productivity / branding / automation upgrade.

= Compliance basis =

* **EU Directive (EU) 2023/2673** — "Return" button requirement for online stores selling to EU consumers, mandatory from 19 June 2026.
* **EU Directive 2011/83/EU** ("Consumer Rights Directive") — right of withdrawal (Art. 9–11), durable-medium confirmation (Art. 7), model withdrawal form (Annex I(B)).

The plugin uses "return" as the user-facing term throughout. The bundled model PDF references "right of withdrawal" as the formal legal term where appropriate.

Always consult a lawyer if you need a binding legal opinion for your jurisdiction.

== Installation ==

1. Install via **Plugins → Add New** in the WordPress admin (search "WPNest Returns for WooCommerce"), or upload the plugin ZIP via **Plugins → Add New → Upload Plugin**.
2. Activate the plugin through the **Plugins** screen.
3. Returns work immediately — no page required. The return flow (including guest verification for customers who checked out without an account) opens as a pop-up. To embed it inline in your own page structure instead, add the **WPNest Returns — Portal** block (or the `[wpnestreturns_returns_page]` shortcode) to any page and select that page under **WooCommerce → Returns Manager → General**. Add a "Start a return" link anywhere with the `[wpnestreturns_button]` shortcode (or button block).
4. (Optional) Visit **WooCommerce → Returns Manager → General** to configure return window, button label, merchant notification email, and the global "automatically email customer on status changes" toggle.
5. (Optional) Visit **WooCommerce → Returns Manager → Exclusion Rules** to fine-tune which orders and products are eligible.
6. (Optional) Upload your own branded model return form PDFs (EN / PL) under the "Customer-facing return forms (PDFs)" card.

The plugin works out of the box with default settings.

= Requirements =

* WordPress 6.2 or higher.
* WooCommerce 7.0 or higher (tested up to 10.x).
* PHP 7.4 or higher.

== Frequently Asked Questions ==

= Do I need to configure anything for the return button to appear? =

No. After activation, every eligible order in My Account → Orders shows a "Return this order" button automatically. The same button appears on the single-order view page. Eligibility defaults are sensible (14-day return window from the order date, excluded statuses `cancelled / failed / refunded / trash`).

= Can guests (customers who checked out without an account) return orders? =

Yes. The `/returns/` page renders an email + order-key verification form for logged-out visitors. After a successful match, a 15-minute one-shot session token is issued and the standard wizard opens. Verification is rate-limited to 5 attempts per hour per IP to prevent enumeration.

= Can customers return only some items, or only some of an item's quantity? =

Yes. The wizard's first step is item selection with per-item quantity inputs. A customer who bought 3 of an item can return just 1, while keeping 2 of the same item.

= What status changes happen on the WooCommerce order? =

The plugin registers four custom statuses (`Return Pending`, `Return Accepted`, `Return Rejected`, `Return Refunded`) and syncs them when admins update the return request. The order itself stays in WooCommerce — no parallel records, no shadow orders.

= Will the plugin refund the customer automatically? =

No automatic refunds. The admin issues the refund manually from the request detail page using WooCommerce's native `wc_create_refund` API (full or partial, per-item, with optional restock). The actual gateway payment is not contacted automatically — you can either trigger the gateway refund separately in the WC order edit, or rely on your gateway plugin's "Refund" button.

= Are customer emails sent automatically? =

Yes:

* On submission — a confirmation with the PDF attached.
* On every status change — a short email with the new state and next steps.

Both can be customized per change (admin checkbox in the detail panel) or globally (Settings → General → "Customer status emails"). Submission emails always go through — they are part of the durable-medium compliance contract.

= Are confirmation PDFs generated locally or via an external service? =

Locally. The plugin bundles tFPDF (LGPL-licensed, no network calls). PDFs are written to `wp-content/uploads/wpnest-rfw/` with random-token filenames, the directory is protected by `.htaccess` (Apache) and `web.config` (IIS) `Deny`-all rules, and admins/customers download only through an authenticated REST endpoint. Direct URL access is denied even if the path is guessed.

= Will my return-request data be deleted when I uninstall the plugin? =

No, by default. Return request history is treated as audit-relevant data and is preserved across deactivate/uninstall cycles. To explicitly delete every request, status log, item row, and the auto-created `/returns/` page on uninstall, enable **Delete data on uninstall** in **Returns Manager → General** *before* uninstalling.

= Does it work with custom themes that override WooCommerce templates? =

Yes. The plugin hooks into the standard `woocommerce_my_account_my_orders_actions` filter and `woocommerce_order_details_after_order_table` action — both fire from the default WooCommerce template flow that virtually all themes preserve.

= Does it work with WooCommerce Subscriptions? =

The WooCommerce Subscriptions plugin is detected automatically and subscription orders are skipped by default. You can disable this guard in **Returns Manager → Exclusion Rules** if you want subscription orders to be returnable too.

= Can I customise the PDF model return form (English / Polish)? =

Yes. Settings → General → "Customer-facing return forms (PDFs)" lets you upload your own branded PDF per language through the WordPress Media Library. The plugin falls back to the bundled generic EU Annex I(B) form when no custom upload exists.

= Can I disable the public `/returns/` page entirely? =

Yes. Settings → General → "Where to show the return button" includes a "Public Returns page" toggle. When off, the `/returns/` URL returns a real HTTP 404. The wizard URL (`/returns/?order=ID`) still works for logged-in customers arriving from the "Return this order" button — so the My Account flow is preserved, but you can keep the page out of the public sitemap.

= How is customer privacy handled? =

* The customer's IP at submission time is stored as a salted HMAC-SHA256 hash, never as a raw address.
* Confirmation PDFs use unguessable filenames and are served only through an authenticated REST stream — direct URL access is denied at the web-server level.
* No external HTTP calls are made — no analytics, no licensing, no phoning home.
* Uninstall data deletion is opt-in.

= Is this plugin official / affiliated with WooCommerce? =

No. This is a third-party plugin built and maintained by WPNest. "WooCommerce" is a trademark of Automattic, used here in compliance with their guidelines (as a "for WooCommerce" suffix indicating compatibility, not endorsement).

== Screenshots ==

1. The 3-step return wizard customers see on `/returns/?order=ID` — items, reasons, summary.
2. My Account → Orders, with the "Return this order" button next to every eligible row.
3. Return Requests dashboard — list with filters, status badges, CSV export, and pending count in the admin menu.
4. Return Requests detail page — customer summary, returned items, status form, refund form, notes, history.
5. WooCommerce → Returns Manager → General — settings for the return window, custom PDF upload, status emails toggle.

== Changelog ==

= 1.3.0 =
* New: refund bank account for offline payments — when the order was paid with a method selected in settings (default: cash on delivery, bank transfer, cheque), the return wizard asks the customer for a bank account number, validated as NRB/IBAN with the mod-97 checksum both live in the browser and again server-side. Providing the number doubles as the consumer's explicit consent to a refund by bank transfer (art. 32(2) of the Polish Consumer Rights Act). Includes an optional bank-name field and a merchant-editable legal hint under the field.
* New: the collected account number appears on the request detail page (formatted, with one-click copy), in the merchant notification email (full number, ready for the transfer), and masked (`•••• 1234`) on the customer's confirmation PDF.
* New: shipping cost refunds — the refund form on the request detail page can include the order's shipping lines (per-line toggle with an editable partial amount), refunded through the native WooCommerce refund API.
* New: "Rejected returns" setting — a rejected return now blocks re-submission for the same order by default; turn the toggle off to restore the previous allow-resubmit behaviour. Deleting the rejected request always re-opens returns.
* New: return requests can be permanently deleted from the admin list (with confirmation).
* New: the paper-form (PDF) download card is editable — custom heading and description, plus per-language toggles to hide the EN / PL download buttons.
* Maintenance: database schema 1.3.0 (adds the `bank_name` column via dbDelta — automatic, no action required).

= 1.2.1 =
* New: configurable email sender — set a From name, From address and Reply-To address for the return emails under WooCommerce → Returns Manager → General. Empty fields fall back to your WooCommerce email settings, then to the WordPress default. Applied via per-message headers only, so other plugins' emails are unaffected.

= 1.2.0 =
* New: on activation the "Return this order" button is auto-added to the WooCommerce "Completed order" email (via the editable "Additional content" field) so customers get the return link out of the box — fully editable or removable under WooCommerce → Settings → Emails. Seeded once; removing it is permanent (a later reactivation won't bring it back).
* New: returns no longer require a dedicated page. The flow (guest verification → wizard → submit) is delivered as a pop-up modal out of the box, so guest customers who bought without an account always have a working entry point.
* New: **WPNest Returns — Portal** block + improved `[wpnestreturns_returns_page]` shortcode now run the whole flow on any page you embed them on, without bouncing back to a `/returns/` page.
* New: **Start a return** button — `[wpnestreturns_button]` shortcode and matching block — opens the returns pop-up from a menu, footer or product page.
* New: page picker under **WooCommerce → Returns Manager** lets you choose which page hosts the inline portal (with auto-detection of the page containing the block/shortcode).
* Change: the plugin no longer auto-creates a `/returns/` page on activation. Existing installs keep working unchanged — outbound links resolve to your existing page automatically.
* Change: disabling the public landing sections no longer forces a 404; it now simply hides the standalone orders list / guest form while keeping direct return links working.

= 1.0.3 =
* Renamed all PHP constants, classes, functions, options, transients and CSS classes from the legacy `wpnest_rfw_*` / `WPNest_RFW_*` / `WPNEST_RFW_*` / `wpnest-rfw-*` prefixes to the contiguous, per-plugin `wpnestreturns_*` / `WPNestReturns_*` / `WPNESTRETURNS_*` / `wpnestreturns-*` prefixes — the brand+module split was collapsed into a single unique identifier per WordPress.org plugin review guidance.
* Added a one-shot upgrade migration that renames the three custom tables (`{prefix}wpnest_rfw_requests`, `_request_items`, `_status_log`), all options and transients, and re-wires the daily cleanup cron event from the legacy hook name to the new one. The migration is idempotent and a no-op on fresh installs.
* The bundled tFPDF library remains under `libraries/tfpdf/` with its upstream public API (`tFPDF`, `TTFontFile`, `FPDF_FONTPATH`) intact — those names are part of the third-party library's interface and renaming them would prevent future upstream updates.
* Text domain (`wpnest-returns-for-woocommerce`) and plugin slug are unchanged.

= 1.0.2 =
* Declared incompatibility with the WooCommerce Cart/Checkout Blocks feature so merchants running block-based checkout receive a visible warning. The digital-content waiver checkbox is rendered on classic shortcode-based checkout only; without this declaration the waiver path would silently no-op on block checkout.
* Fixed potential double-escaping of the "Return this order" action URL on My Account → Orders by returning the raw URL from `woocommerce_my_account_my_orders_actions`. WooCommerce's template escapes it on output; pre-escaping caused `&` → `&amp;amp;` once the wizard URL carried multiple query parameters.
* Bundled the full GNU LGPL 2.1 license text alongside tFPDF (`libraries/tfpdf/LICENSE.txt`) per LGPL §6 distribution requirements.
* Translated three Polish source strings (guest verification errors, button-label hint) to English so they enter the .pot template in the canonical source language. Polish translation preserved in the bundled pl_PL catalogue.
* Internal: moved the custom WooCommerce order-status colour pills from an inline `<style>` block in `admin_head` to `assets/css/admin.css`, enqueued conditionally on the order list / edit screens.

= 1.0.1 =
* Return reason is now optional. Customers can submit a return request without selecting a reason for any item, and the "Other" reason no longer requires a free-text comment.
* Admin lists and exports show "No reason provided" for items submitted without a reason instead of an empty cell.
* Internal: removed dead `pick_reason` / `reason_text_required` client-side strings; server still rejects unknown reason codes for security.

= 1.0.0 =
* Initial release.
* Return button on My Account → Orders and on the single-order view.
* Dedicated `/returns/` page (auto-created on activation) with a 3-step customer wizard:
  * Step 1 — item selection with per-item quantity (return only some of what was ordered).
  * Step 2 — structured reasons per item (8 default reason codes + free-form "Other").
  * Step 3 — summary + confirmation.
* Guest return path with email + order-key verification, 15-minute token, rate-limited at 5 attempts per hour per IP.
* 4 custom WooCommerce statuses (`wc-rfw-pending / accepted / rejected / refunded`) integrated into bulk actions and dropdowns.
* Automatic customer emails on submission (PDF attached) and on every status change (with global + per-change override).
* Merchant notification email on submission.
* Confirmation PDF generation via bundled tFPDF (LGPL), stored in `wp-content/uploads/wpnest-rfw/` with random-token filenames and `.htaccess Deny` + `web.config Deny` directory hardening. Authenticated REST stream endpoint for downloads.
* Admin Return Requests dashboard — list with filters, inline status update, pagination, CSV export, pending-count badge in the WooCommerce submenu.
* Per-request admin detail page — customer summary, returned items, status form, refund form (uses WooCommerce native `wc_create_refund`, full or partial, per-item, with optional restock), order notes (customer-visible or internal), status history timeline.
* Status change log table with user attribution.
* Eligibility engine — return window, excluded statuses, excluded product IDs, customized-product flag, WooCommerce Subscriptions bypass, digital-with-waiver checkout opt-out.
* Settings — General + Exclusion Rules + Get Pro tabs, with split option groups so cross-tab saves never zero each other.
* Custom model return form PDF uploads — admin can override the bundled EN / PL generic EU Annex I(B) form per language through the WordPress Media Library.
* GDPR-aware design — IP hashed at submission, PDFs not browsable, no external HTTP at runtime.
* HPOS compatibility declared.
* Bundled Polish (pl_PL) translation.

== Upgrade Notice ==

= 1.3.0 =
Adds bank-account collection for offline-payment refunds (COD / transfer / cheque) with NRB/IBAN validation, shipping-cost refunds, deletable requests and a rejected-return resubmission control (now blocking by default). The database schema updates automatically.

= 1.2.1 =
Adds configurable From / Reply-To addresses for the return emails (WooCommerce → Returns Manager → General). No action required — empty fields keep the previous behaviour.

= 1.2.0 =
Returns now work without a dedicated page (pop-up fallback) and ship a Gutenberg block + "Start a return" button. Existing returns pages keep working unchanged. Recommended.

= 1.0.3 =
Renames all PHP/CSS identifiers to a single contiguous per-plugin prefix per WordPress.org review. Includes a one-shot, idempotent data migration that renames the three custom DB tables, options, transients and cron events. No action required from the merchant.

= 1.0.2 =
Declares incompatibility with WC Cart/Checkout Blocks (the digital-content waiver runs on classic checkout only). No data migration required.

= 1.0.1 =
The "return reason" field is now optional for customers. No data migration required.

= 1.0.0 =
Initial public release.

== Third-party services ==

This plugin **does not** call any external service at runtime. No analytics, no licensing checks, no remote logging.

The following third-party code is **bundled** inside the plugin and runs entirely locally:

* **tFPDF 1.33** (LGPL) — Unicode-aware PDF generator (fork of FPDF 1.85) used to produce confirmation PDFs. Source: https://github.com/Setasign/tFPDF — bundled in `libraries/tfpdf/` with the upstream license preserved.

== Privacy ==

* Return requests, status history, and request items are stored in three custom database tables (`wp_wpnest_rfw_requests`, `wp_wpnest_rfw_status_log`, `wp_wpnest_rfw_request_items`).
* The submitting customer's IP is stored as `hash_hmac( 'sha256', $ip, wp_salt('auth') )` — a one-way hash with a per-site secret. The raw IP is never persisted.
* Confirmation PDFs (one per request) are stored in `wp-content/uploads/wpnest-rfw/` with random-token filenames. Direct HTTP access to that folder is denied (`.htaccess Deny from all` + `web.config Deny`). Authenticated downloads go through `GET /wp-json/wpnest-rfw/v1/pdf/{request_id}` and are gated by capability/owner/guest-token rules.
* On uninstall, the plugin preserves all data by default. Enable **Delete data on uninstall** in Returns Manager → General to drop the tables, options, auto-created `/returns/` page, and PDF files when the plugin is uninstalled.
* No data leaves the WordPress install. No HTTP calls are made to third parties.