commit 1fdedd16ad4d977fc6bb3382abd68bc24a248447 Author: magdev Date: Thu Jan 22 15:37:20 2026 +0100 Initialize composer project for WooCommerce Licensed Product Client - Set up composer.json with package metadata and PSR-4 autoloading - Add symfony/http-client ^7.0 as HTTP client dependency - Create project structure (src/, tests/, tmp/) - Add README.md with project overview - Add CHANGELOG.md to track version history - Add .gitignore for vendor and cache files - Include OpenAPI specification in tmp/openapi.json Co-Authored-By: Claude Opus 4.5 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dd3fa9c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +indent_size = 2 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35985cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/vendor/ +.phpunit.result.cache +.php-cs-fixer.cache +*.log +.DS_Store +.idea/ +.vscode/ +*.swp +*.swo diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3025632 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.0.1] - 2026-01-22 + +### Added + +- Initial composer project setup +- Package configuration with PSR-4 autoloading +- Symfony HttpClient dependency (^7.0) +- Project documentation (README.md, CHANGELOG.md) +- OpenAPI specification reference in tmp/openapi.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..41494a5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,91 @@ +# License-Client for WooCommerce Licensed Product Plugin + +**Author:** Marco Graetsch +**Author URL:** +**Author Email:** +**Repository URL:** +**Issues URL:** +**Package-Name:** `magdev/wc-licensed-product-client` + +## Project Overview + +This composer package implements a Client for the WooCommerce Licensed Product Plugin. It uses the REST API as described in `tmp/openapi.json` to activate, validate and check the status of licenses. + +## Features + +- Easy integration in licensed software packages +- Defines a PHP constant if a licensed is valid or not +- Obfuscate the security critical code parts using plain PHP tools as best as possible + +### Key Fact: 100% AI-Generated + +This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase was created through AI assistance. + +## Temporary Roadmap + +**Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file. + +### Known Bugs + +No known bugs at the moment + +### Version 0.0.1 + +- Initialize composer project of type `library` with the known details +- Install dependencies + +## Technical Stack + +- **Language:** PHP 8.3.x +- **Coding-Style:** Symfony +- **HTTP-Client-Library:** symfony/http-client +- **Dependency Management:** Composer +- **OpenAPI Description:** `tmp/openapi.json` + +--- + +**For AI Assistants:** + +When starting a new session on this project: + +1. Read this CLAUDE.md file first +2. Semantic versioning follows the `MAJOR.MINOR.BUGFIX` pattern +3. Check git log for recent changes +4. Verify you're on the `dev` branch before making changes +5. Run `composer install` if vendor/ is missing +6. Test changes before committing +7. Follow commit message format with Claude Code attribution +8. Update this session history section with learnings +9. Always update the `README.md` on related changes +10. Keep changes in a single `CHANGELOG.md` +11. Follow markdown linting rules (see below) + +Always refer to this document when starting work on this project. + +### Markdown Linting Rules + +When editing CLAUDE.md or other markdown files, follow these rules to avoid linting errors: + +1. **MD031 - Blank lines around fenced code blocks**: Always add a blank line before and after fenced code blocks, even when they follow list items. Example of correct format: + + - **Item label**: + + (blank line here) + \`\`\`php + code example + \`\`\` + (blank line here) + +2. **MD056 - Table column count**: Table separators must have matching column counts and proper spacing. Use consistent dash lengths that match column header widths. + +3. **MD009 - No trailing spaces**: Remove trailing whitespace from lines + +4. **MD012 - No multiple consecutive blank lines**: Use only single blank lines between sections + +5. **MD040 - Fenced code blocks should have a language specified**: Always add a language identifier to code blocks (e.g., `txt`, `bash`, `php`). For shortcode examples, use `txt`. + +6. **MD032 - Lists should be surrounded by blank lines**: Add a blank line before AND after list blocks, including after bold labels like `**Attributes:**`. + +7. **MD034 - Bare URLs**: Wrap URLs in angle brackets (e.g., ``) or use markdown link syntax `[text](url)`. + +8. **Author section formatting**: Use a heading (`### Name`) instead of bold (`**Name**`) for the author name to maintain consistent document structure. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3639a9b --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# WooCommerce Licensed Product Client + +A PHP client library for the WooCommerce Licensed Product Plugin REST API. Activate, validate, and check the status of software licenses. + +## Requirements + +- PHP 8.3 or higher +- Composer + +## Installation + +```bash +composer require magdev/wc-licensed-product-client +``` + +## Features + +- Easy integration in licensed software packages +- License validation against domains +- License activation on domains +- License status checking +- Built on Symfony HttpClient + +## API Endpoints + +This client interacts with the following WooCommerce Licensed Product API endpoints: + +- **POST /validate** - Validate a license key for a specific domain +- **POST /status** - Get detailed license status information +- **POST /activate** - Activate a license on a domain + +## Usage + +Coming soon in future versions. + +## License + +GPL-2.0-or-later + +## Author + +Marco Graetsch + +- Website: +- Email: + +## Contributing + +Issues and pull requests are welcome at diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6435f88 --- /dev/null +++ b/composer.json @@ -0,0 +1,36 @@ +{ + "name": "magdev/wc-licensed-product-client", + "description": "Client library for WooCommerce Licensed Product Plugin - Activate, validate and check the status of licenses via REST API", + "type": "library", + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "Marco Graetsch", + "email": "magdev3.0@gmail.com", + "homepage": "https://src.bundespruefstelle.ch/magdev" + } + ], + "homepage": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client", + "support": { + "issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues", + "source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client" + }, + "require": { + "php": "^8.3", + "symfony/http-client": "^7.0" + }, + "autoload": { + "psr-4": { + "Magdev\\WcLicensedProductClient\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Magdev\\WcLicensedProductClient\\Tests\\": "tests/" + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "stable" +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..6f8ea95 --- /dev/null +++ b/composer.lock @@ -0,0 +1,537 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "dabb6ce648c8b638dc4b20b8999dea1d", + "packages": [ + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616", + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-23T14:50:43+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "75d7043853a42837e68111812f4d964b01e5101c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-29T11:18:49+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.3" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/tmp/openapi.json b/tmp/openapi.json new file mode 100644 index 0000000..6e98b39 --- /dev/null +++ b/tmp/openapi.json @@ -0,0 +1,538 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "WooCommerce Licensed Product API", + "description": "REST API for validating and managing software licenses bound to domains. This API allows external applications to validate license keys, check license status, and activate licenses on specific domains.", + "version": "0.0.7", + "contact": { + "name": "Marco Graetsch", + "url": "https://src.bundespruefstelle.ch/magdev", + "email": "magdev3.0@gmail.com" + }, + "license": { + "name": "GPL-2.0-or-later", + "url": "https://www.gnu.org/licenses/gpl-2.0.html" + } + }, + "servers": [ + { + "url": "{baseUrl}/wp-json/wc-licensed-product/v1", + "description": "WordPress REST API endpoint", + "variables": { + "baseUrl": { + "default": "https://example.com", + "description": "The base URL of your WordPress installation" + } + } + } + ], + "paths": { + "/validate": { + "post": { + "operationId": "validateLicense", + "summary": "Validate a license key for a domain", + "description": "Validates whether a license key is valid for a specific domain. Checks license status, expiration, and domain binding. This is the primary endpoint for software to verify license validity.", + "tags": ["License Validation"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateRequest" + }, + "example": { + "license_key": "ABCD-1234-EFGH-5678", + "domain": "example.com" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/ValidateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "License is valid for the specified domain", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateSuccessResponse" + }, + "example": { + "valid": true, + "license": { + "product_id": 123, + "expires_at": "2027-01-21", + "version_id": 5 + } + } + } + } + }, + "403": { + "description": "License validation failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateErrorResponse" + }, + "examples": { + "license_not_found": { + "summary": "License key not found", + "value": { + "valid": false, + "error": "license_not_found", + "message": "License key not found." + } + }, + "license_revoked": { + "summary": "License has been revoked", + "value": { + "valid": false, + "error": "license_revoked", + "message": "This license has been revoked." + } + }, + "license_expired": { + "summary": "License has expired", + "value": { + "valid": false, + "error": "license_expired", + "message": "This license has expired." + } + }, + "license_inactive": { + "summary": "License is inactive", + "value": { + "valid": false, + "error": "license_inactive", + "message": "This license is inactive." + } + }, + "domain_mismatch": { + "summary": "License not valid for this domain", + "value": { + "valid": false, + "error": "domain_mismatch", + "message": "This license is not valid for this domain." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitExceeded" + } + } + } + }, + "/status": { + "post": { + "operationId": "checkStatus", + "summary": "Get license status information", + "description": "Retrieves detailed status information for a license key, including validity, domain binding, expiration date, and activation counts.", + "tags": ["License Status"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusRequest" + }, + "example": { + "license_key": "ABCD-1234-EFGH-5678" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/StatusRequest" + } + } + } + }, + "responses": { + "200": { + "description": "License status retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusResponse" + }, + "example": { + "valid": true, + "status": "active", + "domain": "example.com", + "expires_at": "2027-01-21", + "activations_count": 1, + "max_activations": 3 + } + } + } + }, + "404": { + "description": "License key not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "example": { + "valid": false, + "error": "license_not_found", + "message": "License key not found." + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitExceeded" + } + } + } + }, + "/activate": { + "post": { + "operationId": "activateLicense", + "summary": "Activate a license on a domain", + "description": "Activates a license key on a specific domain. If the license is already activated on the same domain, returns success. If activating on a new domain, the old domain binding is replaced (single-domain licenses).", + "tags": ["License Activation"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActivateRequest" + }, + "example": { + "license_key": "ABCD-1234-EFGH-5678", + "domain": "newdomain.com" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/ActivateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "License activated successfully or already activated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActivateSuccessResponse" + }, + "examples": { + "activated": { + "summary": "License activated on new domain", + "value": { + "success": true, + "message": "License activated successfully." + } + }, + "already_activated": { + "summary": "License already activated on this domain", + "value": { + "success": true, + "message": "License is already activated for this domain." + } + } + } + } + } + }, + "403": { + "description": "Activation failed due to license restrictions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "license_invalid": { + "summary": "License is not valid", + "value": { + "success": false, + "error": "license_invalid", + "message": "This license is not valid." + } + }, + "max_activations_reached": { + "summary": "Maximum activations reached", + "value": { + "success": false, + "error": "max_activations_reached", + "message": "Maximum number of activations reached." + } + } + } + } + } + }, + "404": { + "description": "License key not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "example": { + "success": false, + "error": "license_not_found", + "message": "License key not found." + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitExceeded" + }, + "500": { + "description": "Server error during activation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "example": { + "success": false, + "error": "activation_failed", + "message": "Failed to activate license." + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ValidateRequest": { + "type": "object", + "required": ["license_key", "domain"], + "properties": { + "license_key": { + "type": "string", + "description": "The license key to validate (format: XXXX-XXXX-XXXX-XXXX)", + "maxLength": 64, + "example": "ABCD-1234-EFGH-5678" + }, + "domain": { + "type": "string", + "description": "The domain to validate the license against", + "maxLength": 255, + "example": "example.com" + } + } + }, + "ValidateSuccessResponse": { + "type": "object", + "properties": { + "valid": { + "type": "boolean", + "const": true, + "description": "Indicates the license is valid" + }, + "license": { + "type": "object", + "properties": { + "product_id": { + "type": "integer", + "description": "WooCommerce product ID associated with the license" + }, + "expires_at": { + "type": ["string", "null"], + "format": "date", + "description": "Expiration date (null for lifetime licenses)" + }, + "version_id": { + "type": ["integer", "null"], + "description": "Product version ID if license is bound to a version" + } + } + } + } + }, + "ValidateErrorResponse": { + "type": "object", + "properties": { + "valid": { + "type": "boolean", + "const": false, + "description": "Indicates validation failed" + }, + "error": { + "type": "string", + "enum": ["license_not_found", "license_revoked", "license_expired", "license_inactive", "domain_mismatch"], + "description": "Error code for programmatic handling" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + } + }, + "StatusRequest": { + "type": "object", + "required": ["license_key"], + "properties": { + "license_key": { + "type": "string", + "description": "The license key to check", + "example": "ABCD-1234-EFGH-5678" + } + } + }, + "StatusResponse": { + "type": "object", + "properties": { + "valid": { + "type": "boolean", + "description": "Whether the license is currently valid" + }, + "status": { + "type": "string", + "enum": ["active", "inactive", "expired", "revoked"], + "description": "Current license status" + }, + "domain": { + "type": "string", + "description": "Domain the license is bound to" + }, + "expires_at": { + "type": ["string", "null"], + "format": "date", + "description": "Expiration date (null for lifetime licenses)" + }, + "activations_count": { + "type": "integer", + "description": "Current number of activations" + }, + "max_activations": { + "type": "integer", + "description": "Maximum allowed activations" + } + } + }, + "ActivateRequest": { + "type": "object", + "required": ["license_key", "domain"], + "properties": { + "license_key": { + "type": "string", + "description": "The license key to activate", + "example": "ABCD-1234-EFGH-5678" + }, + "domain": { + "type": "string", + "description": "The domain to activate the license on", + "example": "newdomain.com" + } + } + }, + "ActivateSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "const": true, + "description": "Indicates activation was successful" + }, + "message": { + "type": "string", + "description": "Human-readable success message" + } + } + }, + "ErrorResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "const": false, + "description": "Indicates the operation failed" + }, + "valid": { + "type": "boolean", + "const": false, + "description": "Indicates validation failed (for validation endpoints)" + }, + "error": { + "type": "string", + "description": "Error code for programmatic handling" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + } + }, + "RateLimitResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "const": false + }, + "error": { + "type": "string", + "const": "rate_limit_exceeded" + }, + "message": { + "type": "string", + "example": "Too many requests. Please try again later." + }, + "retry_after": { + "type": "integer", + "description": "Seconds until rate limit resets" + } + } + } + }, + "responses": { + "RateLimitExceeded": { + "description": "Rate limit exceeded (30 requests per minute per IP)", + "headers": { + "Retry-After": { + "schema": { + "type": "integer" + }, + "description": "Seconds until the rate limit resets" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitResponse" + }, + "example": { + "success": false, + "error": "rate_limit_exceeded", + "message": "Too many requests. Please try again later.", + "retry_after": 45 + } + } + } + } + } + }, + "tags": [ + { + "name": "License Validation", + "description": "Validate license keys against domains" + }, + { + "name": "License Status", + "description": "Check license status and details" + }, + { + "name": "License Activation", + "description": "Activate licenses on domains" + } + ] +}