Joomla extension (package = plugin + component) providing a mandatory “Withdraw from contract” button as required by EU Directive 2023/2673 (effective June 19, 2026).
Two variants are available:
| Variant | Joomla | VirtueMart | PHP | Current version |
|---|---|---|---|---|
| J4/5/6 | 4.x, 5.x, 6.x | 4.x | 8.1+ | 1.6.0 |
| J3 | 3.x | 3.x | 7.0+ | 1.5.2 |
1. Requirements
Variant J4/5/6 + VM4
- Joomla 4.x, 5.x, or 6.x
- VirtueMart 4.x
- PHP 8.1 or newer
- MySQL 5.7+ / MariaDB 10.3+
Variant J3 + VM3
- Joomla 3.x
- VirtueMart 3.x
- PHP 7.0 or newer
- MySQL 5.6+ / MariaDB 10.1+
2. Installation
Obtaining the Installation Package
Download the correct ZIP file:
- J4/5/6:
pkg_semavmwithdrawal.zip(orsemavmwithdrawal_vX.Y.Z.zip) - J3:
pkg_semavmwithdrawal.zipfrom the J3 build (orsemavmwithdrawal_vX.Y.Z_j3.zip)
Installation Steps
- Log in to the Joomla administration panel.
- Navigate to System > Install > Extensions (J4/5/6) or Extensions > Manage > Install (J3).
- Upload the
pkg_semavmwithdrawal.zipfile. - Joomla installs both parts at once:
- Plugin:
plg_system_semavmwithdrawal(System – SemaVirtueMart Contract Withdrawal) - Component:
com_semavmwithdrawal(Contract Withdrawals – admin overview)
- Plugin:
Upgrade: Same procedure as installation. Joomla uses
method="upgrade"and runs migration SQL automatically. Data in the#__sema_withdrawalstable is preserved.
3. Enabling the Plugin
After installation, the plugin must be manually enabled:
J4/5/6
- System > Manage > Plugins
- Search for
semavmwithdrawalorWithdrawal. - Click the plugin name and set Status = Enabled.
- Alternatively: click the red icon directly in the Status column.
J3
- Extensions > Plugins
- Search for
semavmwithdrawal. - Enable the plugin (green checkmark).
Once enabled, the floating button immediately appears on the site frontend (if position is set to float-right or float-left).
4. Basic Configuration
Open the plugin settings:
- J4/5/6: System > Plugins > click the plugin name
- J3: Extensions > Plugins > click the plugin name
Required / Recommended Parameters
| Parameter | Default | Description |
|---|---|---|
| Shop name | (empty = site name) | Displayed in the confirmation email sent to the customer. |
| Notification e-mail | (empty = system sender) | Where new withdrawal alerts are sent. |
| Button text | “Odstoupit od smlouvy” | Text on the button. Must contain readable text – an icon alone is not legally sufficient. |
| Button position | Floating bottom-right | Where the button appears on the site (see section 6). |
| Period length (days) | 14 | Statutory withdrawal period in days. |
| Debug mode | Off | ALWAYS disable in production. Enable only when troubleshooting. |
Additional Parameters
| Parameter | Default | Description |
|---|---|---|
| Offer resolution choice | Yes | Customer can choose: refund / exchange / repair / discount. |
| Choice required | Yes | Customer must select a resolution before submitting. |
| Period handling | Warn but allow | Do not check = no verification; Warn = shows a warning but does not block submission. |
| Terms URL | (empty) | URL to terms & conditions. Displayed in the modal and confirmation email. |
5. Company Details
The “Company details” section in plugin settings. These details appear in the confirmation email sent to the customer:
| Parameter | Description |
|---|---|
| Company logo | Image in the email header. Recommended max width 200 px. |
| Company ID (ICO) | Company identification / tax registration number. |
| Address | Company registered office (email footer). |
| Return address | Where the customer should ship returned goods. If empty, the company address is used. |
| Phone | Contact phone number (email footer). |
| Contact e-mail | Email in the footer. If empty, the system sender is used. |
Recommendation: Fill in all company details. The confirmation email serves as a durable medium under the directive and should contain complete seller identification.
6. Button – Placement and Appearance
Button Position
| Value | Behavior |
|---|---|
| Floating bottom-right | Fixed button in the bottom-right corner of the screen. Always visible. |
| Floating bottom-left | Fixed button in the bottom-left corner. |
| Footer (static) | Button is inserted before the closing </body> tag as a static element. |
| Do not inject (manual) | Plugin does not inject the button. You must place it manually in the template (see section 13). |
Button Color
The “Button & control color” parameter (color picker, default #1f6feb) determines:
- the floating button color
- the color of modal UI controls (confirm button, checkboxes, active states)
- hover state darkens automatically
The color is applied via the CSS custom property --vmw-accent.
7. Resolution Options
When “Offer resolution choice” is enabled, the customer sees a dropdown in step 2 of the form.
Default options:
refund=Refund (contract withdrawal)
exchange=Product exchange
repair=Product repair
discount=Proportionate price discount
Format: key=Label (one line = one option). The label is stored in the audit log and sent in the email.
Options can be freely edited, added, or removed in the plugin settings.
8. Withdrawal Period
- Period length: Default 14 days (parameter
period_days). - Period handling: Determines what happens when an order is older than the configured period.
- Do not check: No verification; the form is always submitted without warning.
- Warn but allow: Shows the customer a warning “OUTSIDE estimated period” but does not block submission.
Important: The plugin NEVER blocks a withdrawal. The period legally runs from the date of goods receipt (not the order date), which the plugin cannot verify precisely. Therefore it only warns – the final decision is up to the shop.
9. Administration – Withdrawal Records
After installation, a new menu item appears in the administration:
- J4/5/6: sidebar menu Components > Contract Withdrawals
- J3: Components > Contract Withdrawals
Records Table
| Column | Description |
|---|---|
| ID | Sequential record number. |
| Order | VirtueMart order number. If an order has multiple withdrawals, a “Duplicate (N×)” badge is shown. |
| Status | Dropdown to change processing status (Pending / In progress / Resolved). |
| Customer | Customer name. |
| Customer email. | |
| Resolution | Chosen resolution type (or “—” if not selected). |
| Items | Summary of selected items, or “Whole order” badge. |
| In period | Whether the withdrawal was filed within the statutory period (Yes / Check). |
| Confirmation sent | Whether the confirmation email was sent to the customer (Yes / No). |
| Received | Date and time the withdrawal was received. |
Changing Status
- Change the status in the “Status” column for the relevant records.
- Click the “Save statuses” button in the toolbar.
- A confirmation message “Statuses saved.” is displayed.
Default statuses (configurable in plugin settings):
pending=Pending
in_progress=In progress
resolved=Resolved
Search and Sorting
- Search: Full-text search on order number, customer name, and email.
- Sorting: Click column headers (ID, Order, Status, Customer, Email, Resolution, In period, Confirmation, Received).
- Pagination: Configurable page size via Joomla pagination.
10. CSV Export
- In the withdrawal records admin view, click the “Export CSV” button in the toolbar.
- A CSV file is downloaded with all records (respects the current search filter).
- Encoding: UTF-8, delimiter: semicolon (
;).
CSV export requires
core.managepermission on the component.
11. Security
CSRF Protection
Every AJAX request from the frontend includes a Joomla form token. The server validates the token before processing. If the token is invalid (e.g., after session expiry), the customer receives the error “The request could not be processed.”
After upgrade: Customers may need to perform a hard refresh (Ctrl+Shift+R) to load the new JavaScript with the updated token.
Rate Limiting
Protection against excessive attempts from a single IP address. When the limit is exceeded, the customer receives the message “Too many attempts. Please try again in a few minutes.”
Honeypot
A hidden form field. If filled by a bot, the request is rejected.
IP Address Behind a Proxy
If the site runs behind a reverse proxy (Cloudflare, nginx, etc.), configure the “Trusted proxy IP header” parameter:
| Value | Use case |
|---|---|
| None (REMOTE_ADDR) | Default, safest. Use when the site is NOT behind a proxy. |
| HTTP_CF_CONNECTING_IP | For sites behind Cloudflare. |
| HTTP_X_FORWARDED_FOR | For sites behind nginx/Apache reverse proxy. |
| HTTP_X_REAL_IP | For sites behind nginx with proxy_set_header X-Real-IP. |
Warning: Setting the wrong header allows attackers to spoof their IP. Use only the header that your proxy actually sets.
12. Emails
The plugin sends two types of email:
Customer Confirmation (HTML)
- Recipient: Customer email (from the VM order).
- Subject: “Confirmation of contract withdrawal – order [number]”
- Content:
- Header with company logo (if configured)
- Confirmation of withdrawal receipt
- Order number, order date, date received
- Period information (within period / outside estimate)
- Chosen resolution (if selected)
- List of selected items and total amount (if items were chosen)
- Statutory withdrawal declaration text
- Return address for goods
- Link to terms & conditions (if configured)
- Footer with company details (ID, address, phone, email)
This email serves as a durable medium under the directive – it is legal proof that the withdrawal was received.
Shop Notification (Plain Text)
- Recipient: Email from the “Notification e-mail” parameter (or system sender).
- Subject: “New contract withdrawal – order [number]”
- Content: Brief overview – order number, customer, email, resolution, items, date.
Email Footer Contact Fallback
The email in the footer is determined by cascading fallback:
- “Contact e-mail” parameter (
company_email_contact) - If empty: “Notification e-mail” parameter (
notify_email) - If empty: Joomla system sender (
mailfrom)
13. Manual Button Placement
If you choose the position “Do not inject (manual)”, you must place the button directly in the site template.
Insert this HTML code at the desired location:
<button type="button" id="vmw-trigger" class="vmw-trigger">
Withdraw from contract
</button>
Requirements:
- The element MUST have
id="vmw-trigger"(JavaScript listens for clicks on this ID). - The
vmw-triggerclass provides basic styling (optional; you can use your own CSS). - The button text can be changed but must be readable (legal requirement).
Even with the “Do not inject” option, the plugin still injects the modal, JavaScript, and CSS into the page – it only skips the button itself.
14. Uninstallation
Procedure
- J4/5/6: System > Manage > Extensions > search for
SemaVMwithdrawal> Uninstall. - J3: Extensions > Manage > search for
SemaVMwithdrawal> Uninstall.
What Gets Deleted
- Plugin
plg_system_semavmwithdrawal(PHP, JS, CSS, language files) - Component
com_semavmwithdrawal(admin files) - Table
#__sema_withdrawal_ratelimit(rate limit records)
What Is PRESERVED
- Table
#__sema_withdrawals(audit records of withdrawals) - Table
#__sema_withdrawal_items(items linked to withdrawals)
Why is data preserved? Withdrawal records are legal evidence. Automatic deletion on uninstall could cause legal problems. If you truly want to remove the tables, do it manually via phpMyAdmin:
DROP TABLE IF EXISTS `#__sema_withdrawal_items`; DROP TABLE IF EXISTS `#__sema_withdrawals`;(replace
#__with your actual table prefix)
15. J3 Version Specifics
Differences from J4/5/6
| Aspect | J4/5/6 | J3 |
|---|---|---|
| Current version | 1.6.0 | 1.5.2 |
| PHP | 8.1+ (namespaces, type hints) | 7.0+ (no namespaces) |
| MVC pattern | Modern Joomla (DI, services/) | Legacy (JFactory, JControllerLegacy) |
| Admin template | Bootstrap 5 | Bootstrap 2 |
| Phone/email fields | Native field types | Text fields |
| Language files | Standard autoload | script.php + runtime fallback |
Language Files in J3
J3 has a known issue loading Czech language files for system plugins. Workarounds in place:
- Install script (
script.php) copies.inifiles toadministrator/language/during installation. - Runtime fallback (
ensureAdminLanguageFiles()) checks and copies files at runtime if missing. - Triple fallback in
loadPluginLanguage(): JLanguage > cs-CZ fallback > parse_ini_file with injection.
If some texts in J3 administration appear in English instead of Czech, this is expected behavior. The Plugin Manager may overwrite Czech strings with English ones.
Frontend
Frontend behavior (button, modal, form) is identical in both variants – same JavaScript and CSS.
16. Troubleshooting
Button Does Not Appear
- Verify the plugin is enabled (Status = Enabled / green checkmark).
- Check the “Button position” parameter – is it set to “Do not inject”?
- Verify no other plugin/module on the page is blocking
onAfterRender. - Check the browser console (F12) for JavaScript errors.
Error “The request could not be processed”
- After upgrade: Customer must perform a hard refresh (Ctrl+Shift+R) to load the new JS with CSRF token.
- Expired session: Refreshing the page will renew the token.
- Enable Debug mode in plugin settings for more detailed error messages.
Error “Too many attempts”
Rate limiting. The customer must wait a few minutes. If this is legitimate traffic behind a proxy, set the correct trusted proxy IP header.
Email Not Delivered
- Verify Joomla mail settings: System > Global Configuration > Server > Mail Settings (J4/5/6) or System > Global Configuration > Server (J3).
- Check the spam folder.
- Verify the “Notification e-mail” parameter is correctly filled in.
- Enable Debug mode – the AJAX response will include information about whether the email was sent.
Order Not Found
- The customer must enter the exact VirtueMart order number and the email associated with the order.
- The plugin searches the
#__virtuemart_orderstable joined with#__virtuemart_order_userinfos(type BT).
Admin – “Contract Withdrawals” Menu Not Visible
- Verify the component
com_semavmwithdrawalis installed. - Verify user permissions –
core.manageon the component is required.
J3 – Czech Texts Appear in English
Known issue with loading Czech in J3 system plugins. Possible solutions:
- Go to Extensions > Manage > Discover and click Discover (re-detects language files).
- Manually copy files from
plugins/system/semavmwithdrawal/language/cs-CZ/toadministrator/language/cs-CZ/. - Clear the Joomla cache.
How the Form Works (Customer Perspective)
- The customer clicks the “Withdraw from contract” button on any page of the e-shop.
- A modal window opens.
- Step 1 – Order Verification: The customer enters the order number and email. Clicks “Verify order”.
- The plugin verifies the details against the VirtueMart database.
- Step 2 – Confirmation: An order summary is displayed:
- Order number, date, total amount
- List of items with checkboxes (for partial withdrawal) and quantity fields
- Resolution choice (if enabled)
- Duplicate warning (if a withdrawal already exists for this order)
- Period warning (if the order is older than the configured number of days)
- The customer selects items, chooses a resolution, and clicks “Confirm withdrawal”.
- A confirmation screen appears with a reference number and the return address.
- A confirmation email is sent to the customer. A notification is sent to the shop.
Launch Checklist
- Plugin installed and enabled
- Shop name filled in
- Notification email filled in
- Company details filled in (logo, ID, address, phone, email)
- Return address filled in
- Terms & conditions URL configured
- Button position selected
- Debug mode DISABLED
- Trusted proxy IP header configured (if site runs behind a proxy)
- Test withdrawal completed and email received
- Terms & conditions updated with information about the withdrawal button on the website