{"id":311450,"date":"2026-05-17T07:39:21","date_gmt":"2026-05-17T07:39:21","guid":{"rendered":"https:\/\/es.wordpress.org\/plugins\/wc-apg-withdrawal\/"},"modified":"2026-06-25T08:13:25","modified_gmt":"2026-06-25T08:13:25","slug":"apg-withdrawal-for-woocommerce","status":"publish","type":"plugin","link":"https:\/\/pcd.wordpress.org\/plugins\/apg-withdrawal-for-woocommerce\/","author":12534269,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"0.6.1","stable_tag":"0.6.1","tested":"7.1","requires":"6.0","requires_php":"7.4","requires_plugins":null,"header_name":"APG Withdrawal for WooCommerce","header_author":"Art Project Group","header_description":"Add to WooCommerce an online withdrawal workflow compliant with EU requirements.","assets_banners_color":"837e9c","last_updated":"2026-06-25 08:13:25","external_support_url":"","external_repository_url":"","donate_link":"https:\/\/artprojectgroup.es\/tienda\/donacion","header_plugin_uri":"https:\/\/wordpress.org\/plugins\/apg-withdrawal-for-woocommerce\/","header_author_uri":"https:\/\/artprojectgroup.es\/","rating":5,"author_block_rating":0,"active_installs":20,"downloads":490,"num_ratings":1,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.1.0":{"tag":"0.1.0","author":"artprojectgroup","date":"2026-05-18 12:34:07"},"0.2.0":{"tag":"0.2.0","author":"artprojectgroup","date":"2026-05-18 13:53:45"},"0.3.0":{"tag":"0.3.0","author":"artprojectgroup","date":"2026-05-18 17:16:16"},"0.4.0":{"tag":"0.4.0","author":"artprojectgroup","date":"2026-05-19 10:17:21"},"0.5.0":{"tag":"0.5.0","author":"artprojectgroup","date":"2026-06-03 10:10:25"},"0.6.0":{"tag":"0.6.0","author":"artprojectgroup","date":"2026-06-23 09:39:01"},"0.6.1":{"tag":"0.6.1","author":"artprojectgroup","date":"2026-06-25 08:13:25"}},"upgrade_notice":{"0.6.1":"<ul>\n<li>The bundled translation now wins over the centralised language pack for any locale shipped under <code>\/languages<\/code>, so the strings packaged with the current release are always rendered instead of waiting for translate.wordpress.org to regenerate.<\/li>\n<\/ul>"},"ratings":{"1":0,"2":0,"3":0,"4":0,"5":1},"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3534328,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3534328,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.jpg":{"filename":"banner-1544x500.jpg","revision":3534328,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.jpg":{"filename":"banner-772x250.jpg","revision":3534328,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":{"apg-withdrawal\/link":{"name":"apg-withdrawal\/link","title":"Withdrawal link"},"apg-withdrawal\/notice":{"name":"apg-withdrawal\/notice","title":"Withdrawal exclusion notice"}},"tagged_versions":["0.1.0","0.2.0","0.3.0","0.4.0","0.5.0","0.6.0","0.6.1"],"block_files":[],"assets_screenshots":{"screenshot-1.jpg":{"filename":"screenshot-1.jpg","revision":3537071,"resolution":"1","location":"assets","locale":"","width":2560,"height":4616},"screenshot-2.jpeg":{"filename":"screenshot-2.jpeg","revision":3535169,"resolution":"2","location":"assets","locale":"","width":1730,"height":2078},"screenshot-3.jpeg":{"filename":"screenshot-3.jpeg","revision":3535169,"resolution":"3","location":"assets","locale":"","width":1760,"height":2070},"screenshot-4.jpeg":{"filename":"screenshot-4.jpeg","revision":3535169,"resolution":"4","location":"assets","locale":"","width":1684,"height":2220},"screenshot-5.jpg":{"filename":"screenshot-5.jpg","revision":3535169,"resolution":"5","location":"assets","locale":"","width":2560,"height":2714},"screenshot-6.jpg":{"filename":"screenshot-6.jpg","revision":3535169,"resolution":"6","location":"assets","locale":"","width":2560,"height":2714}},"screenshots":{"1":"Plugin settings page with general options, automation rules and customer email notifications.","2":"Customer withdrawal form on the public page (guest checkout).","3":"Withdrawal form integrated in the My Account area with order selector.","4":"My Account orders list with the <em>Withdrawal request<\/em> action per order.","5":"Admin withdrawals list with status, scope and order reference.","6":"Edit withdrawal screen with full request details and status history."}},"plugin_section":[],"plugin_tags":[255182,9682,263226,245590,286],"plugin_category":[45],"plugin_contributors":[80963],"plugin_business_model":[],"class_list":["post-311450","plugin","type-plugin","status-publish","hentry","plugin_tags-consumer-rights","plugin_tags-refund","plugin_tags-right-of-withdrawal","plugin_tags-withdrawal","plugin_tags-woocommerce","plugin_category-ecommerce","plugin_contributors-artprojectgroup","plugin_committers-artprojectgroup"],"banners":{"banner":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/banner-772x250.jpg?rev=3534328","banner_2x":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/banner-1544x500.jpg?rev=3534328","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/icon-128x128.png?rev=3534328","icon_2x":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/icon-256x256.png?rev=3534328","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/screenshot-1.jpg?rev=3537071","caption":"Plugin settings page with general options, automation rules and customer email notifications."},{"src":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/screenshot-2.jpeg?rev=3535169","caption":"Customer withdrawal form on the public page (guest checkout)."},{"src":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/screenshot-3.jpeg?rev=3535169","caption":"Withdrawal form integrated in the My Account area with order selector."},{"src":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/screenshot-4.jpeg?rev=3535169","caption":"My Account orders list with the <em>Withdrawal request<\/em> action per order."},{"src":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/screenshot-5.jpg?rev=3535169","caption":"Admin withdrawals list with status, scope and order reference."},{"src":"https:\/\/ps.w.org\/apg-withdrawal-for-woocommerce\/assets\/screenshot-6.jpg?rev=3535169","caption":"Edit withdrawal screen with full request details and status history."}],"raw_content":"<!--section=description-->\n<p><strong>APG Withdrawal for WooCommerce<\/strong> adds to your WooCommerce store a complete online right of withdrawal workflow compliant with EU consumer protection legislation.<\/p>\n\n<h4>Features<\/h4>\n\n<ul>\n<li>Customer withdrawal form via the <code>[apg_withdrawal_form]<\/code> shortcode.<\/li>\n<li>Configurable withdrawal window (days) and deadline source (completed or created date).<\/li>\n<li>Optional extra grace days on top of the standard withdrawal window.<\/li>\n<li>Active request detection: hides the withdrawal button if a request is already open for the order.<\/li>\n<li>Optional digital-content waiver checkbox at checkout (both classic shortcode and block-based checkout): a configurable selector chooses when to display it \u2014 never, only on virtual products (or per-product <code>_apg_withdrawal_type = digital<\/code>), on every order, or on selected categories and\/or selected products. The customer's choice is persisted to order meta as legal evidence.<\/li>\n<li>Admin request log with full request details (custom post type).<\/li>\n<li>IP address and browser identifier storage options for legal evidence.<\/li>\n<li>Email notification to the store admin on every new request.<\/li>\n<li>Automatic customer acknowledgement email on submission.<\/li>\n<li>Customer status update emails when the request is accepted, rejected or completed.<\/li>\n<li>Automation: updates the withdrawal request status automatically when the linked WooCommerce order changes status.<\/li>\n<li>My Account integration: customers can view their withdrawal request history.<\/li>\n<li>CSV export of all withdrawal requests.<\/li>\n<li>100% compatible with HPOS (High-Performance Order Storage).<\/li>\n<\/ul>\n\n<h4>Translations<\/h4>\n\n<ul>\n<li>English: by <a href=\"https:\/\/artprojectgroup.es\/\">Art Project Group<\/a> (default language).<\/li>\n<li>Spanish: by <a href=\"https:\/\/artprojectgroup.es\/\">Art Project Group<\/a>.<\/li>\n<\/ul>\n\n<h4>More information<\/h4>\n\n<p>You can learn more about <strong>APG Withdrawal for WooCommerce<\/strong> on our <a href=\"https:\/\/artprojectgroup.es\/plugins-para-woocommerce\/apg-withdrawal-for-woocommerce\">official website<\/a>, and follow the development on <a href=\"https:\/\/github.com\/artprojectgroup\/apg-withdrawal-for-woocommerce\">GitHub<\/a>.<\/p>\n\n<h3>Thanks<\/h3>\n\n<p>Thanks to everyone who uses the plugin, helps improve it, makes a donation or encourages us with their comments.<\/p>\n\n<p>If you find this plugin useful, you can support its development with a <a href=\"https:\/\/artprojectgroup.es\/tienda\/donacion\">small donation<\/a>.<\/p>\n\n<h3>External Services<\/h3>\n\n<p>This plugin connects to the WordPress.org Plugins API to fetch information about the plugin (such as the rating). It sends the plugin slug when requesting data. More information: https:\/\/wordpress.org\/about\/privacy\/<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Install the plugin in one of the following ways:\n\n<ul>\n<li>Upload the <code>apg-withdrawal-for-woocommerce<\/code> folder to the <code>\/wp-content\/plugins\/<\/code> directory via FTP.<\/li>\n<li>Upload the full ZIP file via <em>Plugins -&gt; Add New -&gt; Upload<\/em> in the WordPress administration panel.<\/li>\n<li>Search for <strong>APG Withdrawal for WooCommerce<\/strong> in <em>Plugins -&gt; Add New<\/em> and click <em>Install Now<\/em>.<\/li>\n<\/ul><\/li>\n<li>Activate the plugin through the <em>Plugins<\/em> menu in the WordPress administration panel.<\/li>\n<li>Configure the plugin in <em>WooCommerce -&gt; Withdrawal<\/em> or through the <em>Settings<\/em> link on the plugins page.<\/li>\n<li>Add the <code>[apg_withdrawal_form]<\/code> shortcode to the page configured as the withdrawal page in the settings.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"how%20do%20i%20configure%20the%20plugin%3F\"><h3>How do I configure the plugin?<\/h3><\/dt>\n<dd><p>In the plugin settings you can configure the notification email, the withdrawal page, the withdrawal window in days, the deadline source (completed or created date), the extra grace days and which data to store (IP address, browser identifier).<\/p><\/dd>\n<dt id=\"is%20the%20plugin%20compatible%20with%20hpos%3F\"><h3>Is the plugin compatible with HPOS?<\/h3><\/dt>\n<dd><p>Yes. The plugin is fully compatible with WooCommerce High-Performance Order Storage.<\/p><\/dd>\n<dt id=\"can%20guest%20customers%20submit%20a%20withdrawal%20request%3F\"><h3>Can guest customers submit a withdrawal request?<\/h3><\/dt>\n<dd><p>Yes. The form supports both logged-in customers (with pre-filled data and order selector) and guests (with email lookup of their orders).<\/p><\/dd>\n<dt id=\"where%20should%20i%20place%20the%20withdrawal%20link%3F\"><h3>Where should I place the withdrawal link?<\/h3><\/dt>\n<dd><p>The withdrawal form page is auto-created on activation and contains the <code>[apg_withdrawal_form]<\/code> shortcode. To comply with Article 11a of Directive 2011\/83\/EU (added by Directive 2023\/2673), the link to that page should be prominently visible and easy to find on the storefront. The plugin gives you several tools to place it; deciding <em>where<\/em> to place it is the merchant's (or their web designer's) responsibility:<\/p>\n\n<ul>\n<li>The fixed URL of the auto-created page, available in <em>WooCommerce \u2192 Withdrawal \u2192 Withdrawal page<\/em>.<\/li>\n<li>The <code>[apg_withdrawal_link]<\/code> shortcode, with optional <code>label<\/code>, <code>class<\/code> and <code>target<\/code> attributes, to drop the link inside any post, page, footer widget or HTML block.<\/li>\n<li>The matching <em>Withdrawal link<\/em> Gutenberg block for sites built with the Full Site Editor.<\/li>\n<li>The <em>Withdrawal request<\/em> action that is automatically added to every eligible order in the <em>My Account \u2192 Orders<\/em> table.<\/li>\n<\/ul>\n\n<p>Typical recommended placements:<\/p>\n\n<ul>\n<li>The site footer, so the link is reachable from any page.<\/li>\n<li>The <em>My Account<\/em> menu (the per-order action is already added; you can also add a top-level menu item linking to the public form).<\/li>\n<li>The Terms and Conditions \/ Privacy Policy pages, alongside the rest of the consumer information required by Article 6.1.h of Directive 2011\/83\/EU.<\/li>\n<li>The order processing \/ completed emails (the plugin already injects the link there automatically via <code>woocommerce_email_after_order_table<\/code>).<\/li>\n<\/ul><\/dd>\n<dt id=\"how%20long%20should%20i%20keep%20the%20withdrawal%20request%20records%3F\"><h3>How long should I keep the withdrawal request records?<\/h3><\/dt>\n<dd><p>The plugin does not delete withdrawal request records automatically. As a general recommendation, keep them for at least <strong>5 years<\/strong> after their creation \u2014 the typical statute of limitations for consumer and contractual actions in many EU jurisdictions. Always check the applicable retention period in your country before deleting old records or running the plugin's CSV export + uninstall flow.<\/p><\/dd>\n<dt id=\"where%20can%20i%20get%20support%3F\"><h3>Where can I get support?<\/h3><\/dt>\n<dd><p><strong>APG Withdrawal for WooCommerce<\/strong> is a free plugin. <strong>Art Project Group<\/strong> does not provide free technical support, but offers a paid <a href=\"https:\/\/artprojectgroup.es\/tienda\/ticket-de-soporte\">technical support<\/a> service for installation and configuration.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>0.6.1<\/h4>\n\n<ul>\n<li>Bundled translation now takes precedence over the centralised language pack: when the plugin ships a <code>.mo<\/code> (or <code>.l10n.php<\/code>) for the current locale under <code>\/languages<\/code>, the <code>lang_dir_for_domain<\/code> filter returns that folder unconditionally, instead of only falling back to it when WordPress could not find anything in <code>wp-content\/languages\/plugins\/<\/code>. Result: the locale shipped by the plugin (currently <code>es_ES<\/code>) is always rendered with the strings packaged in the current release, so new wording and changelog-driven copy adjustments reach users on the same day as the plugin update without waiting for translate.wordpress.org to regenerate its language pack. Locales for which the plugin does not bundle a translation continue to be loaded from the centralised system pack as before.<\/li>\n<\/ul>\n\n<h4>0.6.0<\/h4>\n\n<ul>\n<li>Fixed: the plugin's bundled Spanish translation (and any future locale shipped under <code>\/languages<\/code>) was not being loaded on sites where translate.wordpress.org had not yet generated a language pack. WordPress's just-in-time loader only inspects <code>wp-content\/languages\/plugins\/<\/code>, so the <code>.mo<\/code> file shipped inside the plugin was ignored. The plugin now hooks the WordPress 6.6+ <code>lang_dir_for_domain<\/code> filter to return the bundled <code>\/languages<\/code> folder when no language pack is available, without resorting to the WP.org-discouraged <code>load_plugin_textdomain()<\/code> call. On WordPress 6.5 and below the bundled translation will still not load until a language pack is published, but the plugin keeps working in the source language.<\/li>\n<li>WooCommerce 10.9 transactional-email log integration: the three <code>WC_Email<\/code> classes shipped by the plugin (customer acknowledgement, admin notification, status update) are picked up automatically by the new built-in <code>EmailLogger<\/code>. Each log entry is enriched via <code>woocommerce_email_log_context<\/code> with the withdrawal request id, scope and the SHA-256 receipt hash plus its UTC timestamp, so the log entry can be cross-checked against the email actually delivered to the customer. The filter is silently ignored on older WooCommerce versions.<\/li>\n<li>Article 11a label safety net: a new helper (<code>apg_withdrawal_label_is_ambiguous()<\/code>) flags withdrawal labels that fail the \"unambiguous wording\" requirement of Article 11a of Directive 2011\/83\/EU. Used in two places: (a) the configurable confirmation button label (<code>Settings \u2192 Button text<\/code>) now surfaces an admin warning if the merchant saves an ambiguous wording (e.g. \"Cont\u00e1ctanos\", \"Gestionar pedido\", \"Volver\"); the choice is not blocked, only flagged; (b) the <code>[apg_withdrawal_link]<\/code> shortcode and the matching <code>apg-withdrawal\/link<\/code> Gutenberg block emit a <code>_doing_it_wrong()<\/code> notice (visible with <code>WP_DEBUG<\/code>) when an ambiguous custom <code>label<\/code> attribute is used. Lists of \"passes\" and \"blacklist\" terms are filterable via <code>apg_withdrawal_label_unambiguous_terms<\/code> and <code>apg_withdrawal_label_ambiguous_terms<\/code>.<\/li>\n<li>Annex I.B link redesign on the public form: replaced the single long inline link with a short descriptive sentence (\"If you prefer, you can use the official model (Annex I.B).\") plus a \"Print\" button styled with the native WooCommerce button classes. The button appends <code>?print=1<\/code> to the Annex I.B URL so the browser print dialog opens automatically on page load (the existing on-page Print button still works for users who reach the URL directly).<\/li>\n<li>Annex I.B addressee block polish: the merchant state and country are now rendered with their human-readable names (via <code>WC()-&gt;countries-&gt;get_countries()<\/code> and <code>get_states()<\/code>) instead of the raw <code>XX:YY<\/code> ISO code stored in <code>woocommerce_default_country<\/code>, each on its own paragraph. Every paragraph in the addressee block ends with a period, as expected in formal correspondence.<\/li>\n<li>WPML \/ Polylang integration for merchant-configurable strings: the confirmation button text, the digital-content waiver custom label and the four per-type exclusion notices are now registered with WPML's String Translation (Polylang ships a compatibility layer for the same <code>wpml_register_single_string<\/code> action) on <code>init<\/code>. When neither plugin is active the registration is a no-op and the original value is rendered unchanged.<\/li>\n<li>Rate limiting + honeypot on the public form: a new <code>apg_withdrawal_is_rate_limited()<\/code> helper throttles repeated submissions by IP + email pair (default policy: 5 attempts per 10 minutes, both filterable via <code>apg_withdrawal_rate_limit_max<\/code> and <code>apg_withdrawal_rate_limit_window<\/code>). A hidden honeypot field is rendered in both steps of the form and silently swallows automated submissions before any persistence happens. Client IP detection respects common reverse-proxy headers and is filterable via <code>apg_withdrawal_client_ip<\/code>.<\/li>\n<li>Verified Article 11a continuous visibility throughout the 14-day window: the My Account \u2192 Orders \"Withdraw from the contract here\" action is shown for every order whose withdrawal deadline is still open, regardless of the WooCommerce status, with the deadline source automatically falling back from <code>completed_date<\/code> to <code>created_date<\/code> for orders that have not yet been marked complete. No code change required.<\/li>\n<\/ul>\n\n<h4>0.5.0<\/h4>\n\n<ul>\n<li>Compliance with Directive (EU) 2023\/2673 (amends Directive 2011\/83\/EU on consumer rights). The plugin now covers the additional obligations introduced by the new Article 11a (online withdrawal function) plus the related pre-contractual and burden-of-proof requirements.<\/li>\n<li>Category-level <em>Withdrawal type<\/em> term meta on <code>product_cat<\/code>, with automatic inheritance for products that keep the \"Withdrawal allowed (default)\" value. When a product belongs to several categories with conflicting types, the most restrictive type wins (priority order: <code>excluded<\/code> &gt; <code>personalized<\/code> &gt; <code>digital<\/code> &gt; <code>manual<\/code> &gt; <code>allowed<\/code>).<\/li>\n<li>New <code>[apg_withdrawal_notice]<\/code> shortcode, matching <code>apg-withdrawal\/notice<\/code> Gutenberg block and <code>woocommerce_single_product_summary<\/code> injection (priority 20, between the price and the Add to Cart button) that automatically displays the exclusion notice on the product page when the effective withdrawal type is not <code>allowed<\/code>.<\/li>\n<li>New plugin settings section \"Exclusion notice texts\" with one editable textarea per non-default type (<code>excluded<\/code>, <code>digital<\/code>, <code>personalized<\/code>, <code>manual<\/code>) and a translated default text per type. Optional per-product override field on the <em>Withdrawal<\/em> product data tab to customise the notice for a single product.<\/li>\n<li>\"Digital content waiver\" settings section simplified to a single excluding selector with three modes \u2014 <code>Never (disabled)<\/code>, <code>On products classified as digital content<\/code>, <code>On every order<\/code> \u2014 driven exclusively by the per-product \/ per-category withdrawal type. Legacy installations with mode <code>virtual<\/code> are migrated to <code>digital<\/code>; mode <code>specific<\/code> is migrated to <code>digital<\/code> and the previously selected categories \/ products are automatically marked with <code>_apg_withdrawal_type = digital<\/code> to preserve their behaviour. The legacy <code>digital_waiver_categories<\/code> \/ <code>digital_waiver_products<\/code> settings stop being honoured at the UI level (a one-time silent migration runs on <code>init<\/code>, flagged by the <code>apg_withdrawal_migrated_to_0_5<\/code> option).<\/li>\n<li>New printable Annex I.B model withdrawal form served at <code>?apg_withdrawal_model_form=1<\/code> with <code>@media print<\/code> styling, pre-populated with the store name, address, email (from WooCommerce settings) and an optional merchant phone (new <code>Merchant phone (optional)<\/code> plugin setting). The public withdrawal request form links to it as \"Download the official model withdrawal form (Annex I.B)\".<\/li>\n<li>New <code>[apg_withdrawal_link]<\/code> shortcode and <code>apg-withdrawal\/link<\/code> Gutenberg block to render a link to the public withdrawal form with optional <code>label<\/code>, <code>class<\/code> and <code>target<\/code> attributes. The default label uses the literal wording suggested by Article 11a(1) (\"Withdraw from the contract here\"). The My Account per-order action label has been updated to the same default for new installs.<\/li>\n<li>Customer acknowledgement email now includes a verifiable SHA-256 hash of the receipt content (computed over name + email + order + scope + products + details + UTC timestamp) and the UTC timestamp used for verification. Hash and timestamp are also persisted in post meta (<code>_apg_withdrawal_receipt_hash<\/code>, <code>_apg_withdrawal_receipt_hash_timestamp<\/code>) and exposed in the CSV export.<\/li>\n<li>Digital-content waiver consent at checkout is now persisted as a structured log (<code>_apg_withdrawal_digital_waiver_log<\/code> order meta) that includes the exact label shown to the customer, UTC timestamp, IP, user agent and checkout type (<code>classic<\/code> or <code>block<\/code>). The legacy <code>_apg_withdrawal_digital_waiver<\/code> boolean meta is also written for backwards compatibility.<\/li>\n<li>Email delivery indicator: every status-change email and the initial customer acknowledgement now record whether <code>wp_mail()<\/code> was invoked, whether it returned success (= \"accepted by the mailer\", not actual recipient delivery), the UTC timestamp and any error captured through <code>wp_mail_failed<\/code>. The information is surfaced in the request detail screen and exported as two additional CSV columns.<\/li>\n<li>GDPR integration: the plugin now registers a personal-data exporter and a personal-data eraser with the native WordPress privacy tools. The eraser <strong>anonymises<\/strong> withdrawal requests (replaces name, email, phone, IP, user agent and customer-supplied free text with <code>[redacted]<\/code>) and keeps the record itself plus the <code>_apg_withdrawal_wc_order_id<\/code> reference for legal evidence, in line with the burden of proof in Article 16 bis(8). The same anonymisation is also triggered automatically when a WordPress user is deleted (via <em>Users \u2192 Delete<\/em>, a customer-facing \"Delete my account\" button shipped by third-party plugins such as <code>apg-gdpr-texts-for-forms<\/code>, or any other path), so the withdrawal records never outlive the user account with personal data attached.<\/li>\n<li>CSV export now defends against spreadsheet formula injection: every cell whose first character is <code>=<\/code>, <code>+<\/code>, <code>-<\/code>, <code>@<\/code>, tab or carriage return is prefixed with an apostrophe before being written via <code>fputcsv<\/code>.<\/li>\n<li>New FAQ entries documenting where the withdrawal link should be placed by the merchant or the web designer and recommending a minimum 5-year retention period for withdrawal request records.<\/li>\n<\/ul>\n\n<h4>0.4.0<\/h4>\n\n<ul>\n<li>New setting \"Custom checkbox text\" in the Digital content waiver section: lets the merchant override the default acknowledgement label rendered at checkout with a custom plain-text string. Leaving the field empty keeps the default translatable text.<\/li>\n<li>The default page auto-created by the plugin now uses the title \"Exercise the right of withdrawal\" (translated to \"Ejercer derecho de desistimiento\" in Spanish) and lets WordPress derive its slug from the title. Existing pages are not modified \u2014 only new installations get the new title and slug.<\/li>\n<li>Internal: corrected the allowed-modes whitelist in the settings sanitiser (<code>disabled<\/code>, <code>virtual<\/code>, <code>all<\/code>, <code>specific<\/code>) so the values now match the actual mode selector.<\/li>\n<\/ul>\n\n<h4>0.3.0<\/h4>\n\n<ul>\n<li>New: digital-content withdrawal waiver checkbox at checkout. Customers buying digital content or virtual services see an optional acknowledgement that requesting the immediate supply waives their right of withdrawal (EU consumer protection requirement). The checkbox is informational; ticking it is not mandatory and does not block order placement.<\/li>\n<li>The checkbox is injected in both checkouts: classic shortcode (via <code>woocommerce_checkout_before_terms_and_conditions<\/code> with priority 999) and block-based (via JavaScript that reinserts itself with a <code>MutationObserver<\/code> to remain right before the native terms checkbox, after any other custom one).<\/li>\n<li>In the block checkout, a generic cleanup pass removes content injected next to our wrapper by third-party plugins whose selectors over-match (e.g. plugins using <code>.wp-block-woocommerce-checkout-terms-block .wc-block-components-checkbox<\/code> plus jQuery <code>.after()<\/code>), avoiding duplicated privacy or marketing notices.<\/li>\n<li>The customer's choice is persisted to order meta <code>_apg_withdrawal_digital_waiver<\/code> (<code>'1'<\/code> or <code>'0'<\/code>) on both checkouts: the classic checkout reads the POST value on <code>woocommerce_checkout_create_order<\/code>, the block checkout injects the value into the StoreAPI request body under <code>extensions['apg-withdrawal']['digital_waiver']<\/code> and the server hook <code>woocommerce_store_api_checkout_update_order_from_request<\/code> writes the same meta.<\/li>\n<li>The block-checkout script reacts to cart changes mid-checkout: it watches StoreAPI cart mutations and, via a nonced AJAX endpoint (<code>apg_withdrawal_check_cart_waiver<\/code>), re-checks server-side whether the current cart still qualifies, inserting or removing the checkbox without a full page reload.<\/li>\n<li>New settings section \"Digital content waiver\" with a single SelectWoo selector for when to show the checkbox: never (default), only on virtual products, on every order, or on products in selected categories or selected products (these two can be combined). Category and product selectors load only when relevant. The \"Only on virtual products\" mode also matches products with the per-product <code>_apg_withdrawal_type = digital<\/code> setting, so virtual flag and explicit digital classification are treated as equivalent triggers.<\/li>\n<\/ul>\n\n<h4>0.2.0<\/h4>\n\n<ul>\n<li>The frontend form now inherits the native WooCommerce stylesheet (notices, fields, buttons) without requiring custom CSS overrides.<\/li>\n<li>Notices rendered with <code>wc_print_notice()<\/code> so they pick up the correct WooCommerce template for both block themes (<code>block-notices\/*.php<\/code>) and classic themes (<code>notices\/*.php<\/code>).<\/li>\n<li>Dynamic notices (order-not-found feedback and product warning) are pre-rendered server-side via <code>wc_print_notice()<\/code> and toggled by JavaScript, instead of being built by hand with legacy markup that breaks on block themes.<\/li>\n<li>Order-not-found feedback follows the native WooCommerce pattern: notice at the top of the form plus <code>woocommerce-invalid<\/code> class on the email field.<\/li>\n<li>Buttons use <code>wc_wp_theme_get_element_class_name( 'button' )<\/code> for theme and block-theme compatibility.<\/li>\n<li>Removed inline CSS injected from JavaScript in favour of native WooCommerce notice classes.<\/li>\n<li>Spanish translation updated to informal \"t\u00fa\" treatment as recommended by the WooCommerce style guide.<\/li>\n<\/ul>\n\n<h4>0.1.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<\/ul>","raw_excerpt":"Add to WooCommerce an online withdrawal workflow with customer form, My Account integration and admin request log.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/311450","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=311450"}],"author":[{"embeddable":true,"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/artprojectgroup"}],"wp:attachment":[{"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=311450"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=311450"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=311450"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=311450"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=311450"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/pcd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=311450"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}