{"id":319606,"date":"2026-06-01T13:12:48","date_gmt":"2026-06-01T13:12:48","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/beziworld-withdrawal-for-woocommerce\/"},"modified":"2026-06-01T20:53:43","modified_gmt":"2026-06-01T20:53:43","slug":"beziworld-withdrawal-for-woocommerce","status":"publish","type":"plugin","link":"https:\/\/vec.wordpress.org\/plugins\/beziworld-withdrawal-for-woocommerce\/","author":23394266,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.16.0","stable_tag":"2.16.0","tested":"7.0","requires":"6.4","requires_php":"7.4","requires_plugins":null,"header_name":"BeziWorld Withdrawal for WooCommerce","header_author":"Kamil \u0106wiertnia","header_description":"EU Directive 2023\/2673 compliant withdrawal-from-contract flow for WooCommerce. One-click form, partial returns, automatic confirmation, configurable refund.","assets_banners_color":"308dbd","last_updated":"2026-06-01 20:53:43","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/beziworld.eu\/plugins\/withdrawal-for-woocommerce","header_author_uri":"https:\/\/beziworld.eu","rating":0,"author_block_rating":0,"active_installs":0,"downloads":96,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.2":{"tag":"1.0.2","author":"beziworld","date":"2026-06-01 13:12:36"},"1.1.0":{"tag":"1.1.0","author":"beziworld","date":"2026-06-01 14:34:20"},"1.2.0":{"tag":"1.2.0","author":"beziworld","date":"2026-06-01 16:31:25"},"2.15.4":{"tag":"2.15.4","author":"beziworld","date":"2026-06-01 19:35:45"},"2.15.5":{"tag":"2.15.5","author":"beziworld","date":"2026-06-01 20:17:00"},"2.16.0":{"tag":"2.16.0","author":"beziworld","date":"2026-06-01 20:53:43"}},"upgrade_notice":{"2.16.0":"<p>Adds an optional expiry for guest withdrawal links (off by default). No action needed unless you want to enable it.<\/p>","2.15.5":"<p>Polishes the settings-screen layout. No configuration needed.<\/p>","2.15.4":"<p>Lets shop managers (not just administrators) save the plugin settings. No configuration needed.<\/p>","2.15.3":"<p>Improves personal-data erasure so in-progress withdrawals are retained until finalised. No configuration needed.<\/p>","2.15.2":"<p>Corrects the timezone handling of recorded delivery dates in the deadline calculation. No configuration needed.<\/p>","2.15.1":"<p>Security fix for the acknowledgement-certificate feature. Updating is recommended.<\/p>","2.15.0":"<p>Documentation-only release: inline developer hook reference. No code changes.<\/p>","2.14.0":"<p>Adds an overdue-refund count to the stats CLI command and REST endpoint. No configuration needed.<\/p>","2.13.0":"<p>Surfaces overdue-reimbursement warnings on the main dashboard widget. No configuration needed.<\/p>","2.12.0":"<p>Corrects Polish plural forms for counted strings. No configuration needed.<\/p>","2.11.0":"<p>Adds a reimbursement-deadline warning on the Withdrawals screen. No configuration needed.<\/p>","2.10.0":"<p>Adds per-status counts to the Withdrawals admin list filters. No configuration needed.<\/p>","2.9.0":"<p>Adds a read-only <code>\/wp-json\/bzww\/v1\/stats<\/code> REST endpoint for monitoring. No configuration needed.<\/p>","2.8.0":"<p>Adds a &quot;Top reasons&quot; breakdown to the dashboard widget. Reasons are recorded from this version onward.<\/p>","2.7.0":"<p>Adds a <code>wp bzww stats<\/code> WP-CLI command. No configuration needed.<\/p>","2.6.0":"<p>Adds a withdrawal-rate figure to the dashboard widget. No configuration needed.<\/p>","2.5.0":"<p>Adds average refund and average time-to-refund to the dashboard widget. No configuration needed.<\/p>","2.4.0":"<p>Adds a &quot;Record delivery date&quot; order action that sets the start of the withdrawal window. Optional; no change to existing behaviour.<\/p>","2.3.0":"<p>Adds a configurable deadline basis (completed \/ order \/ payment date) and optional grace days. Default behaviour (order completed date) is unchanged.<\/p>","2.2.0":"<p>Adds optional Slack\/Discord\/generic webhook notifications on withdrawal submission. Off by default.<\/p>","2.1.0":"<p>Adds optional Article 16(m) digital-content consent capture at the classic checkout. Off by default; no change unless enabled.<\/p>","2.0.0":"<p>Milestone release consolidating the 1.x compliance toolkit and adding an onboarding Help tab. No breaking changes \u2014 safe to upgrade; your settings and records are preserved.<\/p>","1.5.0":"<p>Adds a compliance readiness checklist to the settings screen and refines frontend styling for default themes.<\/p>","1.4.0":"<p>Adds a printable\/PDF-ready acknowledgement certificate linked from the confirmation email and the consumer&#039;s confirmation screen.<\/p>","1.3.0":"<p>Adds a one-click Annex I.A\/I.B legal-text generator and an optional two-step confirmation screen, plus default-theme styling refinements.<\/p>","1.2.0":"<p>Adds always-visible withdrawal access (footer link + optional sticky bar), an optional validated IBAN refund field, and WCAG 2.1 AA accessibility improvements.<\/p>","1.1.0":"<p>Adds a tamper-evident SHA-256 receipt code to the withdrawal acknowledgement, clearer receipt-only wording, and spam\/rate-limit hardening on the public forms. Recommended for all sites.<\/p>","1.0.2":"<p>Follow-up maintenance release addressing the PHPCS escape-output finding on the settings page reported during WordPress.org review.<\/p>","1.0.1":"<p>Maintenance release addressing WordPress.org plugin-directory review feedback (escaping discipline, no inline scripts).<\/p>","1.0.0":"<p>Initial release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3556797,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3556797,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3556797,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3556797,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3556797,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":{"bzww\/withdrawal-form":{"$schema":"https:\/\/schemas.wp.org\/trunk\/block.json","apiVersion":3,"name":"bzww\/withdrawal-form","title":"Withdrawal Form","category":"woocommerce","icon":"undo","description":"Renders the EU 2023\/2673 withdrawal-from-contract form. Same content as the [bzww_withdrawal_form] shortcode.","keywords":["withdrawal","refund","return","woocommerce","eu"],"textdomain":"beziworld-withdrawal-for-woocommerce","supports":{"html":false,"align":["wide","full"]},"attributes":{},"editorScript":"file:.\/index.js"}},"tagged_versions":["1.0.2","1.1.0","1.2.0","2.15.4","2.15.5","2.16.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3556797,"resolution":"1","location":"assets","locale":"","width":3840,"height":1138},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3556797,"resolution":"2","location":"assets","locale":"","width":1590,"height":1389},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3556797,"resolution":"3","location":"assets","locale":"","width":3601,"height":1786},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3556797,"resolution":"4","location":"assets","locale":"","width":3571,"height":2679}},"screenshots":{"1":"Customer view of the Withdrawal tab inside My Account \u2014 eligible orders with the 14-day deadline.","2":"Item-level partial withdrawal form \u2014 pick which items and how many units to return.","3":"Admin list of withdrawal statements with bulk actions and CSV export.","4":"Settings page \u2014 customer-facing labels, refund policy and eligibility rules."}},"plugin_section":[262246],"plugin_tags":[265372,265373,245590,286,265371],"plugin_category":[45],"plugin_contributors":[265339],"plugin_business_model":[],"class_list":["post-319606","plugin","type-plugin","status-publish","hentry","plugin_section-dashboard-widgets","plugin_tags-odstapienie-od-umowy","plugin_tags-prawo-konsumenckie","plugin_tags-withdrawal","plugin_tags-woocommerce","plugin_tags-zwroty","plugin_category-ecommerce","plugin_contributors-beziworld","plugin_committers-beziworld"],"banners":{"banner":"https:\/\/ps.w.org\/beziworld-withdrawal-for-woocommerce\/assets\/banner-772x250.png?rev=3556797","banner_2x":"https:\/\/ps.w.org\/beziworld-withdrawal-for-woocommerce\/assets\/banner-1544x500.png?rev=3556797","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/beziworld-withdrawal-for-woocommerce\/assets\/icon.svg?rev=3556797","icon":"https:\/\/ps.w.org\/beziworld-withdrawal-for-woocommerce\/assets\/icon.svg?rev=3556797","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/beziworld-withdrawal-for-woocommerce\/assets\/screenshot-1.png?rev=3556797","caption":"Customer view of the Withdrawal tab inside My Account \u2014 eligible orders with the 14-day deadline."},{"src":"https:\/\/ps.w.org\/beziworld-withdrawal-for-woocommerce\/assets\/screenshot-2.png?rev=3556797","caption":"Item-level partial withdrawal form \u2014 pick which items and how many units to return."},{"src":"https:\/\/ps.w.org\/beziworld-withdrawal-for-woocommerce\/assets\/screenshot-3.png?rev=3556797","caption":"Admin list of withdrawal statements with bulk actions and CSV export."},{"src":"https:\/\/ps.w.org\/beziworld-withdrawal-for-woocommerce\/assets\/screenshot-4.png?rev=3556797","caption":"Settings page \u2014 customer-facing labels, refund policy and eligibility rules."}],"raw_content":"<!--section=description-->\n<p><strong>BeziWorld Withdrawal for WooCommerce<\/strong> is a free, GPL-licensed compliance toolkit that prepares your shop for the EU Modernisation Directive (Directive (EU) 2023\/2673) which takes effect on <strong>19 June 2026<\/strong>.<\/p>\n\n<p>The directive forces every online shop selling to EU consumers to expose a dedicated, one-click withdrawal function inside the shop interface. This plugin delivers that function \u2014 and goes well beyond the minimum.<\/p>\n\n<h4>What this plugin gives you<\/h4>\n\n<ul>\n<li>A dedicated <strong>Withdrawal<\/strong> tab inside WooCommerce's My Account area<\/li>\n<li><strong>Item-level partial withdrawals<\/strong> with per-line quantity selection<\/li>\n<li><strong>Public withdrawal page<\/strong> auto-created on activation \u2014 guests enter order number + email to authenticate, no account required<\/li>\n<li>Single-use guest token automatically embedded in the order confirmation email<\/li>\n<li>Immediate confirmation email on a durable medium, as required by Article 11(3) CRD<\/li>\n<li>Configurable withdrawal period (14-60 days, default 14) \u2014 increase if you offer an extended return policy<\/li>\n<li>Deadline calculator that rolls weekends and public holidays forward to the next working day<\/li>\n<li>Polish public holidays preset for 2026-2028 (extensible to any locale through a filter)<\/li>\n<li>Refund calculation that respects Article 13(2) CRD: items plus the cheapest standard shipping option you offered<\/li>\n<li><strong>Automatic refunds<\/strong> through the original payment gateway when supported (Stripe, PayU, Przelewy24, Tpay, Mollie, etc.)<\/li>\n<li>Admin can override the calculated refund amount (restocking fee, partial damage)<\/li>\n<li>Configurable rule engine: exclude products, categories, user roles or payment gateways from withdrawal<\/li>\n<li>Storefront notices on <strong>product \/ cart \/ checkout \/ thank-you<\/strong> pages with WYSIWYG-editable text<\/li>\n<li>Predefined reason picker, configurable from settings<\/li>\n<li>Customer self-service cancellation while still pending<\/li>\n<li><strong>Four customer status emails<\/strong> (confirmation, received, refunded, rejected) \u2014 all overridable from your theme<\/li>\n<li>Optional automatic transition of the order status when a withdrawal is submitted<\/li>\n<li><strong>Full audit trail<\/strong>: IP, user-agent, server-side timestamps in UTC<\/li>\n<li>Trash \/ restore \/ delete for withdrawal records<\/li>\n<li>CSV export of all withdrawal statements<\/li>\n<li>WordPress privacy tools integration \u2014 exporter and eraser registered by default<\/li>\n<li>WP-CLI commands: <code>wp bzww list<\/code>, <code>wp bzww refund<\/code>, <code>wp bzww cancel<\/code>, <code>wp bzww received<\/code>, <code>wp bzww stats<\/code><\/li>\n<li>REST API endpoints under <code>\/wp-json\/bzww\/v1\/<\/code> for headless and mobile clients<\/li>\n<li>Gutenberg block and Elementor widget alternatives to the shortcode<\/li>\n<li>WooCommerce HPOS (custom order tables) compatible<\/li>\n<li>30+ filters and actions for deep customization (see HOOKS.md in the source repository)<\/li>\n<li><strong>Verifiable acknowledgement<\/strong>: each confirmation carries a SHA-256 receipt code recomputable from the stored declaration, so the durable-medium record is tamper-evident<\/li>\n<li><strong>Printable acknowledgement certificate<\/strong> the consumer can save as a PDF with the browser print function<\/li>\n<li><strong>Always-visible access<\/strong>: a site-wide footer link plus an optional dismissible sticky bar, so the withdrawal function is reachable from every page<\/li>\n<li><strong>Annex I generator<\/strong>: one-click withdrawal-information (Annex I.A) and model-withdrawal-form (Annex I.B) pages, generated from your store details, plus <code>[bzww_withdrawal_policy]<\/code> and <code>[bzww_model_form]<\/code> shortcodes<\/li>\n<li><strong>Optional two-step confirmation<\/strong> with a read-only review screen (off by default)<\/li>\n<li><strong>Optional IBAN field<\/strong> for consumers who request a refund to a specific account (never mandatory; validated with the IBAN checksum)<\/li>\n<li><strong>Compliance readiness panel<\/strong> in settings \u2014 an at-a-glance checklist of what is configured for the directive<\/li>\n<li><strong>Honeypot spam protection and per-IP rate limiting<\/strong> on the public lookup and guest submission<\/li>\n<li><strong>WCAG 2.1 AA<\/strong> accessibility on the customer-facing forms<\/li>\n<\/ul>\n\n<h4>Built-in features (no premium upgrade required)<\/h4>\n\n<ul>\n<li>Item-level partial withdrawals with quantity<\/li>\n<li>Guest withdrawal flow with email-based lookup<\/li>\n<li>Automatic refunds via payment gateways<\/li>\n<li>Weekend &amp; public-holiday aware deadline<\/li>\n<li>Audit trail with IP &amp; user-agent<\/li>\n<li>REST API<\/li>\n<li>WP-CLI<\/li>\n<li>CSV export<\/li>\n<li>GDPR exporter\/eraser<\/li>\n<li>Gutenberg block + Elementor widget<\/li>\n<\/ul>\n\n<h4>Theme overrides<\/h4>\n\n<p>Every customer-facing template can be overridden by your theme. Drop a copy of any file from <code>templates\/<\/code> into <code>your-theme\/beziworld-withdrawal-for-woocommerce\/&lt;same-path&gt;.php<\/code> and the plugin will use your version.<\/p>\n\n<h4>External services<\/h4>\n\n<p>This plugin performs <strong>no<\/strong> analytics, telemetry or remote downloads, and phones nothing home. Everything runs inside your WordPress installation.<\/p>\n\n<p>The single exception is entirely optional and off by default: if you enter a notification <strong>Webhook URL<\/strong> in the settings (for Slack, Discord or your own endpoint), the plugin sends a small JSON payload to that URL when a withdrawal is submitted. The payload contains the event name and the withdrawal and order identifiers (and the order number). No webhook is configured by default and nothing is sent unless you set a URL; the destination is the address you choose.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Activate WooCommerce 8.0 or newer if it is not already active.<\/li>\n<li>Upload the plugin via Plugins \u2192 Add New \u2192 Upload Plugin, or unzip into <code>\/wp-content\/plugins\/<\/code>.<\/li>\n<li>Activate the plugin through the Plugins screen.<\/li>\n<li>A <strong>Withdraw from contract<\/strong> page is created automatically \u2014 link it from your terms of sale.<\/li>\n<li>Go to <strong>WooCommerce \u2192 Withdrawal Settings<\/strong> to review the defaults (button labels, refund policy, eligibility rules, storefront notices).<\/li>\n<li>The plugin is now live. Logged-in customers see the <strong>Withdrawal<\/strong> tab inside My Account; guests can use the public page with the <code>[bzww_withdrawal_form]<\/code> shortcode.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"is%20this%20plugin%20enough%20to%20make%20my%20shop%20compliant%20with%20eu%202023%2F2673%3F\"><h3>Is this plugin enough to make my shop compliant with EU 2023\/2673?<\/h3><\/dt>\n<dd><p>The plugin covers the technical requirements: a dedicated withdrawal function, a clearly labelled button, immediate confirmation on a durable medium, item-level selection, and a refund mechanism within 14 days. You still need to update your terms of sale, your privacy policy and the order summary copy. <strong>The plugin does not provide legal advice.<\/strong> Consult a lawyer for the specifics of your jurisdiction.<\/p><\/dd>\n<dt id=\"how%20do%20guests%20%28logged-out%20customers%29%20submit%20a%20withdrawal%3F\"><h3>How do guests (logged-out customers) submit a withdrawal?<\/h3><\/dt>\n<dd><p>The plugin auto-creates a public page on activation with the <code>[bzww_withdrawal_form]<\/code> shortcode. Guests visit that page, enter their order number and the email used at checkout. After verification, the plugin issues a single-use token and shows the same withdrawal form a logged-in customer would see. The link is also embedded in the order confirmation email for one-click access.<\/p><\/dd>\n<dt id=\"does%20it%20support%20multi-shipment%20orders%3F\"><h3>Does it support multi-shipment orders?<\/h3><\/dt>\n<dd><p>Yes. The 14-day clock starts after the last parcel is delivered. Because WooCommerce core does not track shipment-level delivery, the plugin uses the order completion timestamp by default and exposes a <code>bzww\/order\/delivered_at<\/code> filter so a shipping\/tracking plugin can override it.<\/p><\/dd>\n<dt id=\"how%20are%20digital%20products%20handled%3F\"><h3>How are digital products handled?<\/h3><\/dt>\n<dd><p>Per Article 16(m) CRD, sealed digital products and downloadable software are excluded by default. Products flagged as Virtual + Downloadable in WooCommerce are skipped automatically.<\/p><\/dd>\n<dt id=\"can%20a%20customer%20cancel%20a%20pending%20withdrawal%3F\"><h3>Can a customer cancel a pending withdrawal?<\/h3><\/dt>\n<dd><p>Yes. While a statement is in the <strong>pending<\/strong> state the customer sees a \"Cancel this withdrawal\" button on the success page in My Account.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20hpos%20%2F%20custom%20order%20tables%3F\"><h3>Does it work with HPOS \/ custom order tables?<\/h3><\/dt>\n<dd><p>Yes. The plugin declares full HPOS compatibility on activation.<\/p><\/dd>\n<dt id=\"where%20are%20records%20stored%3F\"><h3>Where are records stored?<\/h3><\/dt>\n<dd><p>In a private custom post type, <code>bzww_withdrawal<\/code>. Each statement is one post with meta fields holding order id, items, amount, IP and user-agent.<\/p><\/dd>\n<dt id=\"how%20do%20i%20add%20public%20holidays%20for%20a%20country%20other%20than%20poland%3F\"><h3>How do I add public holidays for a country other than Poland?<\/h3><\/dt>\n<dd><p>Use the <code>bzww\/eligibility\/holidays<\/code> filter to return an array of <code>Y-m-d<\/code> strings. The plugin will roll the 14-day deadline forward when it lands on one of those dates.<\/p><\/dd>\n<dt id=\"can%20i%20customise%20the%20withdrawal%20period%3F\"><h3>Can I customise the withdrawal period?<\/h3><\/dt>\n<dd><p>Yes. WooCommerce \u2192 Withdrawal Settings \u2192 <strong>Withdrawal period (days)<\/strong> \u2014 set anywhere between 14 and 60 days. The default is the 14 days mandated by EU law; the minimum cannot go below the statutory 14.<\/p><\/dd>\n<dt id=\"can%20the%20admin%20adjust%20the%20refund%20amount%20before%20issuing%3F\"><h3>Can the admin adjust the refund amount before issuing?<\/h3><\/dt>\n<dd><p>Yes. Each withdrawal detail page has an editable <strong>Refund amount<\/strong> input pre-filled with the calculated value. Override it to deduct a restocking fee or to account for the condition of returned goods.<\/p><\/dd>\n<dt id=\"where%20can%20i%20learn%20about%20the%20available%20hooks%3F\"><h3>Where can I learn about the available hooks?<\/h3><\/dt>\n<dd><p>The plugin is built to be extended. The main filters and actions are listed below; all pass the relevant order or withdrawal so you can tailor behaviour without touching plugin files.<\/p>\n\n<p><strong>Eligibility &amp; deadlines (filters)<\/strong><\/p>\n\n<ul>\n<li><code>bzww\/eligibility\/excluded_statuses<\/code> \u2014 order statuses excluded from withdrawal.<\/li>\n<li><code>bzww\/eligibility\/deadline<\/code> \u2014 the computed withdrawal deadline (DateTime).<\/li>\n<li><code>bzww\/eligibility\/holidays<\/code> \u2014 extra non-working days for weekend\/holiday roll-over.<\/li>\n<li><code>bzww\/order\/delivered_at<\/code> \u2014 override the delivery date the deadline is based on.<\/li>\n<li><code>bzww\/rules\/order<\/code>, <code>bzww\/rules\/item<\/code> \u2014 allow or deny a specific order or line item.<\/li>\n<li><code>bzww\/withdrawal\/deny_duplicate<\/code> \u2014 whether to block a second statement for the same order.<\/li>\n<\/ul>\n\n<p><strong>Refunds (filters)<\/strong><\/p>\n\n<ul>\n<li><code>bzww\/refund\/deadline_days<\/code> \u2014 statutory reimbursement deadline in days (default 14).<\/li>\n<li><code>bzww\/refund\/args<\/code> \u2014 arguments passed to the WooCommerce refund.<\/li>\n<li><code>bzww\/refund\/cheapest_shipping<\/code> \u2014 override the cheapest-delivery amount (Art. 13(2)).<\/li>\n<\/ul>\n\n<p><strong>Lifecycle (actions)<\/strong><\/p>\n\n<ul>\n<li><code>bzww\/withdrawal\/submitted<\/code> ( $withdrawal_id, $request, $order )<\/li>\n<li><code>bzww\/withdrawal\/cancelled<\/code> ( $withdrawal_id )<\/li>\n<li><code>bzww\/withdrawal\/status_changed<\/code> ( $withdrawal_id, $previous, $current )<\/li>\n<li><code>bzww\/withdrawal\/transition_to_{status}<\/code> ( $withdrawal_id, $previous )<\/li>\n<li><code>bzww\/refund\/before<\/code>, <code>bzww\/refund\/after<\/code>, <code>bzww\/refund\/failed<\/code><\/li>\n<li><code>bzww\/plugin\/booted<\/code> ( $plugin )<\/li>\n<\/ul>\n\n<p><strong>Security, presentation &amp; integration (filters)<\/strong><\/p>\n\n<ul>\n<li><code>bzww\/submit\/rate_limit<\/code>, <code>bzww\/submit\/rate_window<\/code>, <code>bzww\/lookup\/rate_limit<\/code>, <code>bzww\/lookup\/rate_window<\/code> \u2014 throttling.<\/li>\n<li><code>bzww\/request\/client_ip<\/code> \u2014 resolved client IP behind proxies.<\/li>\n<li><code>bzww\/lookup\/resolve_order<\/code> \u2014 custom guest order lookup.<\/li>\n<li><code>bzww\/csv\/columns<\/code>, <code>bzww\/csv\/row<\/code> \u2014 admin CSV export shape.<\/li>\n<li><code>bzww\/webhook\/payload<\/code> \u2014 outbound webhook body.<\/li>\n<li><code>bzww\/templates\/theme_directory<\/code>, <code>bzww\/templates\/located<\/code> \u2014 template overrides.<\/li>\n<li><code>bzww\/placement\/html<\/code>, <code>bzww\/placement\/tokens<\/code>, <code>bzww\/access\/label<\/code>, <code>bzww\/access\/url<\/code> \u2014 front-end placement.<\/li>\n<li><code>bzww\/checkout\/digital_consent_text<\/code>, <code>bzww\/assets\/should_enqueue<\/code>, <code>bzww\/model_form\/policy_html<\/code>, <code>bzww\/model_form\/model_form_html<\/code>.<\/li>\n<\/ul>\n\n<p>A REST endpoint <code>GET \/wp-json\/bzww\/v1\/stats<\/code> (capability <code>manage_woocommerce<\/code>) and the <code>wp bzww<\/code> CLI commands are also available for monitoring and automation.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.16.0<\/h4>\n\n<ul>\n<li>New: optional expiry for the guest withdrawal link. Set <strong>WooCommerce \u2192 Withdrawal Settings \u2192 Guest link validity (days)<\/strong> to a positive number to have the emailed link stop working after that many days (an expired link sends the consumer back to the lookup form). Default is 0 = no expiry, so existing behaviour is unchanged.<\/li>\n<li>Internal: the guest-token issuing and verification logic is now centralised in one place across the form, REST API and My Account flows.<\/li>\n<\/ul>\n\n<h4>2.15.5<\/h4>\n\n<ul>\n<li>Improved: the compliance-readiness panel on the Withdrawal Settings screen now spans the full content width with a cleaner, WordPress-native two-column layout (styling moved into the admin stylesheet).<\/li>\n<\/ul>\n\n<h4>2.15.4<\/h4>\n\n<ul>\n<li>Fix: shop managers can now save the plugin settings. The settings save was previously gated by the administrator-only capability even though the screen is available to shop managers, so their changes were silently rejected.<\/li>\n<\/ul>\n\n<h4>2.15.3<\/h4>\n\n<ul>\n<li>Privacy: the personal-data eraser no longer deletes withdrawal statements whose withdrawal or refund is still in progress (pending\/received); these are retained until finalised so a pending reimbursement is never stranded, with only non-essential request metadata removed. Anonymising a retained (refunded) statement now also clears the free-text reason. The personal-data export now includes the received\/refunded timestamps and the confirmation channel.<\/li>\n<\/ul>\n\n<h4>2.15.2<\/h4>\n\n<ul>\n<li>Fix: when an explicit delivery date is recorded for an order, it is now normalised to the shop timezone before the withdrawal deadline is computed, so the final day boundary is correct regardless of the timezone the date was stored in.<\/li>\n<\/ul>\n\n<h4>2.15.1<\/h4>\n\n<ul>\n<li>Security: the post-submission confirmation page now shows the acknowledgement verification code and certificate link only to the order owner or to a request that carries the matching receipt code, so the code can no longer be revealed by guessing a withdrawal reference number. Affects only the acknowledgement-certificate feature; refund and submission flows are unchanged.<\/li>\n<\/ul>\n\n<h4>2.15.0<\/h4>\n\n<ul>\n<li>Documentation: the readme now lists the main developer filters and actions inline (eligibility, refunds, lifecycle, security, presentation and integration), instead of linking out for the hook reference.<\/li>\n<\/ul>\n\n<h4>2.14.0<\/h4>\n\n<ul>\n<li>The <code>wp bzww stats<\/code> command and the <code>\/wp-json\/bzww\/v1\/stats<\/code> REST endpoint now report the number of open statements past the reimbursement deadline (<code>overdue_refunds<\/code>), so external monitoring and alerting can track compliance risk.<\/li>\n<li>Fix: the \"All\" counter on the Withdrawals list now matches every row the list can display.<\/li>\n<li>Internal: the bundled translation compiler now byte-sorts the compiled .mo string table for full gettext conformance.<\/li>\n<\/ul>\n\n<h4>2.13.0<\/h4>\n\n<ul>\n<li>The dashboard widget now highlights, at the top, when open statements have passed the statutory reimbursement deadline \u2014 so the compliance risk is visible on login, not only on the Withdrawals screen.<\/li>\n<\/ul>\n\n<h4>2.12.0<\/h4>\n\n<ul>\n<li>Fix: Polish translations now use the correct grammatical plural forms for counts (e.g. processed-statement notices and the admin status counters), instead of falling back to English. The translation build tooling now compiles plural forms into the .mo file.<\/li>\n<\/ul>\n\n<h4>2.11.0<\/h4>\n\n<ul>\n<li>New: the Withdrawals admin screen now shows a warning when open statements have passed the 14-day statutory reimbursement deadline (EU Directive Art. 13(3)), so refunds are not missed. The deadline is filterable via <code>bzww\/refund\/deadline_days<\/code>.<\/li>\n<\/ul>\n\n<h4>2.10.0<\/h4>\n\n<ul>\n<li>The Withdrawals admin list now shows a count next to each status filter (All, Pending, Received, Refunded, Rejected, Trash), so you can see your queue at a glance without opening each view.<\/li>\n<\/ul>\n\n<h4>2.9.0<\/h4>\n\n<ul>\n<li>New read-only REST endpoint <code>GET \/wp-json\/bzww\/v1\/stats<\/code> (requires the manage_woocommerce capability) returning withdrawal counts by status, the total, the refunded amount and the shop currency \u2014 for headless dashboards and monitoring.<\/li>\n<\/ul>\n\n<h4>2.8.0<\/h4>\n\n<ul>\n<li>The selected predefined withdrawal reason is now stored separately, and the dashboard widget shows the most common reasons over the last 30 days \u2014 helping you spot and reduce avoidable returns.<\/li>\n<li>Security: the CSV export now neutralises spreadsheet formula injection in consumer-supplied fields (name, email, reason).<\/li>\n<li>Fix: automatic order-status transition on submission now works for all statuses (a prefix-handling bug previously skipped statuses such as \"completed\" and \"cancelled\").<\/li>\n<li>Hardening: outbound webhook requests no longer follow redirects and reject unsafe (internal) URLs; the Polish holiday preset is now generated algorithmically so it never expires; when a consumer requests a refund to a specific IBAN, automatic reversal to the card is disabled so it can be settled to that account.<\/li>\n<\/ul>\n\n<h4>2.7.0<\/h4>\n\n<ul>\n<li>New WP-CLI command <code>wp bzww stats<\/code> \u2014 prints withdrawal counts by status and the total refunded, for scripting and monitoring.<\/li>\n<\/ul>\n\n<h4>2.6.0<\/h4>\n\n<ul>\n<li>The dashboard widget now shows the withdrawal rate \u2014 the share of recent orders that resulted in a withdrawal \u2014 computed efficiently and cached.<\/li>\n<\/ul>\n\n<h4>2.5.0<\/h4>\n\n<ul>\n<li>The dashboard widget now also shows the average refund amount and the average time from submission to refund over the last 30 days, alongside the existing status counts and total refunded.<\/li>\n<\/ul>\n\n<h4>2.4.0<\/h4>\n\n<ul>\n<li>New: a \"Record delivery date\" order action. From the order screen you can stamp the delivery date that starts the 14-day withdrawal clock \u2014 useful for marking the last parcel of a multi-shipment order without a separate tracking plugin. The recorded date takes priority over the configured deadline basis.<\/li>\n<\/ul>\n\n<h4>2.3.0<\/h4>\n\n<ul>\n<li>New: configurable basis for the withdrawal deadline \u2014 start the 14-day clock from the order completed date (default), the order date, or the payment date \u2014 plus an optional \"grace days\" buffer. A delivery date recorded on the order (meta _bzww_delivered_at, e.g. by a tracking\/shipping plugin) still takes priority, so multi-shipment deadlines remain accurate.<\/li>\n<\/ul>\n\n<h4>2.2.0<\/h4>\n\n<ul>\n<li>New (opt-in): outbound webhook notifications. Set a Slack, Discord or generic webhook URL and the plugin posts a small JSON payload when a withdrawal is submitted. Off by default; nothing is sent unless a URL is configured (see Notifications in the settings, and the External services section).<\/li>\n<\/ul>\n\n<h4>2.1.0<\/h4>\n\n<ul>\n<li>New (opt-in): capture the consumer's Article 16(m) consent at checkout for carts containing immediately-supplied digital content (virtual + downloadable). The acknowledgement is stored on the order with its text and timestamp and recorded as an order note; it can be shown for the record only or required to complete checkout. Applied to the classic checkout; block (Store API) checkout support is planned.<\/li>\n<\/ul>\n\n<h4>2.0.0<\/h4>\n\n<ul>\n<li>Milestone release consolidating the compliance toolkit built across the 1.x series: verifiable SHA-256 acknowledgement receipt, printable certificate, always-visible footer\/sticky access, Annex I.A\/I.B page generator, optional two-step confirmation, optional IBAN field, honeypot + rate limiting, WCAG 2.1 AA forms, and a compliance readiness panel.<\/li>\n<li>New: a contextual Help tab on the settings screen with a quick-start guide and compliance pointers.<\/li>\n<li>Documentation: the plugin description now reflects the full feature set.<\/li>\n<li>No breaking changes \u2014 this is a safe upgrade from any 1.x version; settings and existing withdrawal records are preserved.<\/li>\n<\/ul>\n\n<h4>1.5.0<\/h4>\n\n<ul>\n<li>New: a compliance readiness panel on the settings screen \u2014 an at-a-glance checklist (public page published, guest form available, prominent access, withdrawal period, pre-purchase notice, Annex I.A\/I.B pages) so you can see what is set up for the directive and what still needs attention.<\/li>\n<li>UI: frontend form controls now inherit the active theme's typography, with clearer keyboard-focus styles and reduced-motion support, for a cleaner look on default block themes (Twenty Twenty-*) and Storefront.<\/li>\n<\/ul>\n\n<h4>1.4.0<\/h4>\n\n<ul>\n<li>New: a printable acknowledgement certificate \u2014 a clean, self-contained document of the withdrawal declaration (content, submission time and SHA-256 verification code) that the consumer can save as a PDF with the browser print function. Linked from the confirmation email, the on-screen confirmation, and the admin detail screen. No third-party PDF library is bundled.<\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<ul>\n<li>New: one-click generator for the statutory Annex I texts \u2014 a withdrawal-information page (Annex I.A) and a model withdrawal form (Annex I.B), populated from your store details. Available as the shortcodes [bzww_withdrawal_policy] and [bzww_model_form] and as ready-made pages from the settings screen.<\/li>\n<li>New: optional two-step confirmation \u2014 a read-only review screen with a separate confirmation control before a withdrawal is recorded (off by default; works without JavaScript).<\/li>\n<li>Frontend styling refined for readability on default block themes (Twenty Twenty-*) and Storefront, inheriting the theme's typography.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>Always-visible access: an optional site-wide footer link (on by default) and an optional dismissible sticky bar make the withdrawal function reachable from every page, in line with the Directive's \"prominent and easily accessible\" requirement \u2014 no longer only inside the My Account tab.<\/li>\n<li>Added an optional IBAN field to the withdrawal form (off by default) for consumers who ask to be refunded to a specific bank account. It is never mandatory; entries are validated with the IBAN checksum, surfaced in the confirmation email and admin detail, and included in the privacy exporter\/eraser.<\/li>\n<li>Accessibility: error messages now use a focus-managed alert region, required fields are explicitly marked, and the sticky bar has a keyboard-accessible dismiss control (WCAG 2.1 AA).<\/li>\n<li>New settings section \"Withdrawal access (visibility)\" plus an \"Offer an IBAN field\" toggle.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Acknowledgement of receipt now carries a verifiable SHA-256 code derived from the declaration content, so the confirmation can be checked for tampering \u2014 strengthening the durable-medium record required by Directive (EU) 2023\/2673.<\/li>\n<li>Confirmation wording now states clearly that it acknowledges receipt only and does not by itself decide whether the withdrawal is effective or accepted, keeping the receipt distinct from any automatic refund.<\/li>\n<li>The verification code is also shown on the on-screen confirmation in My Account and on the public guest page.<\/li>\n<li>Added honeypot spam protection and per-IP rate limiting to the public lookup and guest submission endpoints, hardening them against bots and order-number enumeration.<\/li>\n<li>Internal: centralised client IP \/ user-agent retrieval used by the audit trail and the new rate limiter.<\/li>\n<\/ul>\n\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>Pre-escape attribute and label arguments passed to <code>wp_dropdown_pages()<\/code> on the settings page so the PHPCS escape-output rule no longer flags the call.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Move inline checkbox-toggle script on the admin list screen to an enqueued asset (<code>assets\/js\/admin-list.js<\/code>).<\/li>\n<li>Escape the return value of the <code>the_title<\/code> filter and the WooCommerce endpoint-title filter.<\/li>\n<li>Register the Gutenberg block through a PHP render callback that returns a string, replacing the file-based render that echoed shortcode output.<\/li>\n<li>Document and lint-mark the trust boundary on the Elementor widget's shortcode echo.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<li>Full EU 2023\/2673 compliance: dedicated withdrawal function, immediate confirmation, 14-day refund.<\/li>\n<li>Item-level partial withdrawals with per-line quantity.<\/li>\n<li>Public lookup page (order number + email) \u2014 guests submit without an account.<\/li>\n<li>Automatic refund through the original payment gateway when supported.<\/li>\n<li>Admin can override the calculated refund amount per statement.<\/li>\n<li>Configurable withdrawal period (7-60 days).<\/li>\n<li>Deadline calculator with weekend &amp; Polish public-holiday rollover.<\/li>\n<li>Rule engine for excluding products, categories, roles and gateways.<\/li>\n<li>Storefront notices on product \/ cart \/ checkout \/ thank-you with WYSIWYG text.<\/li>\n<li>Four customer status emails (confirmation, received, refunded, rejected) \u2014 theme-overridable.<\/li>\n<li>Customer self-service cancellation.<\/li>\n<li>Trash \/ restore \/ delete workflow for withdrawal records.<\/li>\n<li>Audit trail with IP, user-agent, timestamps.<\/li>\n<li>WordPress privacy tools integration (exporter + eraser).<\/li>\n<li>WP-CLI commands.<\/li>\n<li>REST API.<\/li>\n<li>CSV export.<\/li>\n<li>Gutenberg block + Elementor widget.<\/li>\n<li>WooCommerce HPOS compatible.<\/li>\n<li>Polish translation included (253 strings).<\/li>\n<\/ul>","raw_excerpt":"EU 2023\/2673 withdrawal-from-contract flow for WooCommerce. Per-item withdrawals, guest tokens, automatic gateway refunds, holiday-aware deadlines.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/319606","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=319606"}],"author":[{"embeddable":true,"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/beziworld"}],"wp:attachment":[{"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=319606"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=319606"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=319606"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=319606"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=319606"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/vec.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=319606"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}