From 1fdedd16ad4d977fc6bb3382abd68bc24a248447 Mon Sep 17 00:00:00 2001 From: magdev Date: Thu, 22 Jan 2026 15:37:20 +0100 Subject: [PATCH] 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 --- .editorconfig | 15 ++ .gitignore | 9 + CHANGELOG.md | 18 ++ CLAUDE.md | 91 ++++++++ README.md | 49 +++++ composer.json | 36 ++++ composer.lock | 537 ++++++++++++++++++++++++++++++++++++++++++++++ tmp/openapi.json | 538 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1293 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 tmp/openapi.json 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" + } + ] +}