Implement version 0.0.9 features

- Add API client examples for PHP, Python, JavaScript, curl, and C#
- Create comprehensive REST API documentation in docs/client-examples/
- All examples include rate limit handling (HTTP 429)
- Examples cover validate, status, and activate endpoints

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 21:23:21 +01:00
parent 6378063180
commit d3830e24b9
8 changed files with 1178 additions and 9 deletions

View File

@@ -34,13 +34,7 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
### Known Bugs
_No known bugs at this time._
### Version 0.0.9
**Note for AI Assistants:** Cleanup the versions in this section after implementation, but keep this notice!
_No known bugs at this time.
## Technical Stack
@@ -462,3 +456,27 @@ Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
- Admins can customize subject, heading, additional content, and enable/disable via WC settings
- Expiration warning schedule (days before) remains in plugin settings
- Email enable/disable is controlled through WooCommerce email settings
### 2026-01-21 - Version 0.0.9 Features
**Implemented:**
- Created API client examples for multiple programming languages
- Documentation for REST API usage with code examples
**New files:**
- `docs/client-examples/README.md` - API documentation and overview
- `docs/client-examples/curl.sh` - cURL command examples
- `docs/client-examples/php-client.php` - PHP client class with examples
- `docs/client-examples/python-client.py` - Python client class with examples
- `docs/client-examples/javascript-client.js` - JavaScript/Node.js client class
- `docs/client-examples/csharp-client.cs` - C# client class with examples
**Technical notes:**
- All client examples include error handling for rate limiting (HTTP 429)
- Examples demonstrate all three API endpoints: validate, status, activate
- JavaScript client works in both browser and Node.js environments
- Python client uses dataclasses for type-safe responses
- C# client uses async/await patterns and System.Text.Json

View File

@@ -0,0 +1,69 @@
# WC Licensed Product API Client Examples
This directory contains example API clients for integrating with the WC Licensed Product REST API.
## API Base URL
```shell
https://your-site.com/wp-json/wc-licensed-product/v1
```
## Available Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/validate` | POST | Validate a license key for a specific domain |
| `/status` | POST | Get the current status of a license |
| `/activate` | POST | Activate a license on a domain |
## Rate Limiting
The API is rate-limited to 30 requests per minute per IP address. If you exceed this limit, you'll receive a `429 Too Many Requests` response with a `Retry-After` header indicating when you can retry.
## Example Files
- [curl.sh](curl.sh) - cURL command examples
- [php-client.php](php-client.php) - PHP client class
- [python-client.py](python-client.py) - Python client class
- [javascript-client.js](javascript-client.js) - JavaScript/Node.js client class
- [csharp-client.cs](csharp-client.cs) - C# client class
## Response Format
All endpoints return JSON responses with the following structure:
### Success Response (Validate)
```json
{
"valid": true,
"license": {
"status": "active",
"domain": "example.com",
"expires_at": "2025-12-31",
"product_id": 123
}
}
```
### Error Response
```json
{
"valid": false,
"error": "license_not_found",
"message": "License key not found."
}
```
## Error Codes
| Code | Description |
| ------ | ------------- |
| `license_not_found` | The license key does not exist |
| `license_expired` | The license has expired |
| `license_revoked` | The license has been revoked |
| `license_inactive` | The license is inactive |
| `domain_mismatch` | The license is not valid for the provided domain |
| `max_activations_reached` | Maximum number of domain activations reached |
| `rate_limit_exceeded` | Too many requests, please wait and retry |

View File

@@ -0,0 +1,352 @@
/**
* WC Licensed Product API Client for C#
*
* A C# client for interacting with the WC Licensed Product REST API.
*
* Requirements:
* - .NET 6.0+ or .NET Framework 4.7.2+
* - System.Net.Http
* - System.Text.Json (or Newtonsoft.Json)
*
* Usage:
* var client = new WcLicensedProductClient("https://your-site.com");
* var result = await client.ValidateAsync("XXXX-XXXX-XXXX-XXXX", "example.com");
*/
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace WcLicensedProduct
{
/// <summary>
/// License status response data
/// </summary>
public class LicenseStatus
{
[JsonPropertyName("valid")]
public bool Valid { get; set; }
[JsonPropertyName("status")]
public string Status { get; set; }
[JsonPropertyName("domain")]
public string Domain { get; set; }
[JsonPropertyName("expires_at")]
public string ExpiresAt { get; set; }
[JsonPropertyName("activations_count")]
public int ActivationsCount { get; set; }
[JsonPropertyName("max_activations")]
public int MaxActivations { get; set; }
}
/// <summary>
/// Validation response data
/// </summary>
public class ValidationResult
{
[JsonPropertyName("valid")]
public bool Valid { get; set; }
[JsonPropertyName("error")]
public string Error { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonPropertyName("license")]
public LicenseInfo License { get; set; }
}
/// <summary>
/// License information in validation response
/// </summary>
public class LicenseInfo
{
[JsonPropertyName("status")]
public string Status { get; set; }
[JsonPropertyName("domain")]
public string Domain { get; set; }
[JsonPropertyName("expires_at")]
public string ExpiresAt { get; set; }
[JsonPropertyName("product_id")]
public int ProductId { get; set; }
}
/// <summary>
/// Activation response data
/// </summary>
public class ActivationResult
{
[JsonPropertyName("success")]
public bool Success { get; set; }
[JsonPropertyName("error")]
public string Error { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; }
}
/// <summary>
/// Exception for API errors
/// </summary>
public class WcLicensedProductException : Exception
{
public string ErrorCode { get; }
public int HttpCode { get; }
public WcLicensedProductException(string message, string errorCode = null, int httpCode = 0)
: base(message)
{
ErrorCode = errorCode;
HttpCode = httpCode;
}
}
/// <summary>
/// Exception for rate limit errors
/// </summary>
public class RateLimitException : WcLicensedProductException
{
public int RetryAfter { get; }
public RateLimitException(int retryAfter)
: base($"Rate limit exceeded. Retry after {retryAfter} seconds.")
{
RetryAfter = retryAfter;
}
}
/// <summary>
/// Client for the WC Licensed Product REST API
/// </summary>
public class WcLicensedProductClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
private readonly JsonSerializerOptions _jsonOptions;
/// <summary>
/// Initialize the client
/// </summary>
/// <param name="siteUrl">Your WordPress site URL (e.g., "https://example.com")</param>
/// <param name="timeout">Request timeout (default: 30 seconds)</param>
public WcLicensedProductClient(string siteUrl, TimeSpan? timeout = null)
{
_baseUrl = siteUrl.TrimEnd('/') + "/wp-json/wc-licensed-product/v1";
_httpClient = new HttpClient
{
Timeout = timeout ?? TimeSpan.FromSeconds(30)
};
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
_jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
}
/// <summary>
/// Validate a license key for a specific domain
/// </summary>
/// <param name="licenseKey">The license key to validate</param>
/// <param name="domain">The domain to validate against</param>
/// <returns>Validation result</returns>
public async Task<ValidationResult> ValidateAsync(string licenseKey, string domain)
{
var data = new { license_key = licenseKey, domain = domain };
return await RequestAsync<ValidationResult>("/validate", data);
}
/// <summary>
/// Get the status of a license
/// </summary>
/// <param name="licenseKey">The license key to check</param>
/// <returns>License status</returns>
public async Task<LicenseStatus> StatusAsync(string licenseKey)
{
var data = new { license_key = licenseKey };
return await RequestAsync<LicenseStatus>("/status", data);
}
/// <summary>
/// Activate a license on a domain
/// </summary>
/// <param name="licenseKey">The license key to activate</param>
/// <param name="domain">The domain to activate on</param>
/// <returns>Activation result</returns>
public async Task<ActivationResult> ActivateAsync(string licenseKey, string domain)
{
var data = new { license_key = licenseKey, domain = domain };
return await RequestAsync<ActivationResult>("/activate", data);
}
/// <summary>
/// Make an API request
/// </summary>
private async Task<T> RequestAsync<T>(string endpoint, object data)
{
var url = _baseUrl + endpoint;
var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response;
try
{
response = await _httpClient.PostAsync(url, content);
}
catch (TaskCanceledException)
{
throw new WcLicensedProductException("Request timeout");
}
catch (HttpRequestException ex)
{
throw new WcLicensedProductException($"Request failed: {ex.Message}");
}
// Handle rate limiting
if ((int)response.StatusCode == 429)
{
var retryAfter = 60;
if (response.Headers.TryGetValues("Retry-After", out var values))
{
int.TryParse(string.Join("", values), out retryAfter);
}
throw new RateLimitException(retryAfter);
}
var responseBody = await response.Content.ReadAsStringAsync();
T result;
try
{
result = JsonSerializer.Deserialize<T>(responseBody, _jsonOptions);
}
catch (JsonException)
{
throw new WcLicensedProductException("Invalid JSON response");
}
// Check for API errors (for error responses)
if (!response.IsSuccessStatusCode)
{
var errorResult = JsonSerializer.Deserialize<ValidationResult>(responseBody, _jsonOptions);
if (errorResult?.Error != null)
{
throw new WcLicensedProductException(
errorResult.Message ?? "Unknown error",
errorResult.Error,
(int)response.StatusCode
);
}
}
return result;
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
// =========================================================================
// Usage Examples
// =========================================================================
public class Program
{
public static async Task Main(string[] args)
{
using var client = new WcLicensedProductClient("https://your-wordpress-site.com");
// Example 1: Validate a license
Console.WriteLine("=== Validate License ===");
try
{
var result = await client.ValidateAsync("XXXX-XXXX-XXXX-XXXX", "myapp.example.com");
if (result.Valid)
{
Console.WriteLine("License is valid!");
Console.WriteLine($"Status: {result.License?.Status ?? "active"}");
Console.WriteLine($"Expires: {result.License?.ExpiresAt ?? "Never"}");
}
else
{
Console.WriteLine($"License is not valid: {result.Message ?? "Unknown error"}");
}
}
catch (RateLimitException ex)
{
Console.WriteLine($"Rate limited. Retry after {ex.RetryAfter} seconds.");
}
catch (WcLicensedProductException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine();
// Example 2: Check license status
Console.WriteLine("=== Check License Status ===");
try
{
var status = await client.StatusAsync("XXXX-XXXX-XXXX-XXXX");
Console.WriteLine($"Valid: {(status.Valid ? "Yes" : "No")}");
Console.WriteLine($"Status: {status.Status ?? "unknown"}");
Console.WriteLine($"Domain: {status.Domain ?? "None"}");
Console.WriteLine($"Expires: {status.ExpiresAt ?? "Never"}");
Console.WriteLine($"Activations: {status.ActivationsCount}/{status.MaxActivations}");
}
catch (RateLimitException ex)
{
Console.WriteLine($"Rate limited. Retry after {ex.RetryAfter} seconds.");
}
catch (WcLicensedProductException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine();
// Example 3: Activate license on a domain
Console.WriteLine("=== Activate License ===");
try
{
var result = await client.ActivateAsync("XXXX-XXXX-XXXX-XXXX", "newsite.example.com");
if (result.Success)
{
Console.WriteLine("License activated successfully!");
}
else
{
Console.WriteLine($"Activation failed: {result.Message ?? "Unknown error"}");
}
}
catch (RateLimitException ex)
{
Console.WriteLine($"Rate limited. Retry after {ex.RetryAfter} seconds.");
}
catch (WcLicensedProductException ex)
{
Console.WriteLine($"Error ({ex.ErrorCode}): {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,89 @@
#!/bin/bash
# WC Licensed Product API - cURL Examples
#
# Replace YOUR_SITE_URL with your WordPress site URL
# Replace YOUR_LICENSE_KEY with your actual license key
# Replace YOUR_DOMAIN with the domain to validate/activate
BASE_URL="https://YOUR_SITE_URL/wp-json/wc-licensed-product/v1"
LICENSE_KEY="XXXX-XXXX-XXXX-XXXX"
DOMAIN="example.com"
# ------------------------------------------------------------------------------
# Validate License
# ------------------------------------------------------------------------------
# Validates if a license key is valid for a specific domain
# Returns: valid (bool), license details, or error message
echo "=== Validate License ==="
curl -X POST "${BASE_URL}/validate" \
-H "Content-Type: application/json" \
-d "{
\"license_key\": \"${LICENSE_KEY}\",
\"domain\": \"${DOMAIN}\"
}"
echo -e "\n"
# ------------------------------------------------------------------------------
# Check License Status
# ------------------------------------------------------------------------------
# Gets the current status of a license without domain validation
# Returns: status, domain, expiration date, activation count
echo "=== Check License Status ==="
curl -X POST "${BASE_URL}/status" \
-H "Content-Type: application/json" \
-d "{
\"license_key\": \"${LICENSE_KEY}\"
}"
echo -e "\n"
# ------------------------------------------------------------------------------
# Activate License
# ------------------------------------------------------------------------------
# Activates a license on a specific domain
# Returns: success (bool), message
echo "=== Activate License ==="
curl -X POST "${BASE_URL}/activate" \
-H "Content-Type: application/json" \
-d "{
\"license_key\": \"${LICENSE_KEY}\",
\"domain\": \"${DOMAIN}\"
}"
echo -e "\n"
# ------------------------------------------------------------------------------
# Example with verbose output for debugging
# ------------------------------------------------------------------------------
echo "=== Verbose Request (for debugging) ==="
curl -v -X POST "${BASE_URL}/validate" \
-H "Content-Type: application/json" \
-d "{
\"license_key\": \"${LICENSE_KEY}\",
\"domain\": \"${DOMAIN}\"
}" 2>&1
# ------------------------------------------------------------------------------
# Example handling rate limit (429 response)
# ------------------------------------------------------------------------------
# The API returns a Retry-After header when rate limited
# You can use this header to determine when to retry
echo "=== Handling Rate Limits ==="
response=$(curl -s -w "\n%{http_code}" -X POST "${BASE_URL}/validate" \
-H "Content-Type: application/json" \
-d "{
\"license_key\": \"${LICENSE_KEY}\",
\"domain\": \"${DOMAIN}\"
}")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" == "429" ]; then
echo "Rate limited. Please wait before retrying."
echo "Response: $body"
else
echo "HTTP $http_code: $body"
fi

View File

@@ -0,0 +1,220 @@
/**
* WC Licensed Product API Client for JavaScript
*
* A JavaScript client for interacting with the WC Licensed Product REST API.
* Works in both browser and Node.js environments.
*
* Browser usage:
* <script src="javascript-client.js"></script>
* const client = new WcLicensedProductClient('https://your-site.com');
* const result = await client.validate('XXXX-XXXX-XXXX-XXXX', 'example.com');
*
* Node.js usage:
* const WcLicensedProductClient = require('./javascript-client');
* const client = new WcLicensedProductClient('https://your-site.com');
*/
class WcLicensedProductError extends Error {
constructor(message, errorCode = null, httpCode = null) {
super(message);
this.name = 'WcLicensedProductError';
this.errorCode = errorCode;
this.httpCode = httpCode;
}
}
class RateLimitError extends WcLicensedProductError {
constructor(retryAfter) {
super(`Rate limit exceeded. Retry after ${retryAfter} seconds.`);
this.name = 'RateLimitError';
this.retryAfter = retryAfter;
}
}
class WcLicensedProductClient {
/**
* Initialize the client
* @param {string} siteUrl - Your WordPress site URL (e.g., 'https://example.com')
* @param {Object} options - Optional configuration
* @param {number} options.timeout - Request timeout in milliseconds (default: 30000)
*/
constructor(siteUrl, options = {}) {
this.baseUrl = siteUrl.replace(/\/$/, '') + '/wp-json/wc-licensed-product/v1';
this.timeout = options.timeout || 30000;
}
/**
* Validate a license key for a specific domain
* @param {string} licenseKey - The license key to validate
* @param {string} domain - The domain to validate against
* @returns {Promise<Object>} Response data with 'valid' boolean and license info
*/
async validate(licenseKey, domain) {
return this._request('/validate', {
license_key: licenseKey,
domain: domain,
});
}
/**
* Get the status of a license
* @param {string} licenseKey - The license key to check
* @returns {Promise<Object>} License status data
*/
async status(licenseKey) {
return this._request('/status', {
license_key: licenseKey,
});
}
/**
* Activate a license on a domain
* @param {string} licenseKey - The license key to activate
* @param {string} domain - The domain to activate on
* @returns {Promise<Object>} Response with 'success' boolean and message
*/
async activate(licenseKey, domain) {
return this._request('/activate', {
license_key: licenseKey,
domain: domain,
});
}
/**
* Make an API request
* @private
* @param {string} endpoint - API endpoint path
* @param {Object} data - Request data
* @returns {Promise<Object>} Decoded response data
*/
async _request(endpoint, data) {
const url = this.baseUrl + endpoint;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(data),
signal: controller.signal,
});
clearTimeout(timeoutId);
// Handle rate limiting
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
throw new RateLimitError(retryAfter);
}
const result = await response.json();
// Check for API errors
if (response.status >= 400 && result.error) {
throw new WcLicensedProductError(
result.message || 'Unknown error',
result.error,
response.status
);
}
return result;
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof WcLicensedProductError) {
throw error;
}
if (error.name === 'AbortError') {
throw new WcLicensedProductError('Request timeout');
}
throw new WcLicensedProductError(`Request failed: ${error.message}`);
}
}
}
// =============================================================================
// Usage Examples
// =============================================================================
// Example usage (uncomment to run):
/*
(async () => {
const client = new WcLicensedProductClient('https://your-wordpress-site.com');
// Example 1: Validate a license
console.log('=== Validate License ===');
try {
const result = await client.validate('XXXX-XXXX-XXXX-XXXX', 'myapp.example.com');
if (result.valid) {
console.log('License is valid!');
console.log('Status:', result.license?.status || 'active');
console.log('Expires:', result.license?.expires_at || 'Never');
} else {
console.log('License is not valid:', result.message || 'Unknown error');
}
} catch (error) {
if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter} seconds.`);
} else {
console.log('Error:', error.message);
}
}
console.log();
// Example 2: Check license status
console.log('=== Check License Status ===');
try {
const status = await client.status('XXXX-XXXX-XXXX-XXXX');
console.log('Valid:', status.valid ? 'Yes' : 'No');
console.log('Status:', status.status || 'unknown');
console.log('Domain:', status.domain || 'None');
console.log('Expires:', status.expires_at || 'Never');
console.log('Activations:', `${status.activations_count || 0}/${status.max_activations || 1}`);
} catch (error) {
if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter} seconds.`);
} else {
console.log('Error:', error.message);
}
}
console.log();
// Example 3: Activate license on a domain
console.log('=== Activate License ===');
try {
const result = await client.activate('XXXX-XXXX-XXXX-XXXX', 'newsite.example.com');
if (result.success) {
console.log('License activated successfully!');
} else {
console.log('Activation failed:', result.message || 'Unknown error');
}
} catch (error) {
if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter} seconds.`);
} else {
console.log(`Error (${error.errorCode}):`, error.message);
}
}
})();
*/
// Export for Node.js
if (typeof module !== 'undefined' && module.exports) {
module.exports = WcLicensedProductClient;
module.exports.WcLicensedProductError = WcLicensedProductError;
module.exports.RateLimitError = RateLimitError;
}

View File

@@ -0,0 +1,188 @@
<?php
/**
* WC Licensed Product API Client for PHP
*
* A simple PHP client for interacting with the WC Licensed Product REST API.
*
* Requirements:
* - PHP 7.4+
* - cURL extension
*
* Usage:
* $client = new WcLicensedProductClient('https://your-site.com');
* $result = $client->validate('XXXX-XXXX-XXXX-XXXX', 'example.com');
*/
class WcLicensedProductClient
{
private string $baseUrl;
private int $timeout;
/**
* Constructor
*
* @param string $siteUrl Your WordPress site URL (e.g., 'https://example.com')
* @param int $timeout Request timeout in seconds
*/
public function __construct(string $siteUrl, int $timeout = 30)
{
$this->baseUrl = rtrim($siteUrl, '/') . '/wp-json/wc-licensed-product/v1';
$this->timeout = $timeout;
}
/**
* Validate a license key for a specific domain
*
* @param string $licenseKey The license key to validate
* @param string $domain The domain to validate against
* @return array Response data
* @throws Exception On request failure
*/
public function validate(string $licenseKey, string $domain): array
{
return $this->request('/validate', [
'license_key' => $licenseKey,
'domain' => $domain,
]);
}
/**
* Get the status of a license
*
* @param string $licenseKey The license key to check
* @return array Response data with status, domain, expiration, etc.
* @throws Exception On request failure
*/
public function status(string $licenseKey): array
{
return $this->request('/status', [
'license_key' => $licenseKey,
]);
}
/**
* Activate a license on a domain
*
* @param string $licenseKey The license key to activate
* @param string $domain The domain to activate on
* @return array Response data
* @throws Exception On request failure
*/
public function activate(string $licenseKey, string $domain): array
{
return $this->request('/activate', [
'license_key' => $licenseKey,
'domain' => $domain,
]);
}
/**
* Make an API request
*
* @param string $endpoint API endpoint path
* @param array $data Request data
* @return array Decoded response data
* @throws Exception On request failure
*/
private function request(string $endpoint, array $data): array
{
$url = $this->baseUrl . $endpoint;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($response === false) {
throw new Exception('cURL error: ' . $error);
}
$decoded = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('Invalid JSON response: ' . json_last_error_msg());
}
// Add HTTP code to response for easier handling
$decoded['_http_code'] = $httpCode;
return $decoded;
}
}
// =============================================================================
// Usage Examples
// =============================================================================
// Uncomment to run examples:
/*
$client = new WcLicensedProductClient('https://your-wordpress-site.com');
// Example 1: Validate a license
try {
$result = $client->validate('XXXX-XXXX-XXXX-XXXX', 'myapp.example.com');
if ($result['valid']) {
echo "License is valid!\n";
echo "Status: " . ($result['license']['status'] ?? 'active') . "\n";
echo "Expires: " . ($result['license']['expires_at'] ?? 'Never') . "\n";
} else {
echo "License is not valid: " . ($result['message'] ?? 'Unknown error') . "\n";
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
// Example 2: Check license status
try {
$status = $client->status('XXXX-XXXX-XXXX-XXXX');
echo "License Status:\n";
echo " Valid: " . ($status['valid'] ? 'Yes' : 'No') . "\n";
echo " Status: " . ($status['status'] ?? 'unknown') . "\n";
echo " Domain: " . ($status['domain'] ?? 'none') . "\n";
echo " Expires: " . ($status['expires_at'] ?? 'Never') . "\n";
echo " Activations: " . ($status['activations_count'] ?? 0) . "/" . ($status['max_activations'] ?? 1) . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
// Example 3: Activate license on a domain
try {
$result = $client->activate('XXXX-XXXX-XXXX-XXXX', 'newsite.example.com');
if ($result['success'] ?? false) {
echo "License activated successfully!\n";
} else {
echo "Activation failed: " . ($result['message'] ?? 'Unknown error') . "\n";
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
// Example 4: Handling rate limits
try {
$result = $client->validate('XXXX-XXXX-XXXX-XXXX', 'example.com');
if (($result['_http_code'] ?? 200) === 429) {
$retryAfter = $result['retry_after'] ?? 60;
echo "Rate limited. Retry after {$retryAfter} seconds.\n";
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
*/

View File

@@ -0,0 +1,233 @@
"""
WC Licensed Product API Client for Python
A simple Python client for interacting with the WC Licensed Product REST API.
Requirements:
- Python 3.7+
- requests library (pip install requests)
Usage:
client = WcLicensedProductClient('https://your-site.com')
result = client.validate('XXXX-XXXX-XXXX-XXXX', 'example.com')
"""
import requests
from typing import Dict, Any, Optional
from dataclasses import dataclass
@dataclass
class LicenseStatus:
"""License status response data."""
valid: bool
status: str
domain: Optional[str]
expires_at: Optional[str]
activations_count: int
max_activations: int
class WcLicensedProductError(Exception):
"""Base exception for API errors."""
def __init__(self, message: str, error_code: str = None, http_code: int = None):
super().__init__(message)
self.error_code = error_code
self.http_code = http_code
class RateLimitError(WcLicensedProductError):
"""Raised when rate limit is exceeded."""
def __init__(self, retry_after: int):
super().__init__(f"Rate limit exceeded. Retry after {retry_after} seconds.")
self.retry_after = retry_after
class WcLicensedProductClient:
"""Client for the WC Licensed Product REST API."""
def __init__(self, site_url: str, timeout: int = 30):
"""
Initialize the client.
Args:
site_url: Your WordPress site URL (e.g., 'https://example.com')
timeout: Request timeout in seconds
"""
self.base_url = site_url.rstrip('/') + '/wp-json/wc-licensed-product/v1'
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({
'Content-Type': 'application/json',
'Accept': 'application/json',
})
def validate(self, license_key: str, domain: str) -> Dict[str, Any]:
"""
Validate a license key for a specific domain.
Args:
license_key: The license key to validate
domain: The domain to validate against
Returns:
Dict with 'valid' boolean and additional license info
Raises:
WcLicensedProductError: On API error
RateLimitError: When rate limited
"""
return self._request('/validate', {
'license_key': license_key,
'domain': domain,
})
def status(self, license_key: str) -> LicenseStatus:
"""
Get the status of a license.
Args:
license_key: The license key to check
Returns:
LicenseStatus object with license details
Raises:
WcLicensedProductError: On API error
RateLimitError: When rate limited
"""
data = self._request('/status', {
'license_key': license_key,
})
return LicenseStatus(
valid=data.get('valid', False),
status=data.get('status', 'unknown'),
domain=data.get('domain'),
expires_at=data.get('expires_at'),
activations_count=data.get('activations_count', 0),
max_activations=data.get('max_activations', 1),
)
def activate(self, license_key: str, domain: str) -> Dict[str, Any]:
"""
Activate a license on a domain.
Args:
license_key: The license key to activate
domain: The domain to activate on
Returns:
Dict with 'success' boolean and message
Raises:
WcLicensedProductError: On API error
RateLimitError: When rate limited
"""
return self._request('/activate', {
'license_key': license_key,
'domain': domain,
})
def _request(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Make an API request.
Args:
endpoint: API endpoint path
data: Request data
Returns:
Decoded response data
Raises:
WcLicensedProductError: On API error
RateLimitError: When rate limited
"""
url = self.base_url + endpoint
try:
response = self.session.post(url, json=data, timeout=self.timeout)
except requests.RequestException as e:
raise WcLicensedProductError(f"Request failed: {e}")
# Handle rate limiting
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
raise RateLimitError(retry_after)
try:
result = response.json()
except ValueError:
raise WcLicensedProductError("Invalid JSON response")
# Check for API errors
if response.status_code >= 400 and 'error' in result:
raise WcLicensedProductError(
result.get('message', 'Unknown error'),
error_code=result.get('error'),
http_code=response.status_code,
)
return result
# =============================================================================
# Usage Examples
# =============================================================================
if __name__ == '__main__':
# Initialize the client
client = WcLicensedProductClient('https://your-wordpress-site.com')
# Example 1: Validate a license
print("=== Validate License ===")
try:
result = client.validate('XXXX-XXXX-XXXX-XXXX', 'myapp.example.com')
if result.get('valid'):
print("License is valid!")
print(f"Status: {result.get('license', {}).get('status', 'active')}")
print(f"Expires: {result.get('license', {}).get('expires_at', 'Never')}")
else:
print(f"License is not valid: {result.get('message', 'Unknown error')}")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds.")
except WcLicensedProductError as e:
print(f"Error: {e}")
print()
# Example 2: Check license status
print("=== Check License Status ===")
try:
status = client.status('XXXX-XXXX-XXXX-XXXX')
print(f"Valid: {'Yes' if status.valid else 'No'}")
print(f"Status: {status.status}")
print(f"Domain: {status.domain or 'None'}")
print(f"Expires: {status.expires_at or 'Never'}")
print(f"Activations: {status.activations_count}/{status.max_activations}")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds.")
except WcLicensedProductError as e:
print(f"Error: {e}")
print()
# Example 3: Activate license on a domain
print("=== Activate License ===")
try:
result = client.activate('XXXX-XXXX-XXXX-XXXX', 'newsite.example.com')
if result.get('success'):
print("License activated successfully!")
else:
print(f"Activation failed: {result.get('message', 'Unknown error')}")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds.")
except WcLicensedProductError as e:
print(f"Error ({e.error_code}): {e}")

View File

@@ -3,7 +3,7 @@
* Plugin Name: WooCommerce 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.
* Version: 0.0.8
* Version: 0.0.9
* Author: Marco Graetsch
* Author URI: https://src.bundespruefstelle.ch/magdev
* License: GPL-2.0-or-later
@@ -28,7 +28,7 @@ if (!defined('ABSPATH')) {
}
// Plugin constants
define('WC_LICENSED_PRODUCT_VERSION', '0.0.8');
define('WC_LICENSED_PRODUCT_VERSION', '0.0.9');
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));