VirtueMart Update Key Manager
Complete administrator, store-owner, and extension-author documentation for protected purchases, private Joomla updates, expiring licences, and renewal reminders.
Author: Darko Fatur ·
1. Product overview
VirtueMart Update Key Manager supports both licensed Joomla extension packages and ordinary protected digital files. Once an order enters a configured paid status, it sends the buyer a protected purchase link; extension products also receive a time-limited key for automatic Joomla updates.
Files are served from the VirtueMart Safe Path, outside the public web root. Purchase downloads have a configurable limit. A single-file product such as a JPEG or PDF is limited only by that count, while extension update downloads remain unlimited while the licence is active.
Main capabilities
- One cryptographically random 256-bit update key for each licensed paid order item.
- SHA-256-only key storage: the database does not retain the recoverable full key.
- Limited initial package downloads and unlimited licensed update downloads.
- Protected packages kept below the VirtueMart Safe Path rather than at public URLs.
- Automatic discovery of the highest strict semantic version (
MAJOR.MINOR.PATCH). - Static public Joomla update XML generation with exact SHA-256 package checksums.
- Administrator search, audit counters, revocation, reactivation, deletion, and immediate feed publishing.
- Scheduled expiry reminders with unique, product-restricted VirtueMart coupons.
- English (
en-GB) and Slovenian (sl-SI) language files.
Important licence behavior
- A key belongs to one order item and therefore one configured product/package mapping.
- Repeated payment callbacks are idempotent: the unique order-item constraint prevents duplicate licences.
- Buying again creates a new order, key, and term. It does not extend or change the old licence.
- Revoked or expired licences cannot use purchase links or automatic updates.
- The key is shown in the original licence email only. Administrators see only its ending hint.
2. Installed architecture
| Extension | Identifier | Responsibility |
|---|---|---|
| Component | com_vmupdatekeymanager |
Licence database and administrator list; purchase/update download endpoint; dynamic feed endpoint; static feed publishing service; component options. |
| VirtueMart custom-field plugin | plg_vmcustom_vmupdatekeymanager |
Product mapping, version display, paid-order processing, storage creation, licence issuance, customer email, protected order-detail link, and renewal-term snapshot. |
| Installer plugin | plg_installer_vmupdatekeymanagerupdatekey |
Adds this manager's saved key only to protected manager-package downloads from shop.topoweryou.com. |
| Scheduled Tasks plugin | plg_task_vmupdatekeymanagerrenewal |
Runs renewal reminders/coupon creation and publishes all configured static update XML files. |
The parent package installs all four children, enables all three plugins, creates required database structure, migrates older installations, updates the manager update-site URL, and creates the two daily scheduled tasks if they do not already exist.
3. Requirements and preparation
- Joomla 5 or Joomla 6. The package preflight rejects other major versions.
- A functioning VirtueMart store with a correctly configured Safe Path.
- MySQL-compatible database support used by Joomla/VirtueMart.
- Working Joomla outbound email configuration.
- PHP/web-server permission to create and read protected directories.
- PHP/web-server permission to write the configured public XML directory.
- A functioning Joomla Scheduled Tasks runner: Lazy Scheduler, Web Cron, or server cron/CLI.
- VirtueMart coupon support enabled if renewal reminders and discounts are used.
Before production
- Test installation, email delivery, purchase downloads, update discovery, and update installation on staging.
- Back up the Joomla database and site files.
- Confirm the Safe Path is outside
public_htmlor the equivalent document root. - Confirm that security/CDN services allow non-browser Joomla clients to read XML and reach the download endpoint.
4. Build and installation
Build from this source repository
./build.sh
The build creates the installable parent package:
dist/pkg_vmupdatekeymanager_v1.2.1.zip
The build also prepares dist/updatesxml/vmupdatekeymanager.xml. It checks expected versions and, when xmllint is installed, validates relevant manifests/XML.
Install in Joomla
- Open System → Install → Extensions.
- Upload
pkg_vmupdatekeymanager_v1.2.1.zip. - Verify the component and three plugins appear in Joomla's extensions list.
- Open System → Manage → Plugins and verify all three plugins are enabled. The installer enables them automatically.
- Open System → Scheduled Tasks and verify the renewal and feed-publishing tasks exist, are published, and default to a 24-hour interval.
Upgrade
Install the newer parent package over the existing installation, or use Joomla Update after saving a valid manager update key in the component options. Installation migrations preserve licence rows and add missing columns. Always back up first and test the resulting scheduled tasks and feed.
5. Protected storage
Configure VirtueMart's Safe Path outside the public web root. A typical layout is:
/home/ACCOUNT/domains/shop.example.com/vmfiles/
└── salefiles/
├── route-calculation/
│ ├── pkg_route_calculation_help_for_accounting_v1.6.1.zip
│ └── pkg_route_calculation_help_for_accounting_v1.7.0.zip
├── accounting-tools/
│ └── pkg_accounting_tools_v2.0.0.zip
└── vmupdatekeymanager/
└── pkg_vmupdatekeymanager_v1.2.1.zip
The component installer creates the default salefiles directory when the Safe Path resolves. The custom-field plugin creates each extension subdirectory when required. Upload the first matching release before accepting payment.
Naming rules
| Item | Rule | Example |
|---|---|---|
| Protected base folder | Lowercase ASCII letter/number first, then lowercase letters, numbers, hyphen, underscore. | salefiles |
| Extension folder | Same rule; permanent identifier for the product. | route-calculation |
| Package prefix | Starts with pkg_, ends with _v; middle permits letters, numbers, dot, hyphen, underscore. |
pkg_route_calculation_v |
| Version | Exactly three numeric semantic-version parts. | 1.7.0 |
| ZIP filename | PACKAGE_PREFIX + MAJOR.MINOR.PATCH.zip. |
pkg_route_calculation_v1.7.0.zip |
| Update XML filename | Lowercase letters/numbers/hyphens/underscores, ending in .xml. |
route-calculation.xml |
6. Configuration reference
Component options
Open Components → VirtueMart Update Key Manager → Options.
| Option | Default | Purpose |
|---|---|---|
| Update key | Empty | The key received when this manager was purchased. The installer plugin adds it only to matching manager downloads from the official host. |
| Public XML directory | files/updatesxml |
Path relative to the Joomla root. Every segment must match the lowercase directory naming rule and the resolved destination must remain inside the Joomla root. |
| Public XML base URL | https://shop.topoweryou.com/files/updatesxml |
Public URL corresponding to the directory. Its scheme/host also determines generated download URLs. |
| Permissions | Inherited | core.manage, core.edit.state, core.delete, and core.admin. |
Global custom-field plugin options
Open System → Manage → Plugins → VirtueMart Update Key Manager.
| Option | Default / range | Meaning |
|---|---|---|
| Protected base folder | salefiles |
Directory created inside the VirtueMart Safe Path. |
| Paid statuses | C,S |
Comma-separated VirtueMart order status codes treated as paid. Configure these to match the store's actual payment workflow. |
| Maximum initial-package downloads | 3; 1–1000 | Limit for the protected purchase package. |
| Validity days | 365; 1–3650 | Licence term from issuance/payment processing. |
| Licence email subject/body | Translated defaults | Custom templates; leave empty to use language defaults. |
| Enable renewal reminder | No | Enables snapshotting and scheduled reminders for newly issued licences. |
| Days before expiration | 10; 1–365 | Start of the eligible reminder window. |
| Renewal discount | 10%; 0.01–100 | Percentage used for the generated Gift coupon. |
| Coupon validity | 30 days; 1–365 | Validity from coupon creation. |
| Renewal subject/body | Translated defaults | Custom renewal templates; leave empty for language defaults. |
Email placeholders
| Template | Available placeholders |
|---|---|
| Licence email | {key}, {expires}, {product}, {order}, {download_url}, {download_limit} |
| Renewal email | {product}, {expires}, {coupon}, {renewal_url}, {coupon_expires}, {discount} |
Templates are converted to safe HTML: text is escaped, line breaks are preserved, and the download/renewal URL is made clickable.
7. Configure a licensed VirtueMart product
Create the reusable custom field
- Open VirtueMart → Products → Custom Fields and create a field.
- Set Custom Field Type to Plugin.
- Select VirtueMart Update Key Manager.
- Publish it. Set Cart Attribute to Yes, Cart Input to No, and Administrator only to No.
- Use a shopper-facing title such as Current version and choose the desired VirtueMart display position.
- Save. The field can be reused across licensed products.
The package installer also corrects these flags on existing Update Key Manager fields because VirtueMart requires a cart attribute to call the order-display hooks. The plugin renders no product/cart selector.
Assign it to a product
Open the product's Custom Fields tab, add the reusable field once, and choose one of the two clearly separated download types.
Extension package
Download type: Extension package (ZIP + updates)
Protected product folder: route-calculation
Package prefix: pkg_route_calculation_help_for_accounting_v
Update XML filename: route-calculation.xml
Renewal product URL: (optional; blank uses this product's detail URL)
Single protected file
Download type: Single protected file (limited downloads)
Protected product folder: purchased-photos
Exact filename: landscape.jpeg
Package prefix: (leave empty)
Update XML filename: (leave empty)
Store the exact file at SAFE_PATH/base_folder/protected_product_folder/filename. A single-file purchase does not expire, does not publish an update feed, and remains available until its purchase-download count is used.
- No product ID or SKU mapping is required.
- Do not add conflicting Update Key Manager assignments to one product; issuance/publishing rejects conflicts.
- Each XML filename must be unique across extensions.
- Do not change permanent identifiers after keys and update sites have been distributed.
Upload and verify the first release
- Upload at least one matching ZIP to
SAFE_PATH/base_folder/extension_folder/. - Open the product page. The field should display the highest matching version, such as
v1.6.1. - Run Publish update XML files from the component.
- Open the resulting public XML URL and confirm it is XML, not an HTML error/challenge.
8. Customer and licence lifecycle
Issuance
- VirtueMart changes an order to a configured paid status.
- The plugin checks each order item for a valid Update Key Manager product assignment.
- It validates the billing email, storage mapping, and either the latest matching extension release or the exact configured file.
- It generates a 32-byte random key (256 bits), stores its SHA-256 hash and a short ending hint, and snapshots the licence/renewal terms.
- It emails the protected purchase URL and download limit. Extension products also include the update key and expiry.
- If email sending fails, the new licence row is rolled back so a later callback can retry.
Initial purchase download
The buyer may use either the emailed key URL or the link shown in VirtueMart order details. The order-details link uses a 64-character HMAC token derived from the licence, order item, file, Joomla secret, and VirtueMart order password; it does not reveal the update key.
The endpoint verifies status, exact originally issued filename, storage containment/readability, and remaining count. Extension purchases also enforce licence expiry; single-file purchases are limited by download count and optional administrator revocation. It increments purchase_download_count atomically and records last_used_on.
Automatic Joomla update
- Joomla reads the public update XML and detects a newer version.
- The extension-specific installer plugin or Joomla download-key handling adds
key=…. - The shop hashes the submitted key and finds the licence.
- The endpoint validates active/not-expired state and ensures the requested ZIP matches the licence's package prefix plus a semantic version.
- It streams the private ZIP, increments the update counter, and updates last-use time.
Update downloads do not consume the initial-purchase limit.
9. Administrator operations
Open Components → VirtueMart Update Key Manager. The list supports search by customer email, order number, product name, product SKU, and key hint. Sortable fields include email, order, product, expiry, reminder date, purchase/update counts, and status.
Visible audit data
- Customer email, order number, product name and SKU.
- Key ending (never the full key), expiration date, active/revoked state.
- Renewal coupon and reminder-sent date.
- Purchase download count versus limit and automatic update count.
Toolbar actions
| Action | Permission | Effect |
|---|---|---|
| Activate | core.edit.state |
Clears revoked status. It does not extend an expired date or reset counters. |
| Revoke | core.edit.state |
Immediately blocks both purchase and update downloads. |
| Delete | core.delete |
Permanently deletes selected licence rows and manager-created renewal coupons whose stored ID/code match. |
| Publish update XML files | core.admin |
Processes all valid published product assignments immediately and reports rewritten, unchanged, and failed feeds. |
| Options | core.admin |
Opens component update-key, publishing, and ACL settings. |
10. Publish automatic updates
Recommended static-feed workflow
- Build the exact release ZIP using the permanent package prefix and a strict semantic version.
- Upload it alongside older versions in the protected extension directory.
- Open the component and click Publish update XML files, or allow the daily task to run.
- Open the public XML and verify version, protected download URL, and SHA-256 checksum.
- On a staging customer site, clear Joomla's update cache, discover updates, and install using a valid key.
What the publisher does
- Reads every published Update Key Manager product assignment.
- Resolves its XML filename. A blank value reuses existing XML matching the package element or creates
extension-folder.xml; the manager preservesvmupdatekeymanager.xmlcompatibility. - Rejects conflicting extension-folder mappings or duplicate XML filenames.
- Finds the highest matching semantic-version ZIP.
- Computes SHA-256 from that exact file.
- Builds Joomla update XML for a package targeting Joomla
(5|6)\..*. - Writes atomically under the configured directory. Unchanged content is not unnecessarily replaced.
URLs
Recommended public static feed:
https://shop.example.com/files/updatesxml/route-calculation.xml
Protected update download generated in that XML:
https://shop.example.com/index.php?option=com_vmupdatekeymanager&task=download.get&file=pkg_route_calculation_v1.7.0.zip&channel=update
A dynamic feed endpoint also exists and selects the current package at request time:
https://shop.example.com/index.php?option=com_vmupdatekeymanager&task=updatefeed.get&extension=route-calculation
Static feeds are recommended for reliability, caching, predictable public availability, and easier firewall/CDN handling.
Optional repository publishing helper
For the manager's repository-generated XML only:
./publish-update-xml.sh USER@HOST:/ABSOLUTE/PUBLIC/PATH/files/updatesxml/
# Or use a local target / environment setting
VMUKM_XML_UPLOAD_TARGET=/path/to/files/updatesxml ./publish-update-xml.sh
12. Renewal reminders and coupons
Enable and schedule
- Enable coupon usage in VirtueMart.
- Configure global/custom-field renewal defaults.
- Set an optional per-product Renewal product URL; blank generates that product's VirtueMart detail URL.
- Verify VirtueMart update-key renewal reminders is published in System → Scheduled Tasks.
- Ensure a real scheduler runner invokes Joomla tasks.
Eligibility and behavior
The task selects non-revoked, non-expired licences with stored renewal parameters, no successful reminder date, and an expiry within the snapshotted reminder window. Settings are frozen when the licence is issued so later offer changes do not retroactively alter sold terms.
- Code format:
RENEW-XXXXXXXX, generated from cryptographically random bytes. - Type: percentage Gift coupon, one attempt/use per customer.
- Scope: restricted to the licensed product.
- Registered buyer: also restricted to the original Joomla user.
- Guest buyer: not shopper-bound, but remains random, product-restricted, expiring, and one-use.
Failure and retry
If email fails, renewal_reminder_sent_on remains empty. A still-valid stored coupon is reused on the next run. If that unsent coupon expires, the task unpublishes it and creates a replacement. A successful paid renewal order using the manager-created code unpublishes that coupon. Deleting a licence through the component deletes its matching coupon.
13. Security model
| Control | Protection |
|---|---|
| Random key | 32 bytes from a cryptographically secure generator (256 bits). |
| Database storage | Only SHA-256 hash and short key hint are retained. |
| Order link | HMAC token binds licence, order item, original file, Joomla secret, and VirtueMart order password. |
| Path validation | Strict names, realpath containment beneath Safe Path, direct-child file check, readability/file checks, and basename handling. |
| Package authorization | Purchase channel permits only the recorded original filename; update channel permits only the licence prefix plus a strict semantic version. |
| Licence state | Every download checks existence, expiry, and revoked status. |
| Initial limit | Database update increments only while count remains below the limit, preventing simple concurrent overuse. |
| Response headers | ZIP content type, attachment disposition, private/no-store cache control, and X-Content-Type-Options: nosniff. |
| Admin actions | CSRF token and Joomla ACL checks. |
| Feed integrity | SHA-256 is calculated from the exact protected release and XML is replaced atomically. |
| Key injection | The bundled manager installer plugin limits injection to official matching manager downloads. |
Deployment hardening
- Keep the Safe Path outside the web root and deny direct HTTP access.
- Grant only the required filesystem owner/group access; do not use world-writable permissions.
- Use HTTPS for the store, XML base URL, purchase links, and update endpoint.
- Treat emailed keys as credentials. Never place them in public logs, source control, tickets, or screenshots.
- Restrict component permissions to trusted administrator groups.
- Back up the licence table and Joomla secret consistently; changing the secret invalidates existing HMAC order-detail URLs.
- Exclude only the public XML directory and exact download route from JavaScript challenges—not from normal web/application security controls.
14. Routine operations
For every release
- Update package/child manifest versions and release notes as appropriate.
- Build the final ZIP once.
- Verify its name exactly matches the configured prefix and semantic version.
- Upload that unchanged ZIP into protected storage.
- Publish feeds immediately or monitor the scheduled publishing task.
- Confirm the public XML version and independently compare SHA-256.
- Test update discovery and installation with a valid staging licence.
- Test that invalid, expired, or revoked credentials fail.
Daily/weekly monitoring
- Review Scheduled Tasks history for renewal and feed-publishing failures.
- Check Joomla/PHP logs for storage, mapping, checksum, feed write, or mail errors.
- Review unexpectedly high purchase/update counters and last-use activity.
- Confirm public XML endpoints return HTTP success and XML content without a challenge page.
- Review expiring licences, unsent reminders, and coupon behavior.
Firewall/CDN/hosting
Automated Joomla clients cannot solve JavaScript challenges. Allow direct machine access to:
/files/updatesxml/
/index.php?option=com_vmupdatekeymanager&task=download.get
The endpoint still performs licence validation. Removing a browser challenge does not make ZIP files public.
15. Troubleshooting
| Symptom | Likely cause | Resolution |
|---|---|---|
| No licence/email after payment | Status not in paid list; invalid billing email; missing/conflicting product mapping; no matching ZIP or exact file; mail failure. | Confirm the actual order status code, assignment, file naming/storage, and Joomla mail. Correct it, then cause a safe paid-status callback/retry. |
| Product version is absent | No readable matching strict semantic-version ZIP or invalid field mapping. | Check Safe Path, base/extension folders, prefix, filename, permissions, and published field. |
| Purchase link says forbidden/limit reached | Expired/revoked licence, bad key/token, wrong file/channel, or consumed limit. | Check licence status/expiry/count in the component and use the original link/file. Reactivation does not reset the count. |
| Package/file not found | The recorded purchase file was removed/renamed or filesystem access changed. | Restore the exact original file to the recorded protected folder and correct owner/group/read permissions. |
| Joomla sees no update | Manifest update server wrong; XML stale; cache; feed returns HTML/challenge; version not newer. | Publish feeds, open XML directly, clear update cache, confirm target/version, and whitelist machine access. |
| Update detected but download fails | Key absent/invalid/expired/revoked; host/installer plugin mismatch; package mapping mismatch. | Save the correct key, enable/configure the sold extension's installer plugin, and verify host/prefix/filename. |
| Feed publishing reports conflict | Same extension folder has different mappings, or one XML filename is assigned to multiple extensions. | Make assignments unique and consistent; use one permanent prefix per extension. |
| Feed directory error | Invalid directory segments, path outside Joomla root, absent/unwritable directory, or mismatched base URL. | Use a valid relative path such as files/updatesxml, create it, fix ownership, and set its exact public URL. |
| Checksum mismatch | ZIP changed after XML generation or wrong ZIP/XML deployed. | Re-upload the final exact ZIP, republish XML, and verify SHA-256 independently. |
| Renewal reminders never send | Old licence lacks snapshot; reminders disabled; outside window; task/runner inactive; mail error. | Inspect stored/licence timing, task status/history, scheduler runner, and mail logs. Test with a newly issued staging licence. |
| Coupon exists but reminder failed | Mail failed after coupon creation. | Fix mail and rerun. The task reuses the valid coupon or replaces it after expiry. |
| All issues / Bugs | GitHub | Please report all issues on GitHub. |
HTTP failure meanings
- 403: missing/invalid authorization, inactive licence, wrong package/channel, invalid order link, or purchase limit reached.
- 404: unknown dynamic feed/extension, no matching releases, or requested protected file missing/unreadable.
- 500: plugin/storage configuration problem, checksum failure, or server-side publishing/download error.
16. Backup, recovery, and uninstall
Back up
- The Joomla database, especially
#__vmupdatekeymanager_licenses, VirtueMart order/coupon tables, extension parameters, update sites, and scheduled tasks. - The full VirtueMart Safe Path release tree.
- The public update XML directory.
- Joomla
configuration.phpconsistently with the database because the Joomla secret participates in order-link HMAC validation.
The full customer keys cannot be reconstructed from the database hashes. A lost key must be recovered from the customer's original email or replaced through a new purchase/licence workflow.
Recovery validation
- Restore database, Joomla configuration, protected packages, and public XML together.
- Verify plugin/task enablement and filesystem ownership.
- Republish XML if paths/hosts changed.
- Test an existing order-detail link and a valid update key on staging.
Uninstall
Uninstall the parent package through Joomla. The component uninstall SQL drops #__vmupdatekeymanager_licenses, permanently removing licence/audit records. Treat uninstall as destructive and take a verified backup first. Protected package files and public XML may remain on disk and should be reviewed/removed manually only when no longer required.
17. Technical reference
Endpoints and parameters
| Purpose | Task | Parameters |
|---|---|---|
| Protected download | download.get |
file; channel=purchase|update; either key/dlid, or for purchase links license + token. |
| Dynamic feed | updatefeed.get |
extension matching the configured extension folder. |
Licence database fields
| Area | Fields |
|---|---|
| Identity/security | id, license_hash, key_hint, revoked |
| Order/product | order_id, order_number, order_item_id (unique), virtuemart_product_id, product_sku, product_name, joomla_user_id, email |
| Package/storage | package_prefix, storage_folder, purchase_file |
| Usage/term | purchase_download_limit, purchase_download_count, download_count, created_on, expires_on, last_used_on |
| Renewal | renewal_params, renewal_coupon_id, renewal_coupon_code, renewal_coupon_expires_on, renewal_reminder_sent_on |
Scheduled task routines
vmupdatekeymanager.renewal— create/reuse coupons and send eligible renewal reminders.vmupdatekeymanager.publishfeed— publish all configured protected extension update XML files.
Audit counters
purchase_download_countcounts successful initial-package responses and is constrained bypurchase_download_limit.download_countcounts successful update-channel downloads and has no configured maximum while active.last_used_onrecords the most recent successful download of either channel.
18. Production checklists
Initial launch
- Backups completed and staging installation tested.
- Parent package installed; component and all plugins enabled.
- VirtueMart Safe Path outside public web root and readable by PHP.
- Global paid statuses, term, download limit, and email templates reviewed.
- Reusable custom field correctly configured and assigned once per product.
- Permanent extension folder, prefix, and unique XML filename selected.
- First exact release ZIP uploaded to protected storage.
- Public XML directory writable and base URL correct.
- Both daily scheduled tasks published and scheduler runner functional.
- Mail delivery and VirtueMart coupon usage tested.
- Firewall/CDN permits direct XML and protected endpoint access.
- Paid staging order issues one email/key and a working limited link.
- Valid key updates successfully; invalid/revoked/expired key fails.
Release day
- Final package version and filename match the permanent prefix.
- Final ZIP uploaded unchanged to the correct protected folder.
- Feeds published with zero unexpected failures.
- Public XML version, filename, element, target, and SHA-256 verified.
- Joomla update cache cleared on staging and update discovered.
- Installation completed with a valid key and application smoke-tested.
- Older packages retained for outstanding purchase links.