LucrativeIt Digital Download Licensing Pro – User Guide

Pro is an add-on for the free LucrativeIt Digital Download and Licensing plugin. It adds:

1. Installation

  1. Install and activate LucrativeIt Digital Download and Licensing (free).
  2. Upload the lucrativeit-digital-download-licensing-pro folder to wp-content/plugins/ and activate it.
  3. A Pro section appears in the LucrativeIt Digital Download sidebar, under the normal menu:
    • Pro Settings
    • User Guide (this page)
  4. The product editor gets two Pro panels: Price per number of domains in step 2 (Pricing) and Automatic updates with license key in step 4 (Product file).
  5. The normal Licenses screen gets a Domains column with a Manage button for each license.

2. Seller setup (your store)

2.1 Prepare the product

Go to LucrativeIt Digital Download → Products and edit (or add) your product:

FieldExampleUsed for
Product typeWordPress plugin / Laravel scriptShown in the API
Software version1.0.0Clients compare it with their installed version
Changelog= 1.0.1 = Fixed…Shown in the update popup / artisan output
Filemy-plugin.zipThe file delivered as the update
Releasing an update = upload the new ZIP and raise Software version (e.g. 1.0.0 → 1.0.1). That's it.

2.2 Price per number of domains

Open the product editor: Products → Edit → step 2 (Pricing). Below Plan prices you'll find the Price per number of domains panel with one list per plan: Lifetime, Yearly and Monthly. A list is dimmed while its plan is switched off above.

Example for a product with Yearly price 54 and Lifetime price 199:

PlanDomainsPrice
Yearly154
Yearly289
Yearly5149
Lifetime1199
Lifetime0 (unlimited)499

Each row is named after its domains (2 sites, Unlimited sites) and includes automatic updates.

2.3 What buyers see

Nothing to paste: every price card of the product automatically gets a Number of websites choice under its features. Choosing an option updates the price on the card, and Checkout opens your normal checkout page with that price. After payment the license key is emailed as usual and the domain limit is attached to it automatically.

While a larger option is chosen, Add to cart is hidden on that card (the cart sells the plain plan price). A plan with only one row shows its domain limit as a line (for example "1 site") instead of a choice.

The [lddl_pro_packages product_id="123"] shortcode still works if you want a separate package table on another page.

2.4 Customer domain management

Put this shortcode on the customer account page (or any page for logged-in customers):

[lddl_pro_my_licenses]

Customers see each license, the package, domains used / allowed, and can Deactivate a domain to move the license to another site (can be disabled in Pro Settings).

2.5 Enable updates for a product

Open the product editor: Products → Edit → step 4 (Product file). Below Current version and Changelog you'll find the Automatic updates with license key panel:

  1. Upload the ZIP and set Current version (and Changelog).
  2. Switch on Enable license-gated updates for this product.
  3. Update slug – for WordPress plugins this must be the plugin folder name (e.g. my-awesome-plugin). For Laravel scripts use any unique slug (e.g. my-laravel-app).
  4. Optional: Requires WordPress, Tested up to, Requires PHP, icon and banner (shown in the WordPress "View details" popup).
  5. Click Publish / Update product. The product and its update settings are saved together.
  6. Note the product_id shown under Client config at the bottom of the panel – you put it in the client SDK.

2.6 Pro settings

SettingDefaultMeaning
Default domain limit1Used when a license has no package and the product has no default package
Development domains are freeOnlocalhost, 127.0.0.1, *.test, *.local, staging.*, dev.* don't count
Only deliver updates to activated domainsOnUpdate downloads require an activation for the requesting domain
Customers can deactivate their domainsOnShows the Deactivate button in [lddl_pro_my_licenses]
Download link lifetime60 minSigned update links expire after this time
API requests per IP per minute60Basic brute-force protection

2.7 Manage a license (admin)

Open Licenses. The Domains column shows domains used / allowed for every license (red when the limit is reached). Click Manage to open the license below its row:

3. How the buyer uses the license key

  1. Buys a package → receives LUCRATIVEDIGIPROD-XXXX-XXXX-XXXX-XXXX by email / on the account page.
  2. Enters the key in your product:
    • WordPress plugin: Settings → My Plugin License → Activate license.
    • Laravel script: php artisan license:activate LUCRATIVEDIGIPROD-XXXX-XXXX-XXXX-XXXX.
  3. The site's domain is activated (counts toward the package limit).
  4. Updates:
    • WordPress: appear in Dashboard → Updates and Plugins, installed with Update now (auto-updates work too).
    • Laravel: php artisan license:update.
  5. When the subscription expires, the site still sees that a new version exists, but the download is blocked with "renew your license".
  6. To move to a new site: deactivate on the old site (or from the store account page), then activate on the new one.

4. WordPress plugin integration (for the seller)

Copy sdk/wordpress/class-lddl-license-updater.php into the plugin you sell, e.g. my-awesome-plugin/includes/.

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Version:     1.0.0
 */

define( 'MY_AWESOME_PLUGIN_VERSION', '1.0.0' );

require_once __DIR__ . '/includes/class-lddl-license-updater.php';

function my_awesome_plugin_license() {
	static $updater = null;
	if ( null === $updater ) {
		$updater = new LDDL_License_Updater( array(
			'store_url'   => 'https://your-store.com/',
			'product_id'  => 123,
			'slug'        => 'my-awesome-plugin',   // = plugin folder name = Update slug
			'plugin_file' => __FILE__,
			'version'     => MY_AWESOME_PLUGIN_VERSION,
			'name'        => 'My Awesome Plugin',
		) );
	}
	return $updater;
}
add_action( 'plugins_loaded', 'my_awesome_plugin_license' );

// Optional: premium features only with an active license.
add_action( 'init', function () {
	if ( my_awesome_plugin_license()->is_active() ) {
		// register premium features…
	}
} );

A complete example is in sdk/wordpress/example-plugin/my-awesome-plugin/.

ZIP structure (upload this as the product file):

my-awesome-plugin.zip
└── my-awesome-plugin/
    ├── my-awesome-plugin.php
    └── includes/class-lddl-license-updater.php

What the updater does:

5. Laravel script integration (for the seller)

5.1 Add the package to your script

Copy sdk/laravel into your project, e.g. packages/lddl-license, and add to your script's composer.json:

"repositories": [
    { "type": "path", "url": "packages/lddl-license" }
],
"require": {
    "lucrativeit/lddl-license": "*"
}

Run composer update lucrativeit/lddl-license. The service provider is auto-discovered.
(Optional) publish the config: php artisan vendor:publish --tag=lddl-license.

5.2 .env of the buyer's installation

LDDL_STORE_URL=https://your-store.com/
LDDL_PRODUCT_ID=123
LDDL_APP_VERSION=1.0.0
LDDL_LICENSE_KEY=

Ship LDDL_STORE_URL, LDDL_PRODUCT_ID and LDDL_APP_VERSION in your .env.example; the buyer only adds the key (or uses license:activate).

5.3 Artisan commands

php artisan license:activate LUCRATIVEDIGIPROD-XXXX-XXXX-XXXX-XXXX   # activate this domain
php artisan license:status                                          # validity, package, domains, expiry
php artisan license:update --check                                  # is there a new version?
php artisan license:update                                          # download + install
php artisan license:update --yes                                    # no confirmation (cron / CI)
php artisan license:deactivate                                      # free this domain

license:update does:

  1. Checks the store (license valid + domain activated + package includes updates).
  2. Downloads the ZIP through a signed link.
  3. Backs up the app to storage/app/lddl-backups/ (excluding vendor, node_modules, storage, .git).
  4. php artisan down → extracts the ZIP over the app (a single top-level folder in the ZIP is stripped) without touching .env, storage, bootstrap/cache, public/storage, public/uploads.
  5. Runs migrate --force and optimize:clear (configurable in after_update).
  6. php artisan up and saves the new installed version.

If your ZIP does not contain vendor/, add composer install --no-dev to your own post-update instructions.

5.4 Protect routes with the license

// routes/web.php
Route::middleware(['web', 'license'])->group(function () {
    Route::get('/dashboard', DashboardController::class);
});

The check is cached (cache_minutes, default 12 h) and keeps working for grace_days (default 7) if your store is unreachable. Set redirect_route in the config to redirect to an "Enter license" page instead of a 403.

5.5 Use the manager in code

use Lddl\License\LicenseManager;

$license = app(LicenseManager::class);

if (! $license->isActive()) {
    // show "activate your license" banner
}

$update = $license->checkUpdate();
if ($update['success'] && version_compare($update['new_version'], $license->installedVersion(), '>')) {
    // show "Version {$update['new_version']} is available" in your admin panel
}

5.6 In-app "Update now" button (optional)

use Lddl\License\LicenseManager;
use Lddl\License\Updater;

Route::post('/admin/update', function (LicenseManager $license, Updater $updater) {
    $update = $license->checkUpdate();
    $log = [];
    $version = $updater->run($update, function ($line) use (&$log) { $log[] = $line; });
    return back()->with('status', "Updated to {$version}")->with('log', $log);
})->middleware(['web', 'auth', 'can:admin']);

5.7 Daily license check (optional)

// app/Console/Kernel.php (Laravel ≤10) or routes/console.php (Laravel 11+)
Schedule::command('license:status')->daily();

6. Plain PHP scripts (no framework)

Use sdk/php/LddlLicenseClient.php (needs cURL + Zip):

require __DIR__ . '/LddlLicenseClient.php';

$client = new LddlLicenseClient([
    'store_url'  => 'https://your-store.com/',
    'product_id' => 123,
    'version'    => '1.0.0',
    'domain'     => $_SERVER['HTTP_HOST'],
    'platform'   => 'php',
]);

$key = 'LUCRATIVEDIGIPROD-XXXX-XXXX-XXXX-XXXX';

$res = $client->activate($key);
if (! $res['success']) {
    exit($res['message']);
}

$update = $client->checkUpdate($key);
if ($update['success'] && $update['update_available'] && $update['download_url']) {
    $zip = $client->download($update['download_url'], __DIR__ . '/tmp');
    $client->install($zip, __DIR__, ['.env', 'config.php', 'uploads']);
    // save $update['new_version'] as your installed version
}

7. REST API reference

Base URL: https://your-store.com/wp-json/lddl-pro/v1

All responses are JSON with success, code and message. Send parameters as form fields or JSON.

EndpointParamsSuccess response
POST /license/activatelicense_key, domain, platform, version, optional product_id / slugdomain, activated_at, license{…}
POST /license/deactivatelicense_key, domaindomain, license{…}
GET/POST /license/checklicense_key, domain, versionactivated: true, license{…}
GET/POST /update/checklicense_key, domain, version, optional product_id / slugupdate_available, new_version, download_url (or null + download_blocked), changelog, requires_wp, tested_wp, requires_php, license{…}
GET /update/infoslug or product_idName, version, description, changelog, icons, banners
GET /update/downloadSigned URL from /update/checkThe ZIP file

license{…} object:

{
  "status": "active",
  "product_id": 123,
  "product_name": "My Awesome Plugin",
  "plan": "yearly",
  "expires_at": "2027-09-30T10:00:00+00:00",
  "package": "Business – 5 Sites",
  "max_domains": 5,
  "domains_used": 2,
  "domains_left": 3,
  "updates_enabled": true
}

Error codes:

CodeHTTPMeaning
invalid_license403Key not found
license_expired403Subscription expired / cancelled
domain_limit_reached409All package slots are used
not_activated403Domain not activated for this key
product_mismatch403Key belongs to another product
updates_disabled403Package has no updates
no_release404Updates not enabled / no version / no file
invalid_domain400Missing domain
rate_limited429Too many requests

cURL examples:

curl -X POST https://your-store.com/wp-json/lddl-pro/v1/license/activate \
  -d license_key=LUCRATIVEDIGIPROD-XXXX-XXXX-XXXX-XXXX -d domain=client.com -d platform=laravel -d version=1.0.0

curl -X POST https://your-store.com/wp-json/lddl-pro/v1/update/check \
  -d license_key=LUCRATIVEDIGIPROD-XXXX-XXXX-XXXX-XXXX -d domain=client.com -d version=1.0.0

Domains are normalised: https://www.Client.com/shop → client.com.

8. Developer hooks (store side)

HookTypeArguments
lddl_pro_domain_activatedaction$license, $domain
lddl_pro_domain_deactivatedaction$license_id, $domain
lddl_pro_license_package_attachedaction$license, $package
lddl_pro_update_downloadedaction$license, $domain, $release
lddl_pro_is_local_domainfilter$is_local, $host
lddl_pro_client_ipfilter$ip (e.g. read CF-Connecting-IP behind Cloudflare)

9. Troubleshooting

ProblemFix
WordPress says "Automatic update is unavailable"License expired, domain not activated, package without updates, or no file uploaded. Check Licenses → Manage.
WordPress installs into a wrong folderThe ZIP must contain the plugin folder (named like the slug) at its root.
No update shownRaise Software version on the product; on the client click Dashboard → Updates → Check again or the license page's Check for updates now.
domain_limit_reachedDeactivate an old domain (customer account page or admin), upgrade the package, or set a custom limit.
rate_limited behind a proxy/CDNUse the lddl_pro_client_ip filter to read the real client IP.
Package price not used at checkoutChoose the number of websites on the price card and use Checkout (it adds lddl_package to the checkout URL). The cart sells the plain plan price.