{"id":311673,"date":"2026-06-03T09:34:08","date_gmt":"2026-06-03T09:34:08","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/right-of-withdrawal-for-woocommerce\/"},"modified":"2026-09-10T11:10:38","modified_gmt":"2026-09-10T11:10:38","slug":"right-of-withdrawal-for-woocommerce","status":"publish","type":"plugin","link":"https:\/\/ko.wordpress.org\/plugins\/right-of-withdrawal-for-woocommerce\/","author":18481876,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.1.0","stable_tag":"1.1.0","tested":"7.1.1","requires":"6.0","requires_php":"8.0","requires_plugins":null,"header_name":"Right of Withdrawal flow for shop","header_author":"Teet Bergmann (teetb), Martin Tee (peeterpaanike)","header_description":"EU Directive 2011\/83\/EU compliant right-of-withdrawal flow \u2014 14-day cooling-off period, item-level partial returns, server-side refund math, IBAN validation, admin workflow and HTML emails. HPOS-ready.","assets_banners_color":"","last_updated":"2026-09-10 11:10:38","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":0,"downloads":278,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"teetb","date":"2026-06-03 09:32:45","revision":3559107},"1.1.0":{"tag":"1.1.0","author":"teetb","date":"2026-09-10 11:10:38","revision":3689794}},"upgrade_notice":{"1.1.0":"<p>Adds a Withdrawal column and filter to the orders list, sends emails from the WooCommerce sender address and adds withdrawal for guest orders via a secure emailed link. To enable guest withdrawal, create a page with the [rowfs_withdrawal_form] shortcode and select it in the plugin settings.<\/p>","1.0.0":"<p>First public release.<\/p>"},"ratings":[],"assets_icons":[],"assets_banners":[],"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.0","1.1.0"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[],"plugin_tags":[255182,263925,9682,55969,245590],"plugin_category":[],"plugin_contributors":[182393,234235],"plugin_business_model":[],"class_list":["post-311673","plugin","type-plugin","status-publish","hentry","plugin_tags-consumer-rights","plugin_tags-eu-directive","plugin_tags-refund","plugin_tags-returns","plugin_tags-withdrawal","plugin_contributors-peeterpaanike","plugin_contributors-teetb","plugin_committers-teetb"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/s.w.org\/plugins\/geopattern-icon\/right-of-withdrawal-for-woocommerce.svg","icon_2x":false,"generated":true},"screenshots":[],"raw_content":"<!--section=description-->\n<p>Right of Withdrawal flow for shop implements the consumer right of withdrawal mandated by the European Union <a href=\"https:\/\/eur-lex.europa.eu\/legal-content\/EN\/TXT\/?uri=CELEX%3A32011L0083\">Consumer Rights Directive (2011\/83\/EU)<\/a>, giving customers a clean, server-validated way to exercise their 14-day cooling-off period.<\/p>\n\n<p>The plugin adds a \"Withdraw from order\" button in the customer's account, opens an accessible modal where customers can pick specific items, enter an IBAN and select a withdrawal reason, then notifies the shop via HTML email and tracks the refund through to completion.<\/p>\n\n<h4>Key features<\/h4>\n\n<ul>\n<li>Full or partial withdrawal \u2014 customers select specific items and quantities.<\/li>\n<li><strong>Server-side amount calculation<\/strong> \u2014 line totals are read from the order, never trusted from the client. Coupon discounts are correctly accounted for via <code>WC_Order_Item::get_total()<\/code>.<\/li>\n<li><strong>IBAN format + Mod-97 validation<\/strong> (works in any EU country with <code>bcmod<\/code> or pure-PHP fallback).<\/li>\n<li>Order-meta storage (withdrawal date, IBAN, reason, items, refund status).<\/li>\n<li>Admin metabox with status updates: <em>Mark as refunded<\/em> \/ <em>Mark as partially refunded<\/em>.<\/li>\n<li>\"Withdrawal\" column and filter in <strong>WooCommerce \u2192 Orders<\/strong> \u2014 see pending requests at a glance without opening each order.<\/li>\n<li>Optional automatic creation of an order refund record (with optional restock).<\/li>\n<li><strong>Guest checkout support<\/strong> \u2014 a withdrawal form page (<code>[rowfs_withdrawal_form]<\/code> shortcode) where customers without an account enter their order number and email and receive a secure, signed withdrawal link. Guest orders also get this link in the order email.<\/li>\n<li>Direct link to the withdrawal modal from the order confirmation email. For registered customers the link opens their account \"View order\" page with standard WordPress \/ WooCommerce authentication \u2014 no custom login, no auto-login.<\/li>\n<li>HTML emails for both customer and admin; styled inline for Gmail\/Outlook compatibility.<\/li>\n<li>HPOS (Custom Order Tables) compatible.<\/li>\n<li>Settings page nested under the <strong>WooCommerce<\/strong> menu.<\/li>\n<\/ul>\n\n<h4>Compliance reference<\/h4>\n\n<ul>\n<li><a href=\"https:\/\/eur-lex.europa.eu\/legal-content\/EN\/TXT\/?uri=CELEX%3A32011L0083\">Directive 2011\/83\/EU on consumer rights<\/a>, Articles 9\u201314 (Right of withdrawal).<\/li>\n<li>The 14-day period is configurable; the plugin defaults to the directive's prescribed minimum.<\/li>\n<li>Refunds are processed to the bank account supplied by the consumer (IBAN), satisfying the directive's requirement to use the same payment means <em>unless<\/em> the consumer explicitly agrees to another method.<\/li>\n<\/ul>\n\n<h4>Requirements<\/h4>\n\n<ul>\n<li>WooCommerce 7.0 or later<\/li>\n<li>PHP 8.0 or later<\/li>\n<li>WordPress 6.0 or later<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin folder to <code>\/wp-content\/plugins\/<\/code>.<\/li>\n<li>Activate the plugin through the <strong>Plugins<\/strong> menu in WordPress.<\/li>\n<li>Go to <strong>WooCommerce \u2192 Settings \u2192 Right of Withdrawal<\/strong> and configure the settings.<\/li>\n<li>Pick the terms-and-conditions page and customise the button appearance.<\/li>\n<li>For guest orders: create a page with the <code>[rowfs_withdrawal_form]<\/code> shortcode and select it under <strong>General \u2192 Withdrawal form page (guests)<\/strong>.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"is%20the%20plugin%20hpos%20compatible%3F\"><h3>Is the plugin HPOS compatible?<\/h3><\/dt>\n<dd><p>Yes. The plugin declares full compatibility with the WooCommerce Custom Order Tables (HPOS) and uses the <code>WC_Order<\/code> meta API throughout \u2014 never <code>get_post_meta()<\/code> on the order ID.<\/p><\/dd>\n<dt id=\"does%20the%20refund%20amount%20account%20for%20coupon%20discounts%3F\"><h3>Does the refund amount account for coupon discounts?<\/h3><\/dt>\n<dd><p>Yes. The plugin reads <code>WC_Order_Item::get_total()<\/code> (the post-coupon line total) plus <code>get_total_tax()<\/code>, so a customer returning a coupon-discounted item gets the correct, post-discount refund.<\/p><\/dd>\n<dt id=\"how%20do%20i%20change%20the%20withdrawal%20period%3F\"><h3>How do I change the withdrawal period?<\/h3><\/dt>\n<dd><p>Default is the EU-mandated 14 days. You can change the number and the unit (days \/ months \/ years) in the settings, and choose whether the period starts from the order's payment date or completion date.<\/p><\/dd>\n<dt id=\"does%20the%20email%20button%20log%20the%20customer%20in%20automatically%3F\"><h3>Does the email button log the customer in automatically?<\/h3><\/dt>\n<dd><p>No. The button in the order email is a regular link to the customer's \"View order\" page. If the customer is not signed in, WordPress \/ WooCommerce shows the standard login form and returns the customer to the same page after authentication. The plugin does not implement its own login flow. For guest orders the button opens the withdrawal form page via a signed link that authorises only the withdrawal form for that order \u2014 it never logs anyone in.<\/p><\/dd>\n<dt id=\"where%20does%20the%20customer%20start%20a%20withdrawal%20request%3F\"><h3>Where does the customer start a withdrawal request?<\/h3><\/dt>\n<dd><p>Registered customers: the \"Withdraw from order\" action button in <strong>My Account \u2192 Orders<\/strong>, or the same button in the order confirmation email \u2014 both open the modal on the customer's \"View order\" page. Guests (and anyone not signed in): the withdrawal form page with the <code>[rowfs_withdrawal_form]<\/code> shortcode, or the button in the order email of a guest order.<\/p><\/dd>\n<dt id=\"can%20a%20customer%20submit%20more%20than%20one%20partial%20withdrawal%20for%20the%20same%20order%3F\"><h3>Can a customer submit more than one partial withdrawal for the same order?<\/h3><\/dt>\n<dd><p>Yes. The plugin tracks per-line cumulative withdrawn quantities \u2014 a customer can return 1 of 3 items today, then 1 more next week. Further submissions are blocked once every line has been fully accounted for.<\/p><\/dd>\n<dt id=\"does%20it%20work%20for%20guest%20checkout%20%28no%20customer%20account%29%3F\"><h3>Does it work for guest checkout (no customer account)?<\/h3><\/dt>\n<dd><p>Yes. Create a page, add the <code>[rowfs_withdrawal_form]<\/code> shortcode and select it under <strong>WooCommerce \u2192 Settings \u2192 Right of Withdrawal \u2192 General \u2192 Withdrawal form page (guests)<\/strong>. The customer enters the order number and the email address used for the order; if they match an order that is still within the withdrawal period, a secure link is emailed to the billing address. The link opens the same withdrawal form as in My Account. Guest orders also get the link directly in the order confirmation email.<\/p>\n\n<p>The link is signed (HMAC over order ID, order key, billing email and expiry), is valid until the withdrawal period ends and only authorises the withdrawal form for that one order \u2014 it does not log anyone in. The lookup form always shows the same response, so it cannot be used to check whether an order or email exists, and it is rate-limited per IP.<\/p><\/dd>\n<dt id=\"which%20countries%27%20ibans%20are%20accepted%3F\"><h3>Which countries' IBANs are accepted?<\/h3><\/dt>\n<dd><p>Any country that uses IBAN. The plugin validates the standard format (2-letter country code + 2 check digits + up to 30 alphanumeric characters) and runs the Mod-97 checksum (uses <code>bcmod<\/code> if available, falls back to pure PHP). Non-IBAN formats are rejected at submit time.<\/p><\/dd>\n<dt id=\"what%20happens%20when%20the%20admin%20clicks%20%22mark%20as%20refunded%22%3F\"><h3>What happens when the admin clicks \"Mark as refunded\"?<\/h3><\/dt>\n<dd><p>If \"Create WooCommerce refund automatically\" is enabled (default), a <code>WC_Order_Refund<\/code> record is created with the exact line items and amount the customer requested; the order totals update accordingly, and stock is restocked if enabled. A confirmation email is then sent to the customer. Without auto-refund, only the order status and metadata are updated \u2014 no <code>WC_Order_Refund<\/code> is created.<\/p><\/dd>\n<dt id=\"how%20do%20i%20see%20which%20orders%20have%20a%20pending%20withdrawal%20request%3F\"><h3>How do I see which orders have a pending withdrawal request?<\/h3><\/dt>\n<dd><p>Go to <strong>WooCommerce \u2192 Orders<\/strong>. The <strong>Withdrawal<\/strong> column shows the state of every request (initiated \/ partial \/ refunded), and the <strong>All withdrawal states<\/strong> dropdown next to the other filters narrows the list to pending or processed requests. When requests are waiting, a notice at the top of the orders list links straight to them.<\/p><\/dd>\n<dt id=\"which%20address%20are%20the%20plugin%27s%20emails%20sent%20from%3F\"><h3>Which address are the plugin's emails sent from?<\/h3><\/dt>\n<dd><p>The sender name and address set in <strong>WooCommerce \u2192 Settings \u2192 Emails \u2192 Sender options<\/strong> \u2014 the same as your regular order emails. If an SMTP plugin forces its own \"From\" address, that setting takes precedence. The <code>rowfs_email_headers<\/code> filter lets you change the headers further.<\/p><\/dd>\n<dt id=\"where%20can%20i%20find%20the%20plugin%27s%20settings%3F\"><h3>Where can I find the plugin's settings?<\/h3><\/dt>\n<dd><p><strong>WooCommerce \u2192 Settings \u2192 Right of Withdrawal<\/strong>. The tab has five sub-tabs: General (notifications, deadline, terms page), Refund behaviour (auto-refund, restock), Withdrawal button (label, colours), Modal appearance (colours, opacity, width) and Required fields in modal (reason, IBAN, contact email, terms checkbox).<\/p><\/dd>\n<dt id=\"can%20i%20customise%20the%20list%20of%20withdrawal%20reasons%20shown%20to%20the%20customer%3F\"><h3>Can I customise the list of withdrawal reasons shown to the customer?<\/h3><\/dt>\n<dd><p>Yes. The reasons are filterable. In a child-theme <code>functions.php<\/code> or a small custom plugin:<\/p>\n\n<pre><code>add_filter('rowfs_withdrawal_reasons', function ($reasons) {\n    $reasons['gift_unsuitable'] = __('Gift was unsuitable', 'your-textdomain');\n    unset($reasons['found_better_price']);\n    return $reasons;\n});\n<\/code><\/pre><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>New: \"Withdrawal\" status column in WooCommerce \u2192 Orders (HPOS and legacy storage).<\/li>\n<li>New: Orders list filter \u2014 Withdrawal pending \/ processed \/ any request.<\/li>\n<li>New: Admin notice on the orders list when withdrawal requests are awaiting processing.<\/li>\n<li>New: Guest withdrawal \u2014 <code>[rowfs_withdrawal_form]<\/code> shortcode page where customers without an account request a secure, signed withdrawal link by order number + billing email.<\/li>\n<li>New: Guest orders get the signed withdrawal link in the order confirmation email (previously the button pointed to My Account, which guests cannot use).<\/li>\n<li>New: Setting \"Withdrawal form page (guests)\" under General.<\/li>\n<li>New: <code>rowfs_find_order_by_number<\/code> filter for stores using custom order numbers.<\/li>\n<li>Fix: The \"Counted from: Order completion date\" setting is now respected \u2014 previously the period was always counted from the payment date.<\/li>\n<li>Fix: Plugin emails are now sent from the WooCommerce sender name and address (WooCommerce \u2192 Settings \u2192 Emails) instead of the WordPress default address.<\/li>\n<li>Fix: Admin notification email links to the correct order edit screen on stores without HPOS.<\/li>\n<li>Tweak: Admin notification email sets Reply-To to the customer's contact address.<\/li>\n<li>Security: Signed-link pages are sent with no-cache and no-referrer headers.<\/li>\n<li>Tested with WordPress 7.1 and WooCommerce 11.1.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial public release.<\/li>\n<li>EU Directive 2011\/83\/EU compliant withdrawal flow.<\/li>\n<li>Inline-styled email button for Gmail\/Outlook compatibility.<\/li>\n<li>Coupon-aware refund math (uses <code>get_total()<\/code> not <code>get_subtotal()<\/code>).<\/li>\n<li>HPOS support; partial-withdrawal item-level selection.<\/li>\n<\/ul>","raw_excerpt":"EU Directive 2011\/83\/EU compliant 14-day right-of-withdrawal flow \u2014 partial returns, IBAN refunds, HTML emails, HPOS-ready.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/311673","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=311673"}],"author":[{"embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/teetb"}],"wp:attachment":[{"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=311673"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=311673"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=311673"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=311673"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=311673"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=311673"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}