PHPackages                             magendoo/magendoowithdrawal - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. magendoo/magendoowithdrawal

ActivePrestashop-module

magendoo/magendoowithdrawal
===========================

Article 11a CRD withdrawal function for PrestaShop: statutory two-step withdrawal flow with a guaranteed acknowledgment of receipt and a full audit trail.

v2.0.1(today)10OSL-3.0PHPPHP &gt;=8.1

Since Jul 23Pushed todayCompare

[ Source](https://github.com/magendooro/magendoowithdrawal)[ Packagist](https://packagist.org/packages/magendoo/magendoowithdrawal)[ Docs](https://github.com/magendooro/magendoowithdrawal)[ Fund](https://magendoo.ro)[ RSS](/packages/magendoo-magendoowithdrawal/feed)WikiDiscussions main Synced today

READMEChangelog (2)Dependencies (1)Versions (3)Used By (0)

Magendoo Withdrawal Function
============================

[](#magendoo-withdrawal-function)

EU Article 11a (Directive (EU) 2023/2673) withdrawal function for PrestaShop.

Article 11a, inserted into the Consumer Rights Directive 2011/83/EU by Directive (EU) 2023/2673, requires every B2C shop that concludes distance contracts online to offer a "withdrawal function": a statutory two-step withdraw/confirm flow with a guaranteed acknowledgment of receipt on a durable medium, and a record of the request. The obligation applies from **19 June 2026**.

This module implements that function end to end: a storefront statement flow, a receipt-only acknowledgment email, a back-office grid and workflow, and a full audit trail — with no foreign key from the compliance record to the order, so the record survives even if the order is later deleted.

- **Compatibility:** PrestaShop 9.0.0 and above (`ps_versions_compliancy` in `magendoowithdrawal.php` pins `min: 9.0.0`, `max` = the installed core version). PHP 8.1+.
- **License:** OSL-3.0 (Open Software License 3.0).
- **Version:** 2.0.1.
- **Package:** `magendoo/magendoowithdrawal` on [Packagist](https://packagist.org/packages/magendoo/magendoowithdrawal).

Screenshots
-----------

[](#screenshots)

[![Storefront – start](docs/screenshots/01-storefront-start.jpg)](docs/screenshots/01-storefront-start.jpg) Storefront: start / lookup[![Storefront – form](docs/screenshots/02-storefront-form.jpg)](docs/screenshots/02-storefront-form.jpg) Storefront: withdrawal statement form[![Storefront – review](docs/screenshots/03-storefront-review.jpg)](docs/screenshots/03-storefront-review.jpg) Storefront: review before confirming[![Storefront – success](docs/screenshots/04-storefront-success.jpg)](docs/screenshots/04-storefront-success.jpg) Storefront: success page with reference[![Acknowledgment email](docs/screenshots/05-acknowledgment-email.jpg)](docs/screenshots/05-acknowledgment-email.jpg) Acknowledgment-of-receipt e-mail[![Admin grid](docs/screenshots/06-admin-grid.jpg)](docs/screenshots/06-admin-grid.jpg) Back office: Withdrawal Requests grid[![Admin detail](docs/screenshots/07-admin-detail.jpg)](docs/screenshots/07-admin-detail.jpg) Back office: request detail and workflowFeatures
--------

[](#features)

- **Storefront flow** — guest (order-reference + e-mail lookup) and logged-in customer paths through `start` → `form` → `review` → `submit` → `success` front controllers, each a plain `ModuleFrontController` rendering Smarty templates.
- **Statutory verbatim labels** — the function link and the confirmation-control label are the wording from the EUR-Lex Official Journal text, provided in English, German and Romanian (`Magendoo\Withdrawal\Service\Labels`). A merchant may override either label from the configuration screen; leaving them empty keeps the statutory wording.
- **Receipt-only acknowledgment** — the customer e-mail sent after a submission is worded as an acknowledgment of *receipt* only; it never says "accepted" or "approved" (`AcknowledgmentSender`
    - the `withdrawal_ack` mail templates in `mails/en|de|ro/`).
- **Late submissions accepted and flagged** — because the withdrawal period can be extended up to 12 months under Article 10, a submission made after the statutory period is, by default, accepted and flagged as late (`is_late`) rather than rejected outright; a "block after the period ends" mode is also available.
- **Per-line withdrawal quantities** — a customer can withdraw part of an order; each item line tracks its own withdrawn quantity to 4 decimal places, and remaining withdrawable quantity is recomputed against everything already withdrawn for that order.
- **Back-office grid and workflow** — Sales → Withdrawal Requests lists every request (shop scoped) and a detail view drives approve / decline / complete / cancel transitions plus a manual "resend acknowledgment" action (`AdminMagendooWithdrawalController`).
- **Retention anonymiser** — closed requests past a configurable retention window have their name, e-mail and IP redacted while the compliance skeleton (reference, status, dates, item lines) is kept (`Magendoo\Withdrawal\Service\Anonymizer`). There is no hard-delete path.
- **Token-guarded cron endpoint** — a single URL retries pending acknowledgment e-mails and runs the retention anonymiser; guarded by a random token generated at install (`controllers/front/cron.php`).
- **Shop-scoped / multistore-aware** — every table and query carries `id_shop`; the admin grid and the eligibility/repository services all filter by the current shop context.
- **UTC evidence timestamps** — submission, acknowledgment and anonymisation timestamps are stored as UTC so the audit trail's evidentiary timestamps are unambiguous regardless of the shop's display timezone.

Installation
------------

[](#installation)

Requires PHP 8.1+ and PrestaShop 9.0.0 or above. The module is distributed through [Packagist](https://packagist.org/packages/magendoo/magendoowithdrawal) as `magendoo/magendoowithdrawal` (`type: prestashop-module`).

### 1. Get the module into `modules/magendoowithdrawal`

[](#1-get-the-module-into-modulesmagendoowithdrawal)

**Composer-managed PrestaShop project (recommended).** PrestaShop core does not place `prestashop-module` packages on its own, so use PrestaShop's official [`composer-script-handler`](https://github.com/PrestaShop/composer-script-handler) in your shop's root `composer.json`:

```
{
    "require-dev": {
        "prestashop/composer-script-handler": "^0.6"
    },
    "scripts": {
        "post-install-cmd": [ "PrestaShop\\Composer\\ScriptHandler::install" ]
    },
    "extra": {
        "prestashop": {
            "modules": {
                "magendoo/magendoowithdrawal": "^2.0"
            }
        }
    }
}
```

Then run `composer install` from the shop root; the handler downloads the package and unpacks it into `modules/magendoowithdrawal/`. The shipped `vendor/` (the module's own PSR-4 autoloader) is included in the release archive, so no extra Composer step is needed inside the module.

**Manual, without Composer.** Download the release archive from Packagist/GitHub and extract it so the tree lives at `modules/magendoowithdrawal/` (the folder name must be exactly `magendoowithdrawal`), or upload that ZIP through **Modules -&gt; Module Manager -&gt; Upload a module** in the back office.

### 2. Enable it

[](#2-enable-it)

```
php bin/console prestashop:module install magendoowithdrawal
```

Or from the back office: **Modules -&gt; Module Manager**, search "Magendoo Withdrawal Function", click **Install**.

Installation creates two tables (`ps_withdrawal_request`, `ps_withdrawal_request_item`), registers the `displayFooter`, `displayCustomerAccount`, `displayOrderDetail` and `actionFrontControllerSetMedia` hooks, seeds the configuration defaults below, and adds the **Withdrawal Requests** tab under **Sales** (formerly Orders) in the back-office menu.

Configuration
-------------

[](#configuration)

Open **Modules → Module Manager**, find the module, and click **Configure**. The screen is organised into four sections, backed by these `Configuration` keys:

**General**

- `MAGENDOOWITHDRAWAL_ENABLED` — master on/off switch (default: on).
- `MAGENDOOWITHDRAWAL_PERIOD_DAYS` — statutory withdrawal period in days (default: 14).
- `MAGENDOOWITHDRAWAL_TRANSIT_DAYS` — extra days added to the delivery anchor before the period starts (default: 0).
- `MAGENDOOWITHDRAWAL_ANCHOR_STATES` — comma-separated order-state IDs whose entry starts the period; seeded at install from `PS_OS_SHIPPING` / `PS_OS_DELIVERED` (default shop: `4,5`). If empty, the period is treated as not started and the order stays eligible.
- `MAGENDOOWITHDRAWAL_EXCLUDED_STATES` — comma-separated order-state IDs that are never withdrawable; seeded at install from `PS_OS_CANCELED` / `PS_OS_REFUND` (default shop: `6,7`).
- `MAGENDOOWITHDRAWAL_LATE_MODE` — `accept` (accept and flag as late, default) or `block`(reject once the period has ended).

**Presentation**

- `MAGENDOOWITHDRAWAL_SHOW_FOOTER` — show the statutory link in the storefront footer (default: on).
- `MAGENDOOWITHDRAWAL_SHOW_REASON` — offer the customer an optional reason field on the statement form (default: on; the reason is never mandatory).
- `MAGENDOOWITHDRAWAL_FUNCTION_LABEL` / `MAGENDOOWITHDRAWAL_CONFIRM_LABEL` — overrides for the statutory link text / confirmation-button text (default: empty, i.e. use the built-in verbatim wording).

**Notifications**

- `MAGENDOOWITHDRAWAL_MERCHANT_EMAIL` — recipient for new-request notifications (default: empty, falls back to the shop's own e-mail address).
- `MAGENDOOWITHDRAWAL_NOTIFY_MERCHANT` / `MAGENDOOWITHDRAWAL_NOTIFY_CUSTOMER` — toggle merchant new-request e-mails and customer status-change e-mails (both default: on).
- `MAGENDOOWITHDRAWAL_ACK_MAX_ATTEMPTS` — retry ceiling for the acknowledgment e-mail before it is treated as failed (default: 5).
- `MAGENDOOWITHDRAWAL_ALERT_EMAIL` — address alerted once an acknowledgment exhausts its retries (default: empty, falls back to the merchant recipient).

**Privacy &amp; anti-abuse**

- `MAGENDOOWITHDRAWAL_RETENTION_DAYS` — days after which closed requests are anonymised; `0`keeps records indefinitely (default: 0).
- `MAGENDOOWITHDRAWAL_RATE_ATTEMPTS` / `MAGENDOOWITHDRAWAL_RATE_WINDOW` — guest lookup rate limit: max attempts (default: 10) per window in seconds (default: 60), per IP.

Two more keys exist but are not on the form: `MAGENDOOWITHDRAWAL_CRON_TOKEN` (a random token generated at install, used to authorise the cron endpoint) and `MAGENDOOWITHDRAWAL_BO_LINK` (a cached, token-less back-office deep link, captured the first time an administrator opens the grid, used to build the link inside merchant-notification e-mails).

The **Withdrawal Requests** grid and workflow live at **Sales → Withdrawal Requests** in the back office.

### Cron

[](#cron)

The configuration screen displays the exact URL to schedule, built from the module's own token:

```
index.php?fc=module&module=magendoowithdrawal&controller=cron&token=

```

Hitting it (e.g. every 5 minutes from an external scheduler) retries any pending acknowledgment e-mails up to `MAGENDOOWITHDRAWAL_ACK_MAX_ATTEMPTS` attempts and runs the retention anonymiser against `MAGENDOOWITHDRAWAL_RETENTION_DAYS`. The request must present the correct token (`hash_equals`) or it is rejected with `403 Forbidden`.

Architecture
------------

[](#architecture)

- **Domain** — framework-free plain PHP under `src/Domain/` (`Eligibility`, `EligibilityResult`, `StatusMachine`, `SubmissionData`, `WithdrawalStatus`), PSR-4 autoloaded as `Magendoo\Withdrawal\` via the module's own `vendor/autoload.php` (Composer, no PrestaShop autoload magic involved).
- **Persistence** — Doctrine ORM entities (`src/Entity/WithdrawalRequest.php`, `WithdrawalRequestItem.php`) mapped by annotations onto `ps_withdrawal_request` and `ps_withdrawal_request_item`, read/written through a shop-scoped `Magendoo\Withdrawal\Repository\WithdrawalRequestRepository`. There is no legacy ObjectModel for these tables.
- **Services** — `src/Service/` (`RequestManager`, `Notifier`, `AcknowledgmentSender`, `Anonymizer`, `OrderLocator`, `OrderStateAnchor`, `RateLimiter`, `CsrfToken`, `Labels`), wired as public Symfony services in `config/common.yml` and imported into both the front and admin containers (`config/front/services.yml`, `config/admin/services.yml`). Front and admin controllers resolve them from the current controller's own container (`Controller::getContainer()`), not `SymfonyContainer::getInstance()`, since the latter is `null` in the default (non-`PS_FF_FRONT_CONTAINER_V2`) legacy front dispatch.
- **Presentation** — legacy `ModuleFrontController` classes under `controllers/front/`(`start`, `form`, `review`, `submit`, `success`, `cron`) rendering Smarty templates under `views/templates/front/` and `views/templates/hook/`; a legacy `ModuleAdminController`(`AdminMagendooWithdrawalController`) with a hand-built `HelperList` grid (there being no ObjectModel to drive the default one) and a detail view driving the status workflow.
- **Datetimes** are stored and compared as UTC strings throughout, so audit-trail evidence timestamps do not depend on the shop's configured display timezone.
- Install/uninstall SQL lives in `sql/install.php` / `sql/uninstall.php`; both tables are created with no foreign key to `ps_orders` by design, so a compliance record outlives the order it references.

Testing
-------

[](#testing)

```
cd modules/magendoowithdrawal
composer install
php vendor/bin/phpunit
```

This runs the 77-test unit suite under `tests/Unit/` (pure domain logic — `Eligibility`, `StatusMachine`, `WithdrawalStatus`, `Labels`, `OrderStateAnchor`, `RateLimiter`, `SubmissionData` — no database or PrestaShop bootstrap required).

`tests/Integration/` contains additional scripts (repository, entity write-path, acknowledgment sender, anonymiser, front-office smoke tests) that exercise the module against a running PrestaShop install with a live database and a mail catcher; they are not part of the default `phpunit.xml.dist` test suite and require that environment to be booted first.

License
-------

[](#license)

Released under the [Open Software License 3.0 (OSL-3.0)](LICENSE), matching the license declared in `composer.json`.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance100

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6ef4a3a58fa09cd06825953bc91392aa224ca399f491b77df13ac932cfcddeea?d=identicon)[florinel-chis](/maintainers/florinel-chis)

---

Top Contributors

[![florinel-chis](https://avatars.githubusercontent.com/u/2117053?v=4)](https://github.com/florinel-chis "florinel-chis (29 commits)")

---

Tags

gdprcomplianceprestashopwithdrawaleuconsumer rightsprestashop-modulearticle-11a

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/magendoo-magendoowithdrawal/health.svg)

```
[![Health](https://phpackages.com/badges/magendoo-magendoowithdrawal/health.svg)](https://phpackages.com/packages/magendoo-magendoowithdrawal)
```

###  Alternatives

[whitecube/laravel-cookie-consent

Register, configure and ask for cookies consent in a EU-compliant way.

498731.9k3](/packages/whitecube-laravel-cookie-consent)[devrabiul/laravel-cookie-consent

A GDPR-compliant cookie consent solution for Laravel applications with fully customizable cookie banners, granular consent control, and enterprise-grade compliance features.

18355.4k1](/packages/devrabiul-laravel-cookie-consent)[jfeltkamp/cookiesjsr

COOKiES JSR is a consent management tool and provides UI and API to manage 3rd party services within the GDPR.

27878.6k](/packages/jfeltkamp-cookiesjsr)[mageme/module-eu-withdrawal

Magento 2 implementation scaffolding for the EU consumer-withdrawal workflow under Art. 11a Directive 2011/83/EU (as amended by 2023/2673): guided storefront flow (find order, select items, confirm), durable-medium receipt email, 22-locale Annex I content. NOT legal advice — see README disclaimer; merchant is responsible for compliance verification.

172.0k6](/packages/mageme-module-eu-withdrawal)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
