Cart empty

 x 

VirtueMart Update Key Manager

Complete administrator, store-owner, and extension-author documentation for protected purchases, private Joomla updates, expiring licences, and renewal reminders.

Version 1.2.1 Joomla 5 & 6 VirtueMart English & Slovenian UI

Author: Darko Fatur · This email address is being protected from spambots. You need JavaScript enabled to view it. · https://topoweryou.com · GPL-2.0-or-later

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.
1. Paid VirtueMart order
2. Key and licence issued
3. Buyer receives private link
4. Joomla detects public XML
5. Key authorizes ZIP stream

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

ExtensionIdentifierResponsibility
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.

Install the parent package only. Do not install child ZIP files individually. Child uninstall is blocked by the package manifest.

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

  1. Test installation, email delivery, purchase downloads, update discovery, and update installation on staging.
  2. Back up the Joomla database and site files.
  3. Confirm the Safe Path is outside public_html or the equivalent document root.
  4. 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

  1. Open System → Install → Extensions.
  2. Upload pkg_vmupdatekeymanager_v1.2.1.zip.
  3. Verify the component and three plugins appear in Joomla's extensions list.
  4. Open System → Manage → Plugins and verify all three plugins are enabled. The installer enables them automatically.
  5. 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.

Do not rename a released ZIP after publishing its XML: the XML checksum and filename must describe the exact bytes offered to customers.

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

ItemRuleExample
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
Keep old release ZIPs for as long as buyers must be able to use the initial purchase link captured at checkout. Automatic updates may use the newest matching version, but purchase links stay tied to the originally issued filename.

6. Configuration reference

Component options

Open Components → VirtueMart Update Key Manager → Options.

OptionDefaultPurpose
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.

OptionDefault / rangeMeaning
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.
Precedence: a value saved on a reusable VirtueMart custom field overrides the global plugin value. An empty field inherits the global value. Product-assignment fields (download type, protected folder, package prefix or exact filename, XML filename, and renewal URL) remain product-specific.

Email placeholders

TemplateAvailable 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

  1. Open VirtueMart → Products → Custom Fields and create a field.
  2. Set Custom Field Type to Plugin.
  3. Select VirtueMart Update Key Manager.
  4. Publish it. Set Cart Attribute to Yes, Cart Input to No, and Administrator only to No.
  5. Use a shopper-facing title such as Current version and choose the desired VirtueMart display position.
  6. 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

  1. Upload at least one matching ZIP to SAFE_PATH/base_folder/extension_folder/.
  2. Open the product page. The field should display the highest matching version, such as v1.6.1.
  3. Run Publish update XML files from the component.
  4. Open the resulting public XML URL and confirm it is XML, not an HTML error/challenge.
If no matching ZIP exists when a paid order is handled, the attempted licence is not retained and no licence email is sent. A later paid-status callback can retry safely after the ZIP is uploaded.

8. Customer and licence lifecycle

Issuance

  1. VirtueMart changes an order to a configured paid status.
  2. The plugin checks each order item for a valid Update Key Manager product assignment.
  3. It validates the billing email, storage mapping, and either the latest matching extension release or the exact configured file.
  4. 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.
  5. It emails the protected purchase URL and download limit. Extension products also include the update key and expiry.
  6. 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

  1. Joomla reads the public update XML and detects a newer version.
  2. The extension-specific installer plugin or Joomla download-key handling adds key=….
  3. The shop hashes the submitted key and finds the licence.
  4. The endpoint validates active/not-expired state and ensures the requested ZIP matches the licence's package prefix plus a semantic version.
  5. 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

ActionPermissionEffect
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.
Delete is irreversible. It removes the licence record and its associated renewal coupon; the customer loses authorization. Use Revoke when temporary or reversible blocking is intended.

10. Publish automatic updates

Recommended static-feed workflow

  1. Build the exact release ZIP using the permanent package prefix and a strict semantic version.
  2. Upload it alongside older versions in the protected extension directory.
  3. Open the component and click Publish update XML files, or allow the daily task to run.
  4. Open the public XML and verify version, protected download URL, and SHA-256 checksum.
  5. On a staging customer site, clear Joomla's update cache, discover updates, and install using a valid key.

What the publisher does

  1. Reads every published Update Key Manager product assignment.
  2. Resolves its XML filename. A blank value reuses existing XML matching the package element or creates extension-folder.xml; the manager preserves vmupdatekeymanager.xml compatibility.
  3. Rejects conflicting extension-folder mappings or duplicate XML filenames.
  4. Finds the highest matching semantic-version ZIP.
  5. Computes SHA-256 from that exact file.
  6. Builds Joomla update XML for a package targeting Joomla (5|6)\..*.
  7. 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

11. Integrate a sold Joomla extension

Add a permanent update server

The sold package manifest must point to its public static XML:

<updateservers>
  <server type="extension" name="Route Calculation Help Updates">https://shop.example.com/files/updatesxml/route-calculation.xml</server>
</updateservers>

The URL must return XML directly without login, redirects to HTML, or JavaScript/browser challenges.

Download-key declaration in update XML

<downloads>
  <downloadurl type="full" format="zip">https://shop.example.com/index.php?option=com_vmupdatekeymanager&amp;task=download.get&amp;file=pkg_route_calculation_v1.7.0.zip&amp;channel=update</downloadurl>
</downloads>
<dlid prefix="key=" suffix="" />

The manager's publisher generates this structure, version, package element, target platform, and SHA-256 automatically.

Include an extension-specific installer plugin

The sold extension should include an installer plugin that stores its key and attaches it only when the request matches the expected shop hostname, component task, and package filename. Starting examples are provided in:

  • integration/routecalculationupdatekey.php
  • integration/routecalculationupdatekey.xml
  • integration/update-server-example.xml
Never append a customer key to arbitrary downloads or other hosts. Match the exact host, endpoint, and filename pattern to prevent disclosure.

12. Renewal reminders and coupons

Enable and schedule

  1. Enable coupon usage in VirtueMart.
  2. Configure global/custom-field renewal defaults.
  3. Set an optional per-product Renewal product URL; blank generates that product's VirtueMart detail URL.
  4. Verify VirtueMart update-key renewal reminders is published in System → Scheduled Tasks.
  5. 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.

Licences created before renewal parameters were introduced do not automatically gain reminders. New purchases create new licences with current snapshot data.

13. Security model

ControlProtection
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

  1. Update package/child manifest versions and release notes as appropriate.
  2. Build the final ZIP once.
  3. Verify its name exactly matches the configured prefix and semantic version.
  4. Upload that unchanged ZIP into protected storage.
  5. Publish feeds immediately or monitor the scheduled publishing task.
  6. Confirm the public XML version and independently compare SHA-256.
  7. Test update discovery and installation with a valid staging licence.
  8. 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

SymptomLikely causeResolution
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.php consistently 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

  1. Restore database, Joomla configuration, protected packages, and public XML together.
  2. Verify plugin/task enablement and filesystem ownership.
  3. Republish XML if paths/hosts changed.
  4. 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

PurposeTaskParameters
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

AreaFields
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_count counts successful initial-package responses and is constrained by purchase_download_limit.
  • download_count counts successful update-channel downloads and has no configured maximum while active.
  • last_used_on records 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.