EU Withdrawal Compliance

Description

The EU Directive 2023/2673 obliges every online retailer in the European Union to offer a digital withdrawal function from June 19, 2026. The directive requires that exercising the right of withdrawal must be at least as easy as concluding the contract was: a single click should be enough.

This plugin gives you the most complete implementation in the WordPress.org directory — fully free, no paid tier, no upsells, no premium add-ons — with three features that no other plugin in the directory offers:

  • Verifiable SHA-256 receipt hash: every withdrawal request issues a tamper-evident hash to the customer as durable-medium proof, recoverable from the stored fields if a dispute later arises.
  • Article 16 exclusions with category inheritance: mark products or whole WooCommerce categories as excluded from the right of withdrawal — subcategories inherit automatically — included at no extra cost (some competing plugins gate this feature behind their own paid Pro tier).
  • Native GDPR integration: contributes a suggested Privacy Policy snippet and registers a personal-data exporter and eraser so admins can answer access and erasure requests directly from Tools Export / Erase Personal Data, no second plugin required.

Plus a clean, ready-to-deploy WooCommerce-first implementation:

  • A public withdrawal page automatically created on activation, pre-filled with a neutral, multilingual sample template (with a clear “review with a legal advisor” disclaimer) and the form embedded via shortcode.
  • A [ayudawp_withdrawal_form] shortcode you can use anywhere in your site.
  • A “Right of withdrawal” endpoint inside the WooCommerce My Account area, with a per-order “Withdraw” button shown only while the configured withdrawal window is open.
  • Automatic injection of an “Exercise withdrawal right here” notice with link to the form inside WooCommerce transactional emails (processing, completed and customer invoice), to comply with the trader’s obligation to inform consumers about the existence and placement of the withdrawal function. The list of eligible order statuses is configurable from the settings page (defaults to Processing and Completed).
  • Automatic verification of the order/email pair when WooCommerce is active, including the 14-day deadline check.
  • Configurable deadline calculation: choose whether the 14-day window starts from the order date or from the WooCommerce completion date, and add extra grace days from the settings page.
  • Article 16 exclusions: mark individual products or whole categories as excluded from the right of withdrawal (custom-made, perishable, sealed digital, etc.). Requests on orders containing excluded items are flagged for the admin to review manually — never auto-rejected, since a partial withdrawal over the rest of the order can still be valid.
  • Verifiable receipt hash: every submission generates a SHA-256 hash sent to the customer in the confirmation email so they keep a tamper-evident proof on a durable medium.
  • Private order notes added to the WooCommerce order at every step of the lifecycle: when the request is received and again when it is accepted, rejected or marked as completed (including the admin’s comment if any).
  • Confirmation email to the customer on submission and a follow-up email when the request is accepted, rejected or completed. Optional admin comment is forwarded to the customer (required for rejections, optional for completed). Notification email to the shop admin (with reply-to set to the customer for fast handling, sanitized against header injection).
  • Full request log as a private custom post type, with status tracking (pending, accepted, rejected, completed), customer details, IP address, user agent and submission timestamp for legal traceability.
  • Bulk actions in the withdrawals listing to mark several requests as accepted, rejected or completed at once.
  • “Withdrawal” column in the WooCommerce orders screen (legacy and HPOS) showing the status of any linked request, toggleable from “Screen Options”.
  • Native integration in the WooCommerce admin menu: settings live at WooCommerce EU Withdrawal, request log at WooCommerce Withdrawals.
  • Honeypot anti-spam protection.
  • Conditional asset loading: CSS only loads on the withdrawal page and inside the plugin admin screens.
  • Native integration with the WordPress GDPR tools: suggested Privacy Policy snippet, personal data exporter and eraser available from Tools Export Personal Data and Tools Erase Personal Data, all keyed on the customer email.
  • Translation-ready, fully escaped, follows WordPress Coding Standards, HPOS-compatible.

Why this plugin?

The EU directive will be enforced in every member state from June 19, 2026, so several plugins offer a basic withdrawal button. This one stands out because:

  • It is fully free with no paid tier, no premium add-on and no feature locked behind an upsell. Everything described in this page is what you get on install.
  • It is the only plugin in the directory that issues a SHA-256 receipt hash as durable-medium proof of every withdrawal request, recoverable from the stored fields if a dispute later arises.
  • It is the only plugin in the directory that ships Article 16 product/category exclusions with full subcategory inheritance — a feature that competing plugins gate behind their own paid Pro tier.
  • It is the only plugin in the directory that integrates natively with the WordPress GDPR tools (suggested privacy snippet + personal-data exporter and eraser) — no second GDPR plugin to install.
  • It works with Sequential Order Numbers (free and Pro), Custom Order Numbers for WooCommerce (WPFactory) and YITH numbering schemes out of the box.
  • It offers a configurable deadline calculation (order date vs. WooCommerce completion date) and configurable grace days from the settings UI, without writing code.
  • It exposes 6 documented filters and 2 actions so developers and agencies can extend it without forking.
  • It is maintained by a Spanish WordPress trainer with 15+ years on the platform: bundled es_ES translation, prompt replies on the WordPress.org support forum and an active roadmap of free improvements.

Roadmap

Planned for upcoming free versions:

  • Rate limiting on the public form to prevent abuse.
  • Custom WooCommerce order status “Withdrawal requested” with automatic transition on acceptance.
  • Urgency indicators in the request list (days remaining, expired).
  • Signed token in the email link so guest customers can check status without logging in.
  • Optional IBAN field to speed up manual refunds.
  • Dashboard widget with counters and pending requests.
  • CSV export of withdrawal requests for audit and accounting.
  • PDF download of the request with the receipt hash printed on it.
  • HTML email templates that inherit the WooCommerce email theme.
  • Optional modal display mode for the shortcode.
  • Visible audit log on each request.

Privacy

This plugin stores the following personal data for each withdrawal request, exclusively to fulfil the legal traceability of consumer rights and to allow the shop to handle the request:

  • Customer name and email address (required to contact the consumer about the request).
  • Order reference and order date (required to validate the request against the purchase).
  • IP address and User-Agent string (required to evidence when and how the request was submitted, in line with the directive’s “durable medium” requirement).
  • Submission timestamp (UTC) and SHA-256 receipt hash (required to recompute and verify the integrity of the original submission if disputed).

Data is stored as a private custom post type entry (ayudawp_withdrawal) accessible only to administrators. The plugin does not transmit any data to third-party services; all communication happens between the shop and the customer via standard WordPress emails.

You should add a section to your site’s privacy policy describing this storage. The plugin contributes a suggested Privacy Policy snippet that you can paste from Settings Privacy Policy Guide. Withdrawal data is also exposed to the native WordPress Tools Export Personal Data and Tools Erase Personal Data screens (filtered by customer email).

Support

Need help or have suggestions?

Love the plugin? Please leave us a 5-star review and help spread the word!

About AyudaWP.com

We are specialists in WordPress security, SEO, AI and performance optimization plugins. We create tools that solve real problems for WordPress site owners while maintaining the highest coding standards and accessibility requirements.

Installation

  1. Upload the plugin folder to /wp-content/plugins/.
  2. Activate the plugin from the Plugins screen.
  3. The plugin creates a “Right of withdrawal” page automatically with a sample legal template. Review and edit it from Pages.
  4. Go to WooCommerce EU Withdrawal to configure the notification email address and the page that hosts the form.
  5. Add the URL of the withdrawal page to your footer or to the legal links section so it is visible from any page on your site.

FAQ

Will the form check the 14-day deadline?

Yes, when WooCommerce is active. If the order is older than the configured window the plugin rejects the request with a clear message. From version 1.2.0 you can configure the calculation basis (order date vs. completion date) and add extra grace days directly from WooCommerce EU Withdrawal. The legacy ayudawp_euw_grace_days and ayudawp_euw_skip_deadline_check filters still work for programmatic overrides.

How do I mark products that are excluded from the right of withdrawal (Article 16)?

You have two options, which can be combined:

  1. By category: from WooCommerce EU Withdrawal Article 16 exclusions pick the WooCommerce categories whose products fall under one of the Article 16 exceptions (custom-made, perishable, sealed digital content opened by the consumer, hygiene-sealed items, etc.).
  2. By product: when editing a product, tick the Excluded from right of withdrawal checkbox under the General tab.

When a withdrawal request lands on an order containing excluded items, the plugin flags it in the admin notification email and on the request detail screen. The request is never auto-rejected, because a partial withdrawal over the non-excluded items in the same order can still be valid. The admin reviews and decides.

What is the receipt verification code in the customer email?

It is a SHA-256 hash computed from the request data (post ID, customer name, email, order reference, scope, order date and submission timestamp). The customer keeps the email as a tamper-evident proof on a durable medium. If a dispute later arises, you can recompute the hash from the stored fields with the ayudawp_euw_compute_receipt_hash() helper and confirm the original submission was not altered.

Where are withdrawal requests stored?

Each request is saved as a private custom post type entry called ayudawp_withdrawal. You can manage them under WooCommerce Withdrawals in your admin area. They are not publicly accessible from the frontend.

Does it support HPOS (High-Performance Order Storage)?

Yes. The plugin declares HPOS compatibility on load.

Does it work with plugins that change the WooCommerce order number (Sequential Order Numbers, Custom Order Numbers, etc.)?

Yes. The form accepts both the internal WooCommerce order ID and the displayed order number. The resolver looks up the customer-facing number in the standard _order_number post meta, which covers WooCommerce Sequential Order Numbers (free and Pro), Custom Order Numbers for WooCommerce (WPFactory) and any plugin that follows the same convention. For plugins that store the number elsewhere or compute it on the fly (e.g. YITH Sequential Order Number, custom integrations), use the ayudawp_euw_pre_resolve_wc_order filter to provide your own resolver.

Will the notice appear on every WooCommerce email?

No. By default the notice is only added to the customer-facing emails relevant to the withdrawal window: order processing, completed and customer invoice (the manually triggered one). Admin emails never receive the notice. The notice is also gated by the configured list of eligible order statuses (default: Processing and Completed) so the manual invoice email only carries it when the order is in one of those statuses. You can change the email list with the ayudawp_euw_email_ids filter and the status list under WooCommerce EU Withdrawal Eligible order statuses or with the ayudawp_euw_allowed_statuses filter.

In which languages is the plugin available?

All strings are translation-ready. Translations are managed through the official WordPress.org platform at translate.wordpress.org, so any locale with enough translated strings is delivered automatically to your site when the WordPress site language matches. Contributions to existing or new locales are welcome there.

Does the plugin pass GDPR requirements?

The plugin asks for explicit privacy policy acceptance before submission and stores the visitor IP and user agent only for the purpose of legal traceability of the request. See the Privacy section above for the full list of stored fields. From version 1.4.0 the plugin also integrates natively with the WordPress GDPR tools: a suggested Privacy Policy snippet appears in Settings Privacy Policy Guide, and withdrawal data is exposed to Tools Export Personal Data and Tools Erase Personal Data so admins can fulfil access and erasure requests without leaving the WordPress admin.

What happens if the customer deletes their WordPress user account?

The withdrawal log is independent of the WordPress user table — it lives as a private custom post type indexed by the customer email. Deleting the user account does not delete the log automatically; the customer must request erasure through Tools Erase Personal Data (where the plugin registers an eraser that removes every withdrawal request matching the customer email) or you can delete the corresponding ayudawp_withdrawal entries manually if your retention policy requires it.

Can I customise the email subjects and bodies?

Currently the emails are sent in plain text and their copy is translatable through the standard WordPress text-domain. HTML email templates that respect the WooCommerce email theme are planned for a later release. For now, advanced customisation requires hooking into the wp_mail filters.

Which hooks does the plugin expose for developers?

Filters:

  • ayudawp_euw_grace_days — extra days added to the 14-day deadline. The default is the value stored in settings; the filter receives that value, so returning $days + N adds on top of it.
  • ayudawp_euw_skip_deadline_check — return true to disable the deadline check entirely. Receives the WC_Order as second argument.
  • ayudawp_euw_email_ids — array of WooCommerce email IDs where the withdrawal notice is injected.
  • ayudawp_euw_allowed_statuses — array of order statuses (without the wc- prefix) for which the withdrawal button and email notice are offered. Receives the value stored in settings and the current WC_Order (when available).
  • ayudawp_euw_allow_unverified_order — return true to accept submissions whose order number cannot be matched against a real WooCommerce order. Useful for sites that also handle non-WC purchases.
  • ayudawp_euw_pre_resolve_wc_order — short-circuit the order resolver. Return a WC_Order instance to accept, false to reject, or null (default) to fall through to the built-in strategies. Useful for plugins that store the displayed order number outside the standard _order_number post meta (e.g. YITH Sequential Order Number, custom ERP integrations).
  • ayudawp_euw_resolve_wc_order — late filter that receives the resolved WC_Order (or false) and the raw reference, for auditing or last-chance overrides.

Actions:

  • ayudawp_euw_after_submission — fires after a withdrawal request has been processed. Arguments: CPT ID, submission data array.
  • ayudawp_euw_after_status_change — fires after a status change (individual or bulk). Arguments: CPT ID, new status, optional admin comment.

Is this plugin enough to comply with EU Directive 2023/2673?

Yes. The plugin implements every functional requirement that Directive 2023/2673 imposes EU-wide from 19 June 2026: a discoverable digital withdrawal function, deadline validation, Article 16 exclusions with subcategory inheritance and durable-medium proof of the request via the SHA-256 receipt hash. On top of that it adds operational tools that the directive does not mandate but that make handling requests practical: per-status email injection, status lifecycle with bulk actions, native GDPR integration and full traceability. Member States can layer extra national requirements on top of the EU baseline — the strictest known so far (Germany) requires a two-step confirmation flow with an intermediate review page, which is not yet implemented because the Spanish transposition is still pending. A future release will adapt to the final Spanish Real Decreto once published.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“EU Withdrawal Compliance” is open source software. The following people have contributed to this plugin.

Contributors

“EU Withdrawal Compliance” has been translated into 1 locale. Thank you to the translators for their contributions.

Translate “EU Withdrawal Compliance” into your language.

Interested in development?

Browse the code, check out the SVN repository, or subscribe to the development log by RSS.

Changelog

1.4.0

  • New: native integration with the WordPress GDPR tools. The plugin contributes a suggested Privacy Policy snippet (visible in Settings Privacy Policy Guide) and registers a personal data exporter and eraser, so admins can answer access and erasure requests for withdrawal data from Tools Export Personal Data and Tools Erase Personal Data without leaving WordPress. The exporter and eraser match by customer email and cover every meta field stored alongside the request (name, order reference, IP, user-agent, scope, submission timestamp, receipt hash and free-text details).
  • New: declares Requires Plugins: woocommerce in the plugin header so WordPress can prompt users to install or activate WooCommerce before activating the plugin.

1.3.0

  • New: configurable list of order statuses for which the withdrawal button and email notice are offered. All WooCommerce-registered statuses appear in the new WooCommerce EU Withdrawal Eligible order statuses section (custom statuses from shipping or fulfilment plugins included). Default: Processing and Completed.
  • New: the notice is now also injected in the Customer invoice / Order details email (the manually triggered one). It only renders when the order status matches the configured list, so the manual invoice can still be sent on any status without leaking the notice.
  • Change: the notice is no longer shipped in the On-hold email by default. On-hold typically means payment is still pending, so offering the right of withdrawal at that point would be misleading. Sites that want the previous behaviour can opt back in via the ayudawp_euw_email_ids filter.
  • New filter ayudawp_euw_allowed_statuses to override the eligible statuses programmatically (receives the array and the current WC_Order when available).

1.2.2

  • Fix: the form now resolves the order by its displayed number, not just by the internal post ID. Compatible with WooCommerce Sequential Order Numbers, Sequential Order Numbers Pro, Custom Order Numbers for WooCommerce (WPFactory) and any plugin that stores the customer-facing number in the standard _order_number post meta.
  • Improvement: the “Withdraw” button on the WooCommerce My Account Orders screen and the link injected into WooCommerce transactional emails now pre-fill the form with the same order number the customer sees in their receipt.
  • New filter ayudawp_euw_pre_resolve_wc_order to short-circuit the resolver for plugins with non-meta numbering schemes (e.g. YITH Sequential Order Number) and ayudawp_euw_resolve_wc_order for late override or auditing.

1.2.1

  • Fix: validate that the WooCommerce order exists when WC is active. The previous fallback used to accept submissions whose order number could not be matched against a real WC order — intended as an escape hatch for non-WC purchases — which let users submit withdrawals with completely invented order numbers. Sites that genuinely accept non-WC purchases can opt back into the lenient behaviour with the new ayudawp_euw_allow_unverified_order filter.
  • Fix: translate the Scope value (Full/Partial) in the withdrawal detail metabox. It used to render the raw stored value in English even on translated sites.

1.2.0

  • New: Article 16 exclusions. Mark individual products or whole WooCommerce categories as excluded from the right of withdrawal. Subcategories inherit the exclusion from the parent automatically. Withdrawal requests on orders containing excluded items are flagged for manual review (never auto-rejected) so a partial withdrawal over the rest of the order can still be valid.
  • New: instant-search picker for excluded categories in the settings page, with removable chips and instant auto-save.
  • New: inherited exclusion is reflected in the product editor — the per-product checkbox renders ticked and disabled with a note pointing to the category responsible for the inheritance.
  • New: verifiable SHA-256 receipt hash. Every submission generates a hash sent to the customer in the confirmation email and stored on the request. Acts as tamper-evident proof on a durable medium and can be recomputed later from the stored fields.
  • New: configurable withdrawal deadline. Choose whether the 14-day window starts from the order date or from the WooCommerce completion date, and add extra grace days directly from the settings page. The ayudawp_euw_grace_days filter still works on top of the stored value.
  • New: submission timestamp (UTC) stored alongside each request and surfaced in the request detail metabox.
  • Tweak: polished CPT labels (“Edit withdrawal”, “New withdrawal”, etc.).

1.1.0

  • New: customer email notifications on every status change (accepted, rejected, completed).
  • New: optional admin comment forwarded to the customer on status change. Required for rejections, optional for completed requests.
  • New: WooCommerce order notes on every status change so the order timeline reflects the full withdrawal lifecycle.
  • New: bulk actions in the withdrawals listing to mark several requests as accepted, rejected or completed at once.
  • New: “Withdrawal” column in the WooCommerce orders screen (legacy and HPOS) showing the status of any linked request, toggleable from “Screen Options”.
  • Tweak: trimmed inline styles in the WooCommerce email notice so it inherits the email template styles instead of forcing a coloured callout box.

1.0.0

  • Initial release.

For older entries (1.0.0 through 1.2.x), see changelog.txt in the plugin folder.