5 Commits

Author SHA1 Message Date
086755cb11 Update translations for v0.5.5
Regenerated .pot template and recompiled German translations.
All 391 strings translated.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 17:07:36 +01:00
0b58de193e Fix critical signature compatibility with client library (v0.5.5)
CRITICAL: Key derivation now uses native hash_hkdf() for RFC 5869
compliance. Previous custom implementation was incompatible with
the magdev/wc-licensed-product-client library.

Changes:
- ResponseSigner::deriveCustomerSecret() now uses hash_hkdf()
- Added missing domain validation to /activate endpoint
- Customer secrets will change after upgrade (breaking change)

The signature algorithm now matches the client's ResponseSignature::deriveKey():
- IKM: server_secret
- Length: 32 bytes
- Info: license_key

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 17:06:18 +01:00
ae49b262fa Update wc-licensed-product-client dependency
Updated magdev/wc-licensed-product-client from 64d215c to 5e4b5a9.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 17:02:03 +01:00
5d5bb7e595 Align REST API with client documentation (v0.5.4)
Fixed HTTP status codes for API responses:
- /validate now returns 404 for license_not_found (was 403)
- Added status code mapping: 404 not found, 500 server errors, 403 others

Added configurable rate limiting:
- WC_LICENSE_RATE_LIMIT constant for requests per window
- WC_LICENSE_RATE_WINDOW constant for window duration in seconds

Fixed license_key validation:
- Now enforces minimum 8 characters across all endpoints

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 17:00:52 +01:00
bee9854c18 Add release package v0.5.3
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 16:15:51 +01:00
11 changed files with 214 additions and 57 deletions

View File

@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.5.5] - 2026-01-26
### Fixed
- **CRITICAL:** Response signing key derivation now uses native `hash_hkdf()` for RFC 5869 compliance
- Key derivation now matches client library (`SecureLicenseClient`) exactly
- Added missing domain validation to `/activate` endpoint (1-255 characters)
### Changed
- `ResponseSigner::deriveCustomerSecret()` now uses `hash_hkdf('sha256', $serverSecret, 32, $licenseKey)`
- Previous custom HKDF-like implementation was incompatible with client library
### Security
- Signatures generated by server now verify correctly with `magdev/wc-licensed-product-client`
- All three API endpoints now have consistent parameter validation
## [0.5.4] - 2026-01-26
### Fixed
- REST API `/validate` endpoint now returns HTTP 404 for `license_not_found` error (was 403)
- License key validation now enforces minimum 8 characters per API documentation
### Added
- Configurable rate limiting via `WC_LICENSE_RATE_LIMIT` and `WC_LICENSE_RATE_WINDOW` constants
- Rate limit now defaults to 30 requests per 60 second window (configurable)
### Changed
- Improved HTTP status code mapping: 404 for not found, 500 for server errors, 403 for all other errors
- Rate limiting implementation now uses configurable constants instead of hardcoded values
## [0.5.3] - 2026-01-26 ## [0.5.3] - 2026-01-26
### Added ### Added

View File

@@ -1378,3 +1378,62 @@ Major feature release adding support for WooCommerce variable products. Customer
- Order meta `_licensed_product_domains` now includes optional `variation_id` field - Order meta `_licensed_product_domains` now includes optional `variation_id` field
- License generation uses variation settings when `variation_id` is present in order item - License generation uses variation settings when `variation_id` is present in order item
- Backward compatible: existing simple licensed products continue to work unchanged - Backward compatible: existing simple licensed products continue to work unchanged
### 2026-01-26 - Version 0.5.4 - API Compliance
**Overview:**
Bug fix release aligning server implementation with client documentation at `magdev/wc-licensed-product-client`.
**Fixed:**
- `/validate` endpoint now returns HTTP 404 for `license_not_found` error (was returning 403)
- License key validation now enforces minimum 8 characters across all API endpoints
**Implemented:**
- Configurable rate limiting via `WC_LICENSE_RATE_LIMIT` constant (default: 30 requests)
- Configurable rate window via `WC_LICENSE_RATE_WINDOW` constant (default: 60 seconds)
- HTTP status code mapping: 404 for not found, 500 for server errors, 403 for all other errors
**Modified files:**
- `src/Api/RestApiController.php` - Added configurable rate limiting, fixed HTTP status codes, added license_key validation
**Technical notes:**
- Rate limiting now uses `getRateLimit()` and `getRateWindow()` methods instead of constants
- New `getStatusCodeForResult()` method maps error codes to HTTP status codes
- License key validation callback added to all three endpoints (validate, status, activate)
- Uses PHP 8 match expression for status code mapping
### 2026-01-26 - Version 0.5.5 - Critical Signature Fix
**Overview:**
Critical bug fix for response signing. The key derivation algorithm was incompatible with the client library, causing signature verification failures.
**Critical Fix:**
- Key derivation now uses PHP's native `hash_hkdf()` function per RFC 5869
- Previous custom implementation produced different keys than the client library
- Signatures now verify correctly with `magdev/wc-licensed-product-client`
**Additional Fix:**
- Added missing domain validation to `/activate` endpoint (1-255 characters)
**Modified files:**
- `src/Api/ResponseSigner.php` - Fixed key derivation to use `hash_hkdf()`
- `src/Api/RestApiController.php` - Added domain validation to `/activate` endpoint
**Technical notes:**
- Old implementation: `hash_hmac('sha256', $prk . "\x01", $serverSecret)` - custom HKDF-like
- New implementation: `bin2hex(hash_hkdf('sha256', $serverSecret, 32, $licenseKey))` - RFC 5869
- Parameters match client's `ResponseSignature::deriveKey()` exactly:
- IKM (input keying material): server_secret
- Length: 32 bytes (256 bits)
- Info: license_key (context-specific info)
- **Breaking change for existing signatures** - customer secrets will change after upgrade

4
composer.lock generated
View File

@@ -12,7 +12,7 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git", "url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git",
"reference": "64d215cb265a64ff318cfbb954dd128b0076dc1d" "reference": "5e4b5a970f75d0163c5496581d963a24ade4f276"
}, },
"require": { "require": {
"php": "^8.3", "php": "^8.3",
@@ -52,7 +52,7 @@
"issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues", "issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues",
"source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client" "source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client"
}, },
"time": "2026-01-24T13:32:11+00:00" "time": "2026-01-26T15:54:37+00:00"
}, },
{ {
"name": "psr/cache", "name": "psr/cache",

View File

@@ -4,8 +4,8 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WC Licensed Product 0.5.0\n" "Project-Id-Version: WC Licensed Product 0.5.0\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 16:08+0100\n" "POT-Creation-Date: 2026-01-26 17:06+0100\n"
"PO-Revision-Date: 2026-01-25T18:30:00+00:00\n" "PO-Revision-Date: 2026-01-25T18:30:00+00:00\n"
"Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n" "Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n"
"Language-Team: German (Switzerland) <de_CH@li.org>\n" "Language-Team: German (Switzerland) <de_CH@li.org>\n"
@@ -1282,32 +1282,32 @@ msgstr "Lizenz erfolgreich überprüft!"
msgid "License validation failed." msgid "License validation failed."
msgstr "Lizenzvalidierung fehlgeschlagen." msgstr "Lizenzvalidierung fehlgeschlagen."
#: src/Api/RestApiController.php:84 #: src/Api/RestApiController.php:106
msgid "Too many requests. Please try again later." msgid "Too many requests. Please try again later."
msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut." msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut."
#: src/Api/RestApiController.php:345 src/Api/RestApiController.php:378 #: src/Api/RestApiController.php:400 src/Api/RestApiController.php:433
#: src/License/LicenseManager.php:475 #: src/License/LicenseManager.php:475
msgid "License key not found." msgid "License key not found."
msgstr "Lizenzschlüssel nicht gefunden." msgstr "Lizenzschlüssel nicht gefunden."
#: src/Api/RestApiController.php:386 #: src/Api/RestApiController.php:441
msgid "This license is not valid." msgid "This license is not valid."
msgstr "Diese Lizenz ist ungültig." msgstr "Diese Lizenz ist ungültig."
#: src/Api/RestApiController.php:396 #: src/Api/RestApiController.php:451
msgid "License is already activated for this domain." msgid "License is already activated for this domain."
msgstr "Die Lizenz ist bereits für diese Domain aktiviert." msgstr "Die Lizenz ist bereits für diese Domain aktiviert."
#: src/Api/RestApiController.php:405 #: src/Api/RestApiController.php:460
msgid "Maximum number of activations reached." msgid "Maximum number of activations reached."
msgstr "Maximale Anzahl der Aktivierungen erreicht." msgstr "Maximale Anzahl der Aktivierungen erreicht."
#: src/Api/RestApiController.php:416 #: src/Api/RestApiController.php:471
msgid "Failed to activate license." msgid "Failed to activate license."
msgstr "Lizenz konnte nicht aktiviert werden." msgstr "Lizenz konnte nicht aktiviert werden."
#: src/Api/RestApiController.php:422 #: src/Api/RestApiController.php:477
msgid "License activated successfully." msgid "License activated successfully."
msgstr "Lizenz erfolgreich aktiviert." msgstr "Lizenz erfolgreich aktiviert."
@@ -1432,6 +1432,15 @@ msgstr "Diese Lizenz ist inaktiv."
msgid "This license is not valid for this domain." msgid "This license is not valid for this domain."
msgstr "Diese Lizenz ist für diese Domain nicht gültig." msgstr "Diese Lizenz ist für diese Domain nicht gültig."
#: src/Product/VersionManager.php:166
msgid "Attachment file not found."
msgstr "Anhangs-Datei nicht gefunden."
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
msgstr "Datei-Prüfsumme stimmt nicht überein. Erwartet: %1$s, Erhalten: %2$s"
#: src/Product/LicensedProductType.php:72 #: src/Product/LicensedProductType.php:72
msgid "Licensed Product" msgid "Licensed Product"
msgstr "Lizensiertes Produkt" msgstr "Lizensiertes Produkt"
@@ -1523,15 +1532,6 @@ msgstr "Leer lassen für übergeordneten Standard. 0 = Lebenslang."
msgid "Leave empty for parent default." msgid "Leave empty for parent default."
msgstr "Leer lassen für übergeordneten Standard." msgstr "Leer lassen für übergeordneten Standard."
#: src/Product/VersionManager.php:166
msgid "Attachment file not found."
msgstr "Anhangs-Datei nicht gefunden."
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
msgstr "Datei-Prüfsumme stimmt nicht überein. Erwartet: %1$s, Erhalten: %2$s"
#: src/Product/LicensedProductVariation.php:143 #: src/Product/LicensedProductVariation.php:143
msgid "Monthly" msgid "Monthly"
msgstr "Monatlich" msgstr "Monatlich"

View File

@@ -1,14 +1,14 @@
# SOME DESCRIPTIVE TITLE. # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the WC Licensed Product package. # This file is distributed under the same license as the wc-licensed-product package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WC Licensed Product 0.5.3\n" "Project-Id-Version: wc-licensed-product 0.5.5\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 16:08+0100\n" "POT-Creation-Date: 2026-01-26 17:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1238,32 +1238,32 @@ msgstr ""
msgid "License validation failed." msgid "License validation failed."
msgstr "" msgstr ""
#: src/Api/RestApiController.php:84 #: src/Api/RestApiController.php:106
msgid "Too many requests. Please try again later." msgid "Too many requests. Please try again later."
msgstr "" msgstr ""
#: src/Api/RestApiController.php:345 src/Api/RestApiController.php:378 #: src/Api/RestApiController.php:400 src/Api/RestApiController.php:433
#: src/License/LicenseManager.php:475 #: src/License/LicenseManager.php:475
msgid "License key not found." msgid "License key not found."
msgstr "" msgstr ""
#: src/Api/RestApiController.php:386 #: src/Api/RestApiController.php:441
msgid "This license is not valid." msgid "This license is not valid."
msgstr "" msgstr ""
#: src/Api/RestApiController.php:396 #: src/Api/RestApiController.php:451
msgid "License is already activated for this domain." msgid "License is already activated for this domain."
msgstr "" msgstr ""
#: src/Api/RestApiController.php:405 #: src/Api/RestApiController.php:460
msgid "Maximum number of activations reached." msgid "Maximum number of activations reached."
msgstr "" msgstr ""
#: src/Api/RestApiController.php:416 #: src/Api/RestApiController.php:471
msgid "Failed to activate license." msgid "Failed to activate license."
msgstr "" msgstr ""
#: src/Api/RestApiController.php:422 #: src/Api/RestApiController.php:477
msgid "License activated successfully." msgid "License activated successfully."
msgstr "" msgstr ""
@@ -1383,6 +1383,15 @@ msgstr ""
msgid "This license is not valid for this domain." msgid "This license is not valid for this domain."
msgstr "" msgstr ""
#: src/Product/VersionManager.php:166
msgid "Attachment file not found."
msgstr ""
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
msgstr ""
#: src/Product/LicensedProductType.php:72 #: src/Product/LicensedProductType.php:72
msgid "Licensed Product" msgid "Licensed Product"
msgstr "" msgstr ""
@@ -1472,15 +1481,6 @@ msgstr ""
msgid "Leave empty for parent default." msgid "Leave empty for parent default."
msgstr "" msgstr ""
#: src/Product/VersionManager.php:166
msgid "Attachment file not found."
msgstr ""
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
msgstr ""
#: src/Product/LicensedProductVariation.php:143 #: src/Product/LicensedProductVariation.php:143
msgid "Monthly" msgid "Monthly"
msgstr "" msgstr ""

Binary file not shown.

View File

@@ -0,0 +1 @@
bbd0fa8888c6990a4ba00ccfb8b2189ee6ac529a34cc11a5d8d8d28518b1f6dd wc-licensed-product-0.5.3.zip

View File

@@ -157,16 +157,23 @@ final class ResponseSigner
* to verify signed API responses. Each customer gets their own secret * to verify signed API responses. Each customer gets their own secret
* derived from their license key. * derived from their license key.
* *
* Uses RFC 5869 HKDF via PHP's native hash_hkdf() function.
* Parameters match the client library (SecureLicenseClient):
* - IKM (input keying material): server_secret
* - Length: 32 bytes (256 bits for SHA-256)
* - Info: license_key (context-specific info)
*
* @param string $licenseKey The customer's license key * @param string $licenseKey The customer's license key
* @param string $serverSecret The server's master secret * @param string $serverSecret The server's master secret
* @return string The derived secret (64 hex characters) * @return string The derived secret (64 hex characters)
*/ */
public static function deriveCustomerSecret(string $licenseKey, string $serverSecret): string public static function deriveCustomerSecret(string $licenseKey, string $serverSecret): string
{ {
// HKDF-like key derivation // RFC 5869 HKDF using PHP's native implementation
$prk = hash_hmac('sha256', $licenseKey, $serverSecret, true); // Must match client's ResponseSignature::deriveKey() exactly
$binaryKey = hash_hkdf('sha256', $serverSecret, 32, $licenseKey);
return hash_hmac('sha256', $prk . "\x01", $serverSecret); return bin2hex($binaryKey);
} }
/** /**

View File

@@ -22,14 +22,34 @@ final class RestApiController
private const NAMESPACE = 'wc-licensed-product/v1'; private const NAMESPACE = 'wc-licensed-product/v1';
/** /**
* Rate limit: requests per minute per IP * Default rate limit: requests per window per IP
*/ */
private const RATE_LIMIT_REQUESTS = 30; private const DEFAULT_RATE_LIMIT = 30;
/** /**
* Rate limit window in seconds * Default rate limit window in seconds
*/ */
private const RATE_LIMIT_WINDOW = 60; private const DEFAULT_RATE_WINDOW = 60;
/**
* Get the configured rate limit (requests per window)
*/
private function getRateLimit(): int
{
return defined('WC_LICENSE_RATE_LIMIT')
? (int) WC_LICENSE_RATE_LIMIT
: self::DEFAULT_RATE_LIMIT;
}
/**
* Get the configured rate limit window in seconds
*/
private function getRateWindow(): int
{
return defined('WC_LICENSE_RATE_WINDOW')
? (int) WC_LICENSE_RATE_WINDOW
: self::DEFAULT_RATE_WINDOW;
}
private LicenseManager $licenseManager; private LicenseManager $licenseManager;
@@ -56,12 +76,14 @@ final class RestApiController
{ {
$ip = $this->getClientIp(); $ip = $this->getClientIp();
$transientKey = 'wclp_rate_' . md5($ip); $transientKey = 'wclp_rate_' . md5($ip);
$rateLimit = $this->getRateLimit();
$rateWindow = $this->getRateWindow();
$data = get_transient($transientKey); $data = get_transient($transientKey);
if ($data === false) { if ($data === false) {
// First request, start counting // First request, start counting
set_transient($transientKey, ['count' => 1, 'start' => time()], self::RATE_LIMIT_WINDOW); set_transient($transientKey, ['count' => 1, 'start' => time()], $rateWindow);
return null; return null;
} }
@@ -69,15 +91,15 @@ final class RestApiController
$start = (int) ($data['start'] ?? time()); $start = (int) ($data['start'] ?? time());
// Check if window has expired // Check if window has expired
if (time() - $start >= self::RATE_LIMIT_WINDOW) { if (time() - $start >= $rateWindow) {
// Reset counter // Reset counter
set_transient($transientKey, ['count' => 1, 'start' => time()], self::RATE_LIMIT_WINDOW); set_transient($transientKey, ['count' => 1, 'start' => time()], $rateWindow);
return null; return null;
} }
// Check if limit exceeded // Check if limit exceeded
if ($count >= self::RATE_LIMIT_REQUESTS) { if ($count >= $rateLimit) {
$retryAfter = self::RATE_LIMIT_WINDOW - (time() - $start); $retryAfter = $rateWindow - (time() - $start);
$response = new WP_REST_Response([ $response = new WP_REST_Response([
'success' => false, 'success' => false,
'error' => 'rate_limit_exceeded', 'error' => 'rate_limit_exceeded',
@@ -89,7 +111,7 @@ final class RestApiController
} }
// Increment counter // Increment counter
set_transient($transientKey, ['count' => $count + 1, 'start' => $start], self::RATE_LIMIT_WINDOW); set_transient($transientKey, ['count' => $count + 1, 'start' => $start], $rateWindow);
return null; return null;
} }
@@ -257,7 +279,8 @@ final class RestApiController
'type' => 'string', 'type' => 'string',
'sanitize_callback' => 'sanitize_text_field', 'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => function ($value): bool { 'validate_callback' => function ($value): bool {
return !empty($value) && strlen($value) <= 64; $len = strlen($value);
return !empty($value) && $len >= 8 && $len <= 64;
}, },
], ],
'domain' => [ 'domain' => [
@@ -281,6 +304,10 @@ final class RestApiController
'required' => true, 'required' => true,
'type' => 'string', 'type' => 'string',
'sanitize_callback' => 'sanitize_text_field', 'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => function ($value): bool {
$len = strlen($value);
return !empty($value) && $len >= 8 && $len <= 64;
},
], ],
], ],
]); ]);
@@ -295,11 +322,18 @@ final class RestApiController
'required' => true, 'required' => true,
'type' => 'string', 'type' => 'string',
'sanitize_callback' => 'sanitize_text_field', 'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => function ($value): bool {
$len = strlen($value);
return !empty($value) && $len >= 8 && $len <= 64;
},
], ],
'domain' => [ 'domain' => [
'required' => true, 'required' => true,
'type' => 'string', 'type' => 'string',
'sanitize_callback' => 'sanitize_text_field', 'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => function ($value): bool {
return !empty($value) && strlen($value) <= 255;
},
], ],
], ],
]); ]);
@@ -320,11 +354,32 @@ final class RestApiController
$result = $this->licenseManager->validateLicense($licenseKey, $domain); $result = $this->licenseManager->validateLicense($licenseKey, $domain);
$statusCode = $result['valid'] ? 200 : 403; $statusCode = $this->getStatusCodeForResult($result);
return new WP_REST_Response($result, $statusCode); return new WP_REST_Response($result, $statusCode);
} }
/**
* Get HTTP status code based on validation result
*
* @param array $result The validation result
* @return int HTTP status code
*/
private function getStatusCodeForResult(array $result): int
{
if ($result['valid']) {
return 200;
}
$error = $result['error'] ?? '';
return match ($error) {
'license_not_found' => 404,
'activation_failed' => 500,
default => 403,
};
}
/** /**
* Check license status endpoint * Check license status endpoint
*/ */

View File

@@ -3,7 +3,7 @@
* Plugin Name: WooCommerce Licensed Product * Plugin Name: WooCommerce Licensed Product
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product * Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation. * Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
* Version: 0.5.3 * Version: 0.5.5
* Author: Marco Graetsch * Author: Marco Graetsch
* Author URI: https://src.bundespruefstelle.ch/magdev * Author URI: https://src.bundespruefstelle.ch/magdev
* License: GPL-2.0-or-later * License: GPL-2.0-or-later
@@ -28,7 +28,7 @@ if (!defined('ABSPATH')) {
} }
// Plugin constants // Plugin constants
define('WC_LICENSED_PRODUCT_VERSION', '0.5.3'); define('WC_LICENSED_PRODUCT_VERSION', '0.5.5');
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__); define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__)); define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));