=== MX Contract Withdrawal for WooCommerce ===
Contributors: mediax
Tags: woocommerce, withdrawal, right-of-withdrawal, consumer-rights, ecommerce
Requires at least: 6.5
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 2.0.0
WC requires at least: 8.0
WC tested up to: 10.8
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Provides a privacy-first technical workflow for receiving, recording, and reviewing contract withdrawal requests. This plugin is not a legal compliance tool and does not provide legal advice.

== Description ==

MX Contract Withdrawal for WooCommerce is built to help WooCommerce merchants support the electronic withdrawal function introduced by Directive (EU) 2023/2673, applicable from 19 June 2026. Merchants should verify final legal requirements for their jurisdiction.

The plugin provides a technical workflow for receiving, recording, and reviewing contract withdrawal requests. It is not a legal compliance tool and does not provide legal advice.

The product direction focuses on a two-step withdrawal flow, guest secure-link verification, customer receipt email with durable-medium style confirmation, admin review, and audit records. Croatian-first merchant details include OIB / Tax ID, registered seat, contact details, and return address handling. Possible exceptions and uncertain cases are handled conservatively through fail-open/manual review wording and status handling.

== Installation ==

1. Upload or keep the plugin folder in `wp-content/plugins/mx-contract-withdrawal`.
2. Activate the plugin in WordPress.
3. Make sure WooCommerce is active.
4. Go to WooCommerce > Withdrawal Settings and select an existing public page, or create a draft withdrawal page from the plugin settings.
5. Publish the selected page when you are ready for customers to use the form.

== Basic Setup ==

On activation the plugin creates or updates its custom tables and default settings only. It does not automatically publish or create a public withdrawal page.

Merchants must select an existing page or manually create a withdrawal page from WooCommerce > Withdrawal Settings. The manual create action creates a draft page containing:

`[mx_contract_withdrawal_form]`

The created page title and slug are generated from a translatable source string, so localized stores can use localized page names.

== Shortcode ==

Use this shortcode where customers should submit requests:

`[mx_contract_withdrawal_form]`

== Cache Compatibility ==

The withdrawal page can show customer-specific order and declaration details after verification, especially for guest requests. Exclude the configured withdrawal page URL from page cache before going live.

If your cache layer supports cookie-based bypass rules, also bypass cache when the `mx_cw_access_token` cookie is present. Review rules in page-cache tools such as WP Rocket, LiteSpeed Cache, Cloudflare APO, Varnish/server cache, and managed host caches.

If your store is behind Cloudflare, nginx reverse proxy, load balancer, or managed host proxy, configure real visitor IP handling at the infrastructure level or via the `mx_contract_withdrawal/client_ip` filter. The plugin defaults to `REMOTE_ADDR` and does not blindly trust forwarded headers.

== Update Checks and Privacy ==

The plugin can contact the Media X update server to check whether a plugin update is available. Update checks may include the site URL, plugin version, and basic build information. This is used only to provide available update information.

== Data Retention and Uninstall ==

Withdrawal request, item, and audit records are retained according to the plugin's retention/anonymization behavior. Uninstalling the plugin does not automatically remove those records by default, because stores may need operational request history. Review the retention settings and your database cleanup process before removing stored data.

Retention maintenance uses WooCommerce Action Scheduler when available and keeps a bounded WP-Cron fallback. Action Scheduler improves batching and visibility, but production stores should still use a reliable server cron or WP-CLI queue runner; low-traffic sites that rely only on traffic-triggered WP-Cron may see delayed cleanup.

== Supporting Attachment Storage ==

When optional supporting attachments are enabled, uploaded files are stored in the standard WordPress uploads directory under `mx-contract-withdrawal/YYYY/MM/`. Stored filenames receive a random prefix to reduce collisions and guessability, but file URLs should still be treated as sensitive operational data.

The plugin writes Apache `.htaccess` rules to block PHP execution and directory listing in its upload folder where supported. On nginx or CDN/proxy deployments, add equivalent server rules manually and exclude the plugin upload subdirectory from public CDN or edge caching when attachments may contain sensitive customer documents.

== Custom Order Numbers ==

The public lookup supports standard WooCommerce order numbers and accepts the number with or without the `#` symbol, for example `1234` and `#1234`.

Stores that use custom or sequential order number plugins should test the public withdrawal lookup with the customer-facing order number before going live. Compatibility depends on how the order number plugin stores the display number.

The plugin includes built-in lookup support for a few common custom order number meta keys, including `_order_number`, `_alg_wc_custom_order_number`, and `_ywson_custom_number_order_complete`.

Developers can extend lookup support with these filters:

* `mx_contract_withdrawal/order_lookup_meta_keys`
* `mx_contract_withdrawal/order_number_meta_keys`
* `mx_contract_withdrawal/lookup_order_by_number`

Example: add a custom meta key used by your order number plugin:

```php
add_filter( 'mx_contract_withdrawal/order_lookup_meta_keys', function ( $keys ) {
	$keys[] = '_my_custom_order_number';
	return $keys;
} );
```

`mx_contract_withdrawal/order_number_meta_keys` is a backward-compatible alias applied after `mx_contract_withdrawal/order_lookup_meta_keys`.

Example: resolve the display number with fully custom logic:

```php
add_filter( 'mx_contract_withdrawal/lookup_order_by_number', function ( $order, $order_number, $raw_order_number, $billing_email, $context ) {
	if ( $order instanceof WC_Order ) {
		return $order;
	}

	// Resolve the custom display number to an order ID using your plugin's storage.
	$order_id = 0;

	return $order_id ? wc_get_order( $order_id ) : null;
}, 10, 5 );
```

Resolver results must exist and their customer-facing order number must match the normalized lookup value. Billing email and guest ownership verification continue through the plugin's secure lookup flow.

Improved support for custom WooCommerce order numbers, including popular Sequential/YITH/WPFactory numbering setups and a filter for custom resolvers.

== Current Features ==

* Guest lookup with order number and billing email verification.
* Secure magic-link guest flow with a short-lived access token.
* Logged-in customer order ownership foundation.
* Polished frontend lookup, declaration, review, and success flow.
* Consumer details confirmation before final submission.
* Merchant details including OIB / Tax ID in declaration context.
* Readiness widget for page, merchant identity, cache, and email checks.
* Durable request, item, and audit tables.
* Conservative manual-review eligibility classification.
* Customer receipt, admin notification, status update, and secure-link emails.
* WooCommerce-native email classes and templates.
* Admin request list/detail screens with status changes.
* WooCommerce order notes and admin notices for operational visibility.
* Basic settings page under WooCommerce.
* My Account withdrawal requests endpoint and order action.
* Product and category informational exclusion flags.
* Privacy exporter/eraser hooks and retention cleanup scaffolding.
* Dynamic block registration for the withdrawal form.
* Privacy-preserving hashed IP and user agent storage.
* Transient-based lookup rate limiting.
* Cache compatibility guidance for customer-specific withdrawal pages.

== Limitations ==

This first version focuses on a practical foundation and vertical workflow. The admin UI, settings, WooCommerce emails, privacy hooks, and exclusion flags are intentionally simple and should be reviewed in a staging store before production use. Advanced reporting, PDF generation, automatic refunds, return labels, integrations, and marketplace support are not included.

== Legal Notice ==

This plugin helps implement a technical withdrawal request workflow. It is not legal advice. Please verify the final legal requirements for your store and jurisdiction.

== Screenshots ==

1. Frontend contract withdrawal lookup card.
2. Verified order and consumer details confirmation.
3. Withdrawal review and final confirmation screen.
4. Customer success screen with reference number.
5. WooCommerce > Withdrawal Settings readiness dashboard.
6. WooCommerce > Withdrawal Requests admin detail view.

== Changelog ==

= 2.0.0 =
* Added optional supporting attachments with configurable visibility, reason rules, file-count and size limits, and a restricted JPG, JPEG, PNG, WebP, and PDF allowlist.
* Added secure attachment storage handling, randomized stored filenames, upload-directory protections, validation for real browser uploads, and cleanup during request anonymization.
* Added multi-step frontend attachment selection, selected-file previews, removal controls, upload result notices, and a 2 MB default maximum file size.
* Added an admin attachment gallery, safe attachment deletion, active attachment counts, request-list attachment filters, and attachment count/detail links in admin notification emails.
* Extended the WordPress privacy exporter and eraser to handle supporting attachment metadata without exposing private file paths.
* Added optional browser-native printable request records for administrators and short-lived customer print links after submission, with browser Save as PDF support.
* Added a configurable Return conditions notice with custom title, text, link, Media Library PDF selection, display locations, and optional customer acknowledgement recorded with the request.
* Added Return conditions content to supported frontend, receipt-email, and printable request contexts, including plain-text email URL handling.
* Added settings importance badges for mandatory, recommended, and optional configuration areas, and hid the readiness Mandatory badge once the plugin is ready to accept requests.
* Added an option to show the optional storefront footer link only on WooCommerce shop, product, category, and tag pages.
* Expanded the admin CSV export with human-readable request status and classifications, selected item and quantity summaries, active attachment counts, reason, and customer note.
* Improved CSV export performance by bulk-loading item rows and attachment counts, while preserving current filters and spreadsheet formula-injection protection.
* Improved the Classic and HPOS WooCommerce Orders withdrawal column with the latest request status badge, total request count, and active attachment count.
* Bulk-loaded WooCommerce Orders column summaries to avoid per-order request and attachment queries.
* Hardened custom order-number lookup with broader input normalization, contextual resolver arguments, public-number verification, duplicate-match rejection, and the backward-compatible `mx_contract_withdrawal/order_number_meta_keys` filter alias.
* Hardened the self-hosted updater so updates are offered only when the remote version is strictly newer, same-version notices are removed, and updater caches are cleared after successful updates.
* Rechecked remote version availability before returning self-hosted update package URLs.
* Migrated retention maintenance to WooCommerce Action Scheduler when available, with chained batches, chain-scoped locking, failure reporting, stale submit-lock cleanup, and a bounded WP-Cron fallback.
* Improved retention teardown on deactivation and uninstall by clearing plugin-owned Action Scheduler actions, WP-Cron events, and retention locks.
* Improved retention failure recovery so failed Action Scheduler batches release their matching chain lock before being marked failed.
* Replaced timestamp-only WordPress-local time calls with UTC timestamps where required.
* Prevented customers from continuing to request review when no order item is selected, with both frontend and server-side validation.
* Improved the final confirmation button styling so it inherits the active theme button color instead of using the plugin danger style.
* Improved Croatian translations and regenerated translation files for the new settings, notices, attachments, print views, and admin export fields.
* Updated WordPress and WooCommerce compatibility metadata for the 2.0.0 release.

= 1.0.1 =
* Hardened self-hosted update package URL handling to require HTTPS downloads.
* Improved public form nonce expiry handling with customer-facing recovery notices.
* Improved privacy erasure by removing customer actor identifiers from retained audit log events.
* Aligned release metadata for the 1.0.1 public build.
* Small fixes

= 1.0 =
* First stable release.
* Redesigned the plugin admin settings screen with a cleaner card-based layout, tabbed sections, inline icons, responsive navigation, switch-style toggles, preview actions, and shortcode copy support.
* Reorganized settings into clearer sections for page/display options, merchant details, eligibility and review rules, guest access and privacy, email notifications, and diagnostics.
* Added a dedicated Email notifications section with quick links to the related WooCommerce email settings used by the withdrawal workflow.
* Added privacy-safe email diagnostics for plugin emails, including last attempted send, last confirmed success, email type, request ID, error code, test email support, and a clear diagnostics action.
* Improved email delivery logging so unconfirmed sends and WordPress/PHPMailer errors are recorded more accurately per email type.
* Added CSV export for withdrawal requests using the current status, eligibility, date, and search filters.
* Added admin request overview stats for open requests, requests needing review, unknown deadlines, resolved requests this month, and total requests this month.
* Improved request list and detail badges so manual-review states are easier to understand.
* Changed the default request retention period from 6 years to 10 years.
* Improved retention cleanup so expired records are anonymized through the request repository and stored consumer details and billing snapshots are cleared.
* Added cleanup for stale submit-lock options.
* Improved public order lookup so customers can enter order numbers with or without the # symbol.
* Hardened order number normalization by trimming hidden characters and common punctuation, verifying the public order number, and supporting custom order number variants stored with or without #.
* Added frontend help text explaining where customers can find their order number.
* Reduced order email prefill token lifetime from 7 days to 72 hours.
* Improved expired or already-used secure guest link handling with a clearer message and a Start again action.
* Improved secure guest link handling when a valid guest access session already exists.
* Adjusted guest secure-link email sending so the public lookup flow can redirect before the email is sent.
* Fixed frontend price and currency display so order totals and selected item amounts do not split awkwardly across lines.
* Polished the final request received screen by moving the success checkmark next to the title and normalizing its size.
* Improved personal data export by showing submitted consumer details as readable fields instead of raw JSON.
* Updated privacy policy text and readme documentation for update checks, retention/uninstall behavior, and custom order number compatibility.
* Updated Croatian translations and regenerated translation files.

= 0.3.3 =
* Added an optional contract withdrawal link block for WooCommerce customer order emails.
* Added a new settings section for the order email withdrawal link.
* Added the ability to enable or disable the withdrawal link in order emails.
* Added the ability to choose which WooCommerce customer order emails should include the link.
* The link is shown by default in processing, on-hold, and completed customer order emails.
* Added optional support for showing the link in customer invoice emails.
* Added the ability to display the link as either a button or a plain text link.
* Added a setting for customizing the link/button text.
* The email link now points to the configured contract withdrawal page.
* Added a safe prefill token for order email links.
* The email link no longer exposes the customer e-mail address directly in the URL.
* The prefill token only pre-fills the order number and billing e-mail address in the lookup form.
* The prefill token does not open the second step of the form, does not verify order ownership, and does not bypass the guest secure-link flow.
* Added a 72-hour lifetime for prefill tokens.
* Added the `mx_contract_withdrawal/prefill_token_ttl` filter for changing the prefill token lifetime.
* The email link is not shown when the withdrawal page is missing or not published.
* The email link is not shown when the order already has a withdrawal request.
* The email link is not shown for orders that are not in allowed lookup statuses.
* The email link is not shown for older orders outside the configured action window, unless manual review for older orders is enabled.
* Added protection against rendering the contract withdrawal email block more than once.
* The email block now hooks into multiple WooCommerce e-mail positions for better compatibility with different e-mail templates.
* Improved the e-mail button markup so it works better across e-mail clients.
* The e-mail button now uses inline styling and WooCommerce e-mail colors instead of relying on CSS classes.
* Updated the Croatian default button text to “Pokreni zahtjev za raskid ugovora”.
* Added translation loading before default options are saved on activation, so new Croatian installations can receive Croatian default text.
* Added a new deadline estimation option based on the order received date.
* Added the new “Order received date + delivery buffer” estimation mode.
* Clarified that the existing completed-date estimation option uses the WooCommerce Completed order date.
* Added helper text explaining that WooCommerce Completed date means the date when the order was marked as Completed in WooCommerce.
* Deadline estimation no longer falls back to the order received date when the WooCommerce Completed date is missing; those cases are sent to manual review instead.
* Updated deadline estimation helper text to make it clearer that these options are only review-window estimates and not automatic legal decisions.
* Updated admin deadline notes to clearly show whether the estimate is based on the order received date or the WooCommerce Completed date.
* Updated Croatian translations for the new order email link settings, prefill flow, and deadline estimation text.

= 0.3.2 =
* Maintenance release with packaging/version alignment for the current 0.3.x build.

= 0.3.1 =
* Added a configurable customer-facing order action visibility window for account and public request entry points, without treating it as a legal deadline decision.
* Added a clear My Account withdrawal requests empty state with a link back to customer orders.
* Expanded product and category advisory flags for manual review while keeping flagged items selectable.
* Kept public guest lookup privacy-neutral for old orders and preserved manual-review handling where enabled.
* Secure magic-link guest flow is enabled by default for new installs.
* Polished the frontend withdrawal flow from lookup through success, including consumer detail confirmation before declaration submission.
* Added merchant details including OIB / Tax ID to declaration context and receipt snapshots.
* Added a readiness widget for withdrawal page, merchant identity, shortcode, and email setup.
* Integrated customer/admin/status/secure-link notifications with WooCommerce emails.
* Added WooCommerce order notes and admin notices for request and email events.
* Added cache guidance for customer-specific withdrawal pages.
* Updated Croatian terminology for contract withdrawal contexts.
