code-heaven/license-sdk

Vendor SDK for the Code Heaven Developer License API (validation, domain activation, updates, downloads).

Maintainers

Package info

github.com/AskersCodes/code-heaven-license-sdk

pkg:composer/code-heaven/license-sdk

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-06-04 17:38 UTC

This package is auto-updated.

Last update: 2026-06-08 18:46:02 UTC


README

Vendor SDK for the Code Heaven Developer License API. Use it inside your plugin/theme/app to validate licenses, activate and free domain seats, check for updates, and fetch signed download URLs.

This is the vendor server-to-server flow. Authentication is a single vendor API key (X-CH-Vendor-Key), not the buyer OAuth flow.

  • Base URL: https://api.code-heaven.com/v1
  • PHP 8.0+
  • No hard runtime dependencies (cURL or file_get_contents); pluggable HTTP so it works happily inside WordPress via wp_remote_request.

Install

composer require code-heaven/license-sdk

Quick start: validate and gate

use CodeHeaven\License\Client;
use CodeHeaven\License\LicenseException;
use CodeHeaven\License\TransportException;

$client = new Client('YOUR_VENDOR_KEY', [
    'cacheTtl'     => 12 * 3600,        // serve a fresh answer for 12h
    'offlineGrace' => 14 * 24 * 3600,   // keep working up to 14 days if the API is down
]);

try {
    $res = $client->validate('LICENSE-KEY', 'shop.example.com', 'booknetic-pro');
} catch (LicenseException $e) {
    // 403: invalid / expired / domain_not_activated  ($e->apiCode tells you which)
    bail("License problem: {$e->apiCode}");
} catch (TransportException $e) {
    // API unreachable AND no cached valid result within grace.
    bail('License server unreachable.');
}

if ($res['valid']) {
    enable_premium_features();
} else {
    // $res['status'] is one of: valid | invalid | expired | domain_not_activated | revoked
    disable_premium_features($res['status']);
}

validate() returns the decoded API body plus two SDK markers:

key meaning
valid bool
status valid | invalid | expired | domain_not_activated | revoked
product product slug (or null)
expiresAt ISO 8601 string or null
activations [{domain, activatedAt}, ...]
seatLimit int
_cached present & true when served from cache
_offline present & true when served from cache during an outage

Activate a seat on first run

When validate() returns domain_not_activated (a LicenseException with apiCode === 'domain_not_activated'), claim a seat:

use CodeHeaven\License\SeatLimitException;

try {
    $client->activateDomain('LICENSE-KEY', 'shop.example.com');
} catch (SeatLimitException $e) {
    // 409 — buyer has used every seat; prompt them to deactivate another site.
}

Check for and apply an update

$update = $client->checkUpdate('LICENSE-KEY', 'booknetic-pro', '4.1.0', 'shop.example.com');

if ($update['hasUpdate']) {
    // latestVersion + changelog[{version,date,notes}]
    $dl = $client->download('LICENSE-KEY', 'booknetic-pro', 'shop.example.com', $update['latestVersion']);
    // $dl['url'] is a short-lived signed package URL; $dl['expiresAt'] tells you when it dies.
    download_and_install_zip($dl['url']);
}

Free the seat on uninstall

$client->deactivateDomain('LICENSE-KEY', 'shop.example.com');

Caching and offline grace

validate() is cached so repeated calls in one request never hit the wire twice, and the result persists across requests when you supply a persistent cache:

  • cacheTtl — how long a cached answer is considered fresh. Within this window validate() returns the cached body without any network call.
  • offlineGrace — how long a cached valid answer may be served after it goes stale, but only when the API is unreachable. This stops a transient outage from locking out paying customers. Served results carry _offline => true.

Persistent caches:

use CodeHeaven\License\FileCache;
use CodeHeaven\License\TransientCache;

// Outside WordPress:
new Client($key, ['cache' => new FileCache('/var/cache/myplugin')]);

// Inside WordPress (rides transients / the object cache):
new Client($key, ['cache' => new TransientCache('myplugin_')]);

The default is an in-process ArrayCache (no cross-request persistence, so no offline grace across requests — supply FileCache/TransientCache in production).

Custom HTTP transport (WordPress, Guzzle, …)

Inject any callable as http. It receives (method, url, headers, ?body) and must return ['status' => int, 'headers' => array, 'body' => string], or throw TransportException on a connection failure (which triggers offline grace).

new Client($key, [
    'http' => function (string $method, string $url, array $headers, ?string $body): array {
        $res = wp_remote_request($url, compact('method', 'headers', 'body') + ['timeout' => 15]);
        if (is_wp_error($res)) {
            throw new \CodeHeaven\License\TransportException($res->get_error_message());
        }
        return [
            'status'  => (int) wp_remote_retrieve_response_code($res),
            'headers' => wp_remote_retrieve_headers($res)->getAll(),
            'body'    => (string) wp_remote_retrieve_body($res),
        ];
    },
]);

See examples/gate-plugin.php for a complete, fail-safe WordPress gate (validate → activate-on-first-run → admin nag → deactivate-on-uninstall).

Exceptions

All extend CodeHeaven\License\CodeHeavenException and expose ->statusCode, ->apiCode, ->response.

Exception HTTP When
AuthException 401 Vendor key missing/invalid (your packaging bug)
LicenseException 403 license_invalid / expired / domain_not_activated
SeatLimitException 409 seat_limit_exceeded on activateDomain()
RateLimitException 429 Rate limited (->retryAfter holds the hint, if any)
TransportException API unreachable and no usable cached result
CodeHeavenException 4xx/5xx Any other non-2xx response

Note: an invalid or expired license that the API reports with HTTP 200 and valid:false is returned as data, not thrown. The API only throws LicenseException for the 403 cases above.

Development

composer install
composer test      # phpunit

License

MIT.