GDPR Account Deletion — Complete Guide
Install, configure and use GDPR-compliant account deletion with email confirmation, anonymization respecting accounting retention obligations, and Mailchimp, Brevo and Mailjet unsubscribe for PrestaShop 8 and 9.
Overview
The GDPR Account Deletion module gives your customers a way to exercise their right to erasure (Article 17 GDPR) directly from their customer area, without manual intervention on your part. A request triggers a cascade: confirmation by email with a cryptographic token, anonymization or deletion of the account on the shop side, automatic unsubscription from Mailchimp, Brevo and Mailjet, and writing a proof of processing in an audit log.
Who is it for? Any PrestaShop 8 or 9 merchant who processes personal data of European citizens and wants to avoid manually handling GDPR deletion requests. The module ships with French, English, Spanish and German.
Installation
- Log in to the PrestaShop back office.
- Go to Modules → Module Manager, click Upload a module and drop the
dfaccountdelete-1.0.2.zipfile. - The module is installed and activated automatically. Click Configure.
The installation creates two tables: ps_dfad_log (GDPR log) and ps_dfad_token (email confirmation tokens). It registers the module on the displayCustomerAccount hook, which adds a “Delete my account” block to the customer area.
If you have many customized templates, check after installation that the “Delete my account” block appears in My Account on the shop side. The displayCustomerAccount hook must be supported by your theme — this is the case for the native Classic theme and most commercial themes.
General configuration
The configuration screen is divided into four panels: General settings, Mailchimp, Brevo, Mailjet. Each panel is saved independently.
Deletion mode
Choose between two modes:
- Anonymize (recommended): the customer’s personal data (first name, last name, email, phone, addresses, date of birth) is replaced with anonymous values. Orders remain intact to respect the accounting retention obligation (10 years in France under article L123-22 of the Commercial Code).
- Delete fully if possible: the account is entirely erased if no orders exist. If orders exist, the module automatically switches to anonymization mode so as not to break the legal history.
Even in “Delete fully” mode, you cannot delete an account that has placed orders. This is a French and European legal obligation. The module handles this switch automatically and records in the GDPR log the mode actually applied.
Mandatory email confirmation
Enabled by default (strongly recommended). The customer must click a link received by email to validate their request. This prevents any accidental deletion or one by a third party who has accessed an open session.
Token validity duration
In hours. 24 hours by default. Beyond this duration, the link in the email expires and the request is automatically cancelled.
Logs
Enabled by default. The module writes to ps_dfad_log each request with only pseudonymized data: SHA-256 hash of the email, SHA-256 hash of the IP, customer ID, mode applied, status, providers contacted, user agent, shop ID, timestamp. No personal data is kept in clear text.
The ps_dfad_log table is not deleted when the module is uninstalled. This is intentional: it constitutes proof of processing in the event of an audit by the data protection authority. To delete it manually, run DROP TABLE ps_dfad_log; after uninstallation.
Notified administrator email
Address that will receive a notification (email hash only, never the email in clear text) on each effective deletion. By default, the shop email.
Newsletter providers configuration
The module supports three newsletter platforms as standard. Each provider is disabled by default. Only enable the ones you use.
Mailchimp
Fill in:
- API Key: format
xxxxxxxxxxxx-us21. The suffix (-us21,-eu1, etc.) is the Mailchimp datacenter. The module detects it automatically. - List ID (Audience ID): identifier of your main audience. Found in Mailchimp via Audience → Settings → Audience name and defaults.
- Permanent deletion: enabled by default. Uses the
POST /lists/{list_id}/members/{hash}/actions/delete-permanentendpoint, which corresponds to the true GDPR right to be forgotten: the email is permanently deleted and Mailchimp will refuse any future opt-in with this address. If you prefer simple archiving (the email can then resubscribe later), disable this option.
Click Test connection to validate your configuration. If everything is correct, you will see the name of your audience.
Brevo (formerly Sendinblue)
Fill in:
- API Key v3: format
xkeysib-xxxxxx. Found in Brevo via My account → SMTP & API → API Keys. - List ID (optional): if entered, only removes the contact from this list. If empty, deletes the contact entirely (recommended for GDPR).
Endpoint used: DELETE /v3/contacts/{email} for full deletion, or POST /v3/contacts/lists/{id}/contacts/remove for list removal.
Mailjet
Mailjet uses Basic authentication with two keys. Fill in:
- API Key
- API Secret
- Contact List ID (optional): if entered, unsubscribes from this list only. If empty, deletes the contact via the official Mailjet GDPR endpoint.
The Mailjet deletion flow is in two steps: lookup of the contact identifier via GET /v3/REST/contact/{email}, then DELETE /v4/contacts/{id} on the GDPR endpoint. The module handles this mechanism automatically.
If a platform returns 404 during deletion, the module considers it a success (the contact no longer exists, idempotency). You will not see an error in the logs.
Anonymization or deletion: which to choose?
GDPR imposes the right to erasure, but the French Commercial Code requires the retention of accounting documents for 10 years. These two obligations are reconciled by anonymization, which is the position recommended by the CNIL (French data protection authority).
Anonymization mode
The module replaces in ps_customer:
firstname→Anonymizedlastname→Customeremail→anon-{id_customer}-{hash}@anonymized.localpasswd→ random hash (the customer can no longer log in)birthday→ NULLnote,website,ip_registration_newsletter→ emptyactive→ 0,deleted→ 1
For addresses:
- Addresses not used by orders are deleted entirely.
- Addresses used by orders are anonymized (
firstname,lastname,address1,postcode,city,phone, etc. replaced with neutral values) and markeddeleted = 1.
Full deletion mode
If the customer has not placed any orders: full deletion of addresses then call to Customer::delete(). Otherwise: automatic switch to anonymization mode. No option allows you to force the deletion of an account with orders — this is intentional for legal reasons.
Complete request flow
- The customer goes to My Account and clicks on the “Delete my account” block.
- They arrive on a confirmation page with a warning box, a “Password” field and a checkbox “I understand that this action is irreversible”.
- They enter their password and tick the box. The module verifies the password via the
PrestaShop/Core/Crypto/Hashingclass. - If email confirmation is enabled, the module generates a 32-byte random token (
random_bytes(32)), converts it to 64-character hexadecimal, stores its SHA-256 hash inps_dfad_token, and sends an email containing the token in clear text in a URL. - The customer receives the email and clicks the link. The module retrieves the token from the URL, computes its SHA-256, and checks that it exists in the table, is not expired, and has not already been consumed.
- The token is marked consumed (
consumed_at = NOW()) to prevent reuse. - For each enabled provider (Mailchimp, Brevo, Mailjet), the module calls the deletion API and records the result (HTTP code, message) in the log.
- The module cleans up PrestaShop tables:
ps_emailsubscription,ps_newsletter,ps_cart,ps_cart_product,ps_wishlist,ps_compare,ps_customer_thread,ps_customer_message,ps_guest. - Depending on the mode and presence of orders, the account is anonymized or deleted.
- A row is inserted into
ps_dfad_logwith statusdone, the mode actually applied, and the list of provider results. - A notification email is sent to the configured administrator (email hash only).
- The customer is logged out and redirected to a confirmation page.
GDPR processing log
The configuration screen displays the last 30 requests with:
- Request ID
- Truncated email hash (first 12 characters of the SHA-256)
- Mode applied (anonymize / delete)
- Status (awaiting_confirm, done)
- List of providers and their return code, for example
mailchimp:OK(200) | brevo:OK(204) | mailjet:OK(200) - UTC date and time
The complete table also contains: SHA-256 hash of the IP, user agent truncated to 250 characters, shop ID, internal notes (presence or not of orders).
To export the full log in case of audit, you can query the database directly: SELECT * FROM ps_dfad_log ORDER BY created_at DESC;. No data in clear text ever appears in this log.
Testing before going live
Recommended procedure before activating the module on a production shop:
- Test provider connections: in the module’s BO, click Test connection for each enabled platform. You should see a green message with the name of your audience/account. In case of error, the exact HTTP message returned by the API is displayed.
- Test the full flow on a test account:
- Create a customer account with a real email address that you control.
- Subscribe it to your Mailchimp/Brevo/Mailjet newsletter manually, or via your shop’s form.
- Verify that it appears in each platform.
- Log in to this account on the shop side and start the deletion procedure.
- Confirm via the email received.
- Verify in
ps_customerthat the account is properly anonymized. - Verify in each newsletter platform that the email has disappeared.
- Verify the entry in
ps_dfad_log.
- Test the “customer with order” case: create an account, place a test order (1€), then start deletion in “Delete fully” mode. Verify that the module has correctly switched to anonymization and that the order is intact but with an anonymous address.
Extending: adding a new provider
The architecture follows a Strategy pattern. Adding Sendgrid, Mailerlite, HubSpot, ActiveCampaign or any other platform only requires creating a class.
Create classes/Provider/SendgridProvider.php:
class DfAccountDelete_SendgridProvider extends DfAccountDelete_AbstractProvider
{
public function getKey() { return 'sendgrid'; }
public function isEnabled()
{
return (bool) Configuration::get('DFAD_SG_ENABLED')
&& Configuration::get('DFAD_SG_API_KEY');
}
public function deleteSubscriber($email)
{
$headers = ['Authorization: Bearer ' . Configuration::get('DFAD_SG_API_KEY')];
$url = 'https://api.sendgrid.com/v3/marketing/contacts?emails=' . rawurlencode($email);
$res = $this->http('DELETE', $url, $headers);
return ['ok' => $res['ok'], 'status' => $res['status'], 'message' => $res['message']];
}
public function testConnection()
{
$headers = ['Authorization: Bearer ' . Configuration::get('DFAD_SG_API_KEY')];
$res = $this->http('GET', 'https://api.sendgrid.com/v3/scopes', $headers);
return ['ok' => $res['ok'], 'status' => $res['status'], 'message' => $res['message']];
}
}
Reference the class in DfAccountDeleteService::getProviders():
public function getProviders()
{
return [
new DfAccountDelete_MailchimpProvider(),
new DfAccountDelete_BrevoProvider(),
new DfAccountDelete_MailjetProvider(),
new DfAccountDelete_SendgridProvider(), // new
];
}
And load the class at the top of dfaccountdelete.php with a require_once. The rest (BO configuration panel, test button, integration into the deletion flow) is to be coded similarly to existing providers.
FAQ and troubleshooting
The customer says they did not receive the confirmation email
Check: (1) is the PrestaShop SMTP configuration functional (are you sending other transactional emails like order confirmations?), (2) is the email not in their spam, (3) has the expiration time not been reached. The customer can restart the procedure from their account — the new token automatically invalidates previous ones.
A newsletter platform returns a 401 error
The API key is invalid or expired. Regenerate it in the platform concerned and update it in the module configuration. Use the Test connection button to validate immediately.
The SQL error “LIMIT 1 LIMIT 1” appears
You are on a version older than 1.0.1. Update to the latest version: this bug was fixed in 1.0.1.
The account does not seem anonymized: the original first name/last name/email are still visible
You are on a version older than 1.0.2. PrestaShop’s internal validation on Validate::isCustomerName() refused the old lastname value which contained the # character and digits, which caused the update to fail silently. Update to 1.0.2: the automatic upgrade also repairs accounts that were poorly anonymized in previous versions.
How to delete the GDPR log when uninstalling?
The ps_dfad_log table is intentionally preserved on uninstall (proof of processing). To delete it manually after uninstallation: DROP TABLE ps_dfad_log; DROP TABLE ps_dfad_token;.
Is the module compatible with my existing GDPR module (gdpr, psgdpr)?
Yes, the two modules can coexist. The official PrestaShop module psgdpr is mainly used for data export and consent management; dfaccountdelete is used for the effective deletion of accounts with newsletter integration. They do not step on each other’s toes.
Can the customer cancel after clicking the button?
Yes, as long as they have not clicked the link in the confirmation email. The token automatically expires after the configured duration (24 hours by default). During this window, the account remains fully active. If they do nothing, the request is cancelled.
How to add the deletion block elsewhere than in My Account?
You can create a direct link to {shop_url}/{lang}/module/dfaccountdelete/delete from any page (footer, terms of service page, etc.). If the visitor is not logged in, they will be redirected to the login page with a return to the deletion page after authentication.
Versions and changelog
1.0.2 — critical fix
- Fix:
anonymizeCustomer()usedCustomer::update()which goes throughValidate::isCustomerName(). This validation forbids digits and the#character in firstname/lastname, which caused the anonymization of theps_customerrecord to fail silently. Replaced with a direct SQLUPDATEthat bypasses validation. - The
upgrade-1.0.2.phpupgrade retroactively repairs accounts processed by previous versions that remained un-anonymized.
1.0.1 — fix
- Fix:
tableExists()usedDb::getValue()which automatically appendsLIMIT 1. The querySHOW TABLES LIKE 'xxx' LIMIT 1is invalid SQL in MariaDB. Replaced withexecuteS().
1.0.0 — initial release
- Compatibility PrestaShop 8.0 to 9.x
- Anonymization mode and full deletion mode
- Email confirmation with SHA-256 token
- Mailchimp, Brevo, Mailjet integrations
- GDPR processing log
- Interface and email templates in FR, EN, ES, DE