TL;DR: This page compares free open-source modules with
CopeX_OrderWithdrawalfor the EU right of withdrawal button mandated by Directive (EU) 2023/2673, effective in every EU online shop from 19 June 2026. Goal: a technically honest decision aid for shop owners and developers — not a pure marketing brochure.
DE: A German version of this document is available at
README_DE.md.
- Why this comparison?
- Modules at a glance
- Full feature matrix
- Security analysis: double opt-in vs. direct submit
- GDPR / privacy: where does the customer's email end up?
- Code quality & update safety
- Legal basis: what does the EU require?
- Who should pick what?
- FAQ
- Contact / demo
EU Directive 2023/2673 (modernisation of consumer rights) obliges every online shop selling to EU consumers to expose a clearly identifiable withdrawal button from 19 June 2026 onward. Shops without a compliant solution by then risk cease-and-desist letters (Abmahnung in the German market) — and competing legal firms are watching.
For Magento 2 there are roughly three paths:
- Free open-source modules (OSL-3.0, MIT, AFL) — typically maintained by individual developers or small teams. They cover the baseline.
- In-house build — high effort, full responsibility on you.
copex/module-order-withdrawal— the module from CopeX GmbH, focused on security, compliance depth, refund automation, and business intelligence.
| Typical free module | copex/module-order-withdrawal | |
|---|---|---|
| License | OSL-3.0 / AFL / MIT (open source) | Source code included & fully viewable |
| Magento compatibility | Variable | 2.4.6+ |
| Minimum PHP version | Variable | 8.3 |
| Hyvä theme support | Usually a separate Composer package | Built into the same package (layout switch) |
| Automated tests | Rarely present | 50+ unit tests + Playwright E2E |
| Static analysis | No | phpinsights |
| Roadmap transparency | Variable | Versioned + documented |
Methodology note: The "typical free module" column reflects the picture you find today (May 2026) when inspecting the source code of the most popular open-source Magento 2 withdrawal modules. Individual modules may cover individual points better; the overall pattern is consistent.
Legend: ✅ present · ⚠ partial/limited · ❌ not present · 🛠 in development
| Feature | Free | CopeX |
|---|---|---|
| Withdrawal button in order history | ✅ | ✅ |
| Detail page with order data | ✅ | ✅ |
| Confirmation dialog before submit | ⚠ JS confirm() |
✅ Modal (Luma & Hyvä Alpine) |
| Success page after submit | ✅ | ✅ |
| Public form for guest orders | ✅ (usually search-by-email) | ✅ (with double opt-in) |
| Partial (item-level) withdrawal | ⚠ Some | ✅ Checkbox + qty with already-withdrawn lock |
| Multiple partial submissions per order | ⚠ Some | ✅ |
| Withdrawal reason selection | ❌ | ✅ Per-storeview configurable |
| Comment field | ❌ | ✅ |
| PDF model withdrawal form | ❌ | ✅ Pre-filled with order data |
| Configurable disclaimer text | ❌ | ✅ |
| Feature | Free | CopeX |
|---|---|---|
| Double opt-in via tokenised email | ❌ | ✅ Configurable |
| Server-side reCAPTCHA enforcement | ❌ | ✅ Predispatch observer |
| CSRF protection (form key) | ✅ | ✅ |
| Customer email as URL query parameter | ⚠ Common (GDPR risk) | ❌ Not in URLs |
| Token values in REST API responses | n/a | ❌ Deliberately excluded |
| Configurable token expiry | n/a | ✅ |
| Cron cleanup of orphaned tokens | n/a | ✅ Daily at 02:00 |
| IDOR protection on order_id | ⚠ Usually only email-match | ✅ Customer-ID match |
| Feature | Free | CopeX |
|---|---|---|
| Period counted from last shipment date | ✅ | ✅ |
| Always-allow for unshipped orders | ✅ | ✅ |
| Expected-shipping-days buffer | ❌ | ✅ Configurable |
Grace period with needs_validation status |
❌ Hard "expired" | ✅ Configurable |
| Configurable allowed order statuses | ⚠ Some | ✅ |
| Mandatory acknowledgement-of-receipt email to customer | ✅ | ✅ |
| Reason code with label snapshot | ❌ | ✅ Audit-safe |
| Order-history comment on submit | ✅ | ✅ |
| Order-history comment on confirm | n/a | ✅ |
| Feature | Free | CopeX |
|---|---|---|
| Admin grid under Sales > Withdrawals | ✅ | ✅ |
| Inline status edit | ⚠ Detail page only | ✅ |
| Detail page with full metadata | ⚠ Variable | ✅ |
| Filter: ID, order #, status, date | ✅ | ✅ |
| Filter: withdrawal reason | ❌ | ✅ |
| Filter: type (Full/Partial) | ⚠ When partial exists | ✅ |
| Filter: linked order status | ❌ | ✅ |
| Bulk actions (mass approve/decline) | ⚠ Some | ✅ |
| Row action "Approve" / "Decline" | ⚠ Some | ✅ Context-sensitive |
| Autocomplete for unresolved orders | ❌ | ✅ |
| Auto-refund (create credit memo) | ❌ | ✅ Full & partial |
| ACL-gated backend permissions | ✅ | ✅ |
| Feature | Free | CopeX |
|---|---|---|
| REST API – list withdrawals | ⚠ GET only | ✅ |
| REST API – CRUD | ❌ | ✅ Full CRUD (5 endpoints) |
| REST API – items endpoint (partial) | ❌ | ✅ |
| GraphQL schema + resolvers | ❌ | ✅ Query + mutation |
| ACL-gated API | ✅ | ✅ |
You can build webhook / ERP integrations on top of REST or GraphQL yourself. A dedicated webhook subsystem inside the module would be redundant surface area.
| Feature | Free | CopeX |
|---|---|---|
| Minimum PHP version | Variable | 8.3 |
| Minimum Magento version | Variable | 2.4.6 |
declare(strict_types=1); |
⚠ Partial | ✅ Consistent |
| Service-oriented architecture | ⚠ Logic often in controllers | ✅ Clear separation |
| Repository pattern | ⚠ Mixed | ✅ Complete |
| Unit tests | Rare | ✅ 50+ tests |
| Playwright E2E | ❌ | ✅ Including Mailpit verification |
| Static analysis | ❌ | ✅ phpinsights |
| Feature | Free | CopeX |
|---|---|---|
| Hyvä-compatible | ⚠ Separate package needed | ✅ Same package |
| Tailwind templates | ⚠ Only with compat package | ✅ |
| Alpine.js components | ⚠ Only with compat package | ✅ |
| Mechanism | Composer sub-package | hyva_default layout handle |
| CSP compatibility (PCI-DSS 4.0) | ⚠ Not documented | ✅ CSP-safe by default |
This is by far the most important security differentiator between our module and many free implementations.
Many open-source modules implement the guest-withdrawal flow by simply looking up order number + email in the order repository and, on a match, redirecting the visitor straight to the submit view — with the email address passed in the URL query string.
The problem: Order numbers are sequential (100000001, 100000002, ...). Email addresses appear on every invoice, every delivery note, every order confirmation email. Anyone who gets hold of that data — through a leak, a database dump, a shared invoice, a phishing attack, or a disgruntled ex-employee — can submit withdrawals for someone else's orders without any further verification.
There is no reCAPTCHA, no token, no identity check.
In default mode CopeX requires a two-step verification:
- Customer submits order number + email + reCAPTCHA
- CopeX creates a record with status
awaiting_confirmationand a 64-character token - Customer receives an email with a confirmation link
- Only after clicking → status
pending→ final processing
Result: Only someone who controls the customer's email inbox can complete the withdrawal. Database leaks and invoice access are not sufficient.
Additionally: reCAPTCHA is enforced server-side in a predispatch observer — a client-side bypass via bot scripts is not possible.
Optionality: If you don't want the double-opt-in friction (e.g. because reCAPTCHA + IP rate-limiting are enough), you can disable it via copex_orderwithdrawal/general/require_email_confirmation = 0. Default remains enabled.
A disgruntled ex-employee of a mid-sized B2C shop still has access to an old DB export from their time there. With 50,000 order records, they set up a script that submits a withdrawal per order.
- With a direct-submit module: 50,000 valid withdrawal requests land in the admin grid. The shop has to verify and reject each one — effort: 5-10 min per record, so 4,000-8,000 hours of admin work. Meanwhile real withdrawals pile up in the same inbox = get overlooked = legal risk.
- With CopeX (double opt-in enabled): 50,000 emails go out to the real customers. Since the script doesn't have access to their inboxes, nobody clicks the link. Zero filed withdrawals. The cron cleans up after 24 h.
A subtle but GDPR-relevant detail in many free implementations.
The guest-view URL carries the email address directly in the query string, e.g. /withdrawal/guest/view?order_id=12345&email=max.mustermann@example.com. This means:
| Location | Email stored |
|---|---|
| Browser history | ✅ In plaintext |
| Browser bookmarks | ✅ In plaintext |
| Apache/Nginx access logs | ✅ In plaintext |
| Referer headers (on click to external links) | ✅ In plaintext |
| Reverse proxies (Cloudflare, Varnish) | ✅ In plaintext |
| CDN logs | ✅ In plaintext |
| Browser extensions | ✅ In plaintext |
A personally identifying information item (email = personal data per GDPR Art. 4(1)) thus ends up in at least 5-7 locations outside the shop's control. In case of a data breach (log leak, Cloudflare misconfiguration) this is reportable under GDPR Art. 33.
Emails only ever live in the POST body and in the copex_orderwithdrawal table. URLs only contain:
- Unguessable 64-character tokens (
/withdrawal/request/confirm?token=Ab7…) - Numeric order IDs (only for logged-in customers, with an IDOR guard)
No personal data in the URL path or query string.
In most free Magento 2 withdrawal modules the test suite is empty or absent. CopeX ships 50+ PHPUnit unit tests plus a Playwright E2E suite covering the guest public form, registered order history, partial withdrawal, and the PDF cancellation form.
Practical meaning: On every Magento update (typically 2-4 per year), with a free module you have to manually test whether everything still works. With CopeX you run the test suite and it tells you immediately if something broke.
So you understand what your module actually has to cover.
- Art. 9: 14-day withdrawal period from receipt of goods
- Art. 11: Withdrawal declaration "on a durable medium" — i.e. an email acknowledgement of receipt is legally mandatory
- Annex I (A): model withdrawal instructions
- Annex I (B): model withdrawal form — must be offered as an alternative form
→ Most modules implement the period (Art. 9) and the acknowledgement (Art. 11). The Annex I (B) PDF download is delivered by very few — CopeX does.
- Art. 11a (new): "A clearly identifiable withdrawal button" in the trader's online interface
- Art. 11a(3): The button may not impose preconditions beyond those required to identify the customer
- Art. 11a(4): Immediate confirmation of withdrawal receipt on a durable medium
→ "Reason as a mandatory field" would be unlawful (it violates 11a(3)). For that reason, CopeX makes the reason field optional only, never required.
What you need by 19 June 2026:
- ✅ Withdrawal button (every serious module delivers this)
- ✅ Period calculation from last shipment (most modules)
- ⚠ Optional identity-verification step (Free: usually no real verification, CopeX: double opt-in)
- ✅ Acknowledgement-of-receipt email to customer (most modules)
- ⚠ Model withdrawal form as PDF (typically only premium modules) — debated whether strictly required by 2023/2673 or whether the online form suffices. The PDF route is the only certain one.
- ⚠ Audit capability: who withdrew what when, and when was it verified? — fully tracked in CopeX, basic-info-only in most free modules.
Honest recommendation, against our own sales pressure:
- You run a hobby/mini shop (< 50 orders per month, < 5 withdrawals per year)
- You have strong in-house Magento 2 development that can patch and test on its own
- You're stuck on PHP 7.4 or 8.0 and can't upgrade (legacy stack)
- You only need the statutory minimum and don't want analytics
- You run a B2C shop with meaningful volume (> 100 orders/month)
- You care about data protection (you have a data protection officer / are compliance-obliged)
- You want to analyse withdrawal reasons to improve products
- You have many orders → many potential refunds and want to save admin time
- You want to upgrade or already run on modern PHP (8.3+)
- You run Hyvä with PWA / headless and need GraphQL
- You want EU 2023/2673 compliance without doing the audit yourself
Can I see the source code? Yes. The complete source code ships with the module and is fully viewable.
Does the module work with Adobe Commerce (Magento Enterprise)? The module is built entirely on Magento Open Source APIs and therefore also runs on Adobe Commerce. It has been tested with Magento 2.4.6+ on the Luma, Hyvä and MageSuite frontends.
Which reCAPTCHA version is supported? Magento_ReCaptchaUi (v2 Invisible, v2 Checkbox, v3). Configurable per store view.
Is there a trial? Yes. A 14-day free trial is available — contact office@copex.io.
CopeX GmbH Web: https://copex.io Email: office@copex.io Product page & pricing: https://copex.io/module/order-withdrawal/ — more details and current pricing Module docs: https://docs.copex.io/modules/order-withdrawal/
Disclaimer: This document is maintained by CopeX GmbH and at its core is an argument for our own product. We strive for fairness and accurate comparison. The claims about "typical" free modules describe patterns we observe when inspecting the source code of the most popular free Magento 2 withdrawal modules — individual modules may cover individual points better. Verify our claims by your own source inspection.
Magento 2 withdrawal · Magento 2 withdrawal button · Magento 2 EU 2023/2673 · Magento 2 right of withdrawal · Magento 2 cancellation form · Magento 2 GDPR withdrawal · Magento 2 module comparison · Magento 2 consumer rights directive · Magento 2 Hyvä withdrawal · Magento 2 PWA withdrawal GraphQL · Magento 2 partial withdrawal · Magento 2 open source withdrawal · Adobe Commerce withdrawal module · Magento Open Source withdrawal extension · Magento 2 Widerruf · Magento 2 Widerrufsbutton · Magento 2 Widerrufsformular · Magento 2 Datenschutz Widerruf · Magento 2 Verbraucherrechte · Magento 2 Teilwiderruf · Magento 2 Open Source Widerruf