You've already forked wc-licensed-product
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:
32
CLAUDE.md
32
CLAUDE.md
@@ -34,13 +34,7 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
|
|||||||
|
|
||||||
### Known Bugs
|
### Known Bugs
|
||||||
|
|
||||||
_No known bugs at this time._
|
_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!
|
|
||||||
|
|
||||||
## Technical Stack
|
## 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
|
- Admins can customize subject, heading, additional content, and enable/disable via WC settings
|
||||||
- Expiration warning schedule (days before) remains in plugin settings
|
- Expiration warning schedule (days before) remains in plugin settings
|
||||||
- Email enable/disable is controlled through WooCommerce email 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
|
||||||
|
|||||||
69
docs/client-examples/README.md
Normal file
69
docs/client-examples/README.md
Normal 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 |
|
||||||
352
docs/client-examples/csharp-client.cs
Normal file
352
docs/client-examples/csharp-client.cs
Normal 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
docs/client-examples/curl.sh
Normal file
89
docs/client-examples/curl.sh
Normal 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
|
||||||
220
docs/client-examples/javascript-client.js
Normal file
220
docs/client-examples/javascript-client.js
Normal 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;
|
||||||
|
}
|
||||||
188
docs/client-examples/php-client.php
Normal file
188
docs/client-examples/php-client.php
Normal 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";
|
||||||
|
}
|
||||||
|
*/
|
||||||
233
docs/client-examples/python-client.py
Normal file
233
docs/client-examples/python-client.py
Normal 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}")
|
||||||
@@ -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.0.8
|
* Version: 0.0.9
|
||||||
* 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.0.8');
|
define('WC_LICENSED_PRODUCT_VERSION', '0.0.9');
|
||||||
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__));
|
||||||
|
|||||||
Reference in New Issue
Block a user