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 | 4.x, 5.x | 4.x | 8.1+ | 1.7.3 |
| J3 | 3.x | 3.x | 7.0+ | 1.5.5 |
1. Requirements
Variant J4/5 + VM4
- Joomla 4.x or 5.x (VirtueMart does not support Joomla 6 yet)
- 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:
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) 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
- 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: 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 | (empty = from language file) | Custom button text (same for all languages). When empty, the text is taken from the language file – automatically in the visitor's language. |
| 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. For multilingual sites, per-language URLs are supported (see multilingual support below). |
Multilingual Support (since v1.7.0)
The plugin automatically displays the modal, emails, and error messages in the visitor's language. Supported languages: Czech (cs-CZ), English (en-GB), German (de-DE), Slovak (sk-SK). If the visitor's language is not available, English is used as a fallback.
Button text: When the “Button text” parameter is empty (default), the text is taken from the language file – automatically in the visitor's language. If a custom text is entered, it is used for all languages.
Terms URL: For multilingual sites, you can specify different URLs for each language. Format in the text area (one line = one language):
cs-CZ=https://example.cz/podminky
en-GB=https://example.cz/en/terms
de-DE=https://example.cz/de/agb
sk-SK=https://example.cz/sk/podmienky
A single URL without a language prefix still works (backward compatible). If the visitor's language is not in the list, the first URL is used as a fallback.
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: 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. Since version 1.7.0, emails are sent in the visitor's language (including the subject, body text, and footer).
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)
Currency
Amounts are shown in the order currency as configured in VirtueMart – the symbol, the separators and the position of the symbol all come from the VirtueMart currency table. This applies to the modal and to both e-mails. Since version 1.7.3; before that the currency was hard-coded to Czech koruna, so a shop selling in euro showed the wrong symbol.
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 modal is opened by any element carrying
id="vmw-trigger", or the classvmw-trigger, or the attributedata-vmw-open(since v1.7.2). Any one of them is enough. - The
vmw-triggerclass also provides basic styling (optional; you can use your own CSS). - You may place several buttons on one page – use the class or the attribute for that, since an
idmust be unique in HTML. - The button text can be changed but must be readable (legal requirement).
Opening the modal from your own code (since v1.7.2)
If you need to open the modal from elsewhere (e.g. a link inside an article, or after your own action), use the public API:
<a href="#" onclick="window.VMW.open(); return false;">Withdraw from contract</a>
Both window.VMW.open() and window.VMW.close() are available.
Do not work around it by programmatically “clicking” the plugin's floating button. With the position set to “Do not inject (manual)” no such button exists on the page, so the click has nothing to land on. Use the class, the attribute, or
window.VMW.open().
The trigger may also be inserted later (e.g. via AJAX) – the plugin listens at document level, so a trigger added after page load works too.
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: 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
| Aspect | J4/5 | J3 |
|---|---|---|
| Current version | 1.7.3 | 1.5.5 |
| 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) 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


