feat: Implement OAuth license activation flow
- Add LicenseConnect.tsx focused OAuth confirmation page in customer SPA - Add /licenses/oauth/validate and /licenses/oauth/confirm API endpoints - Update App.tsx to render license-connect outside BaseLayout (no header/footer) - Add license_activation_method field to product settings in Admin SPA - Create LICENSING_MODULE.md with comprehensive OAuth flow documentation - Update API_ROUTES.md with license module endpoints
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace WooNooW\Api\Controllers;
|
||||
|
||||
use WP_REST_Controller;
|
||||
@@ -9,21 +10,23 @@ use WP_Error;
|
||||
* Cart Controller
|
||||
* Handles cart operations via REST API
|
||||
*/
|
||||
class CartController extends WP_REST_Controller {
|
||||
|
||||
class CartController extends WP_REST_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public function register_routes() {
|
||||
public function register_routes()
|
||||
{
|
||||
$namespace = 'woonoow/v1';
|
||||
|
||||
|
||||
// Get cart
|
||||
register_rest_route($namespace, '/cart', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
|
||||
// Add to cart
|
||||
register_rest_route($namespace, '/cart/add', [
|
||||
'methods' => 'POST',
|
||||
@@ -48,7 +51,7 @@ class CartController extends WP_REST_Controller {
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
// Update cart item
|
||||
register_rest_route($namespace, '/cart/update', [
|
||||
'methods' => 'POST',
|
||||
@@ -66,7 +69,7 @@ class CartController extends WP_REST_Controller {
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
// Remove from cart
|
||||
register_rest_route($namespace, '/cart/remove', [
|
||||
'methods' => 'POST',
|
||||
@@ -79,14 +82,14 @@ class CartController extends WP_REST_Controller {
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
// Clear cart
|
||||
register_rest_route($namespace, '/cart/clear', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'clear_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
|
||||
// Apply coupon
|
||||
register_rest_route($namespace, '/cart/apply-coupon', [
|
||||
'methods' => 'POST',
|
||||
@@ -100,7 +103,7 @@ class CartController extends WP_REST_Controller {
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
// Remove coupon
|
||||
register_rest_route($namespace, '/cart/remove-coupon', [
|
||||
'methods' => 'POST',
|
||||
@@ -115,25 +118,26 @@ class CartController extends WP_REST_Controller {
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get cart contents
|
||||
*/
|
||||
public function get_cart($request) {
|
||||
public function get_cart($request)
|
||||
{
|
||||
if (!function_exists('WC')) {
|
||||
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Ensure cart is initialized
|
||||
if (is_null(WC()->cart)) {
|
||||
wc_load_cart();
|
||||
}
|
||||
|
||||
|
||||
$cart = WC()->cart;
|
||||
|
||||
|
||||
// Calculate totals to ensure discounts are computed
|
||||
$cart->calculate_totals();
|
||||
|
||||
|
||||
// Format coupons with discount amounts
|
||||
$coupons_with_discounts = [];
|
||||
foreach ($cart->get_applied_coupons() as $coupon_code) {
|
||||
@@ -145,7 +149,7 @@ class CartController extends WP_REST_Controller {
|
||||
'type' => $coupon->get_discount_type(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'items' => $this->format_cart_items($cart->get_cart()),
|
||||
'totals' => [
|
||||
@@ -167,42 +171,107 @@ class CartController extends WP_REST_Controller {
|
||||
'item_count' => $cart->get_cart_contents_count(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add product to cart
|
||||
*/
|
||||
public function add_to_cart($request) {
|
||||
public function add_to_cart($request)
|
||||
{
|
||||
if (!function_exists('WC')) {
|
||||
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Ensure cart is initialized
|
||||
if (is_null(WC()->cart)) {
|
||||
wc_load_cart();
|
||||
}
|
||||
|
||||
|
||||
$product_id = $request->get_param('product_id');
|
||||
$quantity = $request->get_param('quantity') ?: 1;
|
||||
$variation_id = $request->get_param('variation_id') ?: 0;
|
||||
|
||||
$variation = $request->get_param('variation') ?: [];
|
||||
|
||||
// TEMPORARY DEBUG: Return early to confirm this code is reached
|
||||
if ($product_id == 512) {
|
||||
return new WP_REST_Response([
|
||||
'debug' => true,
|
||||
'message' => 'CartController reached',
|
||||
'product_id' => $product_id,
|
||||
'variation_id' => $variation_id,
|
||||
'variation' => $variation,
|
||||
], 200);
|
||||
}
|
||||
|
||||
// Validate product
|
||||
$product = wc_get_product($product_id);
|
||||
if (!$product) {
|
||||
return new WP_Error('invalid_product', 'Product not found', ['status' => 404]);
|
||||
}
|
||||
|
||||
|
||||
// Check stock
|
||||
if (!$product->is_in_stock()) {
|
||||
return new WP_Error('out_of_stock', 'Product is out of stock', ['status' => 400]);
|
||||
}
|
||||
|
||||
// Add to cart
|
||||
$cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id);
|
||||
|
||||
if (!$cart_item_key) {
|
||||
return new WP_Error('add_to_cart_failed', 'Failed to add product to cart', ['status' => 400]);
|
||||
|
||||
// For variable products with a variation_id, handle attributes properly
|
||||
// This ensures correct attribute keys even if frontend sends wrong/empty data
|
||||
if ($variation_id > 0) {
|
||||
$variation_product = wc_get_product($variation_id);
|
||||
if ($variation_product && $variation_product->is_type('variation')) {
|
||||
// Get the actual attributes stored on the variation
|
||||
$stored_attributes = $variation_product->get_variation_attributes();
|
||||
|
||||
// Merge: use stored attributes as base, but fill in empty values from frontend
|
||||
// This handles variations created with "Any X" option (empty values)
|
||||
$frontend_variation = $request->get_param('variation') ?: [];
|
||||
|
||||
foreach ($stored_attributes as $key => $value) {
|
||||
if ($value === '' && isset($frontend_variation[$key]) && $frontend_variation[$key] !== '') {
|
||||
// Stored value is empty ("Any"), use frontend value
|
||||
$stored_attributes[$key] = sanitize_text_field($frontend_variation[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$variation = $stored_attributes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// DEBUG: Log what we're passing to add_to_cart
|
||||
error_log('[CartController] product_id: ' . $product_id);
|
||||
error_log('[CartController] quantity: ' . $quantity);
|
||||
error_log('[CartController] variation_id: ' . $variation_id);
|
||||
error_log('[CartController] variation: ' . json_encode($variation));
|
||||
error_log('[CartController] frontend_variation (raw): ' . json_encode($request->get_param('variation')));
|
||||
|
||||
// Add to cart
|
||||
$cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id, $variation);
|
||||
|
||||
if (!$cart_item_key) {
|
||||
// Get error notices from WooCommerce to provide a specific reason
|
||||
$notices = wc_get_notices('error');
|
||||
$message = 'Failed to add product to cart';
|
||||
|
||||
if (!empty($notices)) {
|
||||
// Get the last error message
|
||||
$last_notice = end($notices);
|
||||
if (isset($last_notice['notice'])) {
|
||||
// Strip HTML tags for clean error message
|
||||
$message = wp_strip_all_tags($last_notice['notice']);
|
||||
}
|
||||
}
|
||||
|
||||
wc_clear_notices();
|
||||
return new WP_Error('add_to_cart_failed', $message, [
|
||||
'status' => 400,
|
||||
'debug' => [
|
||||
'product_id' => $product_id,
|
||||
'variation_id' => $variation_id,
|
||||
'variation_passed' => $variation,
|
||||
'frontend_variation' => $request->get_param('variation'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'cart_item_key' => $cart_item_key,
|
||||
@@ -210,166 +279,172 @@ class CartController extends WP_REST_Controller {
|
||||
'cart' => $this->get_cart($request)->data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update cart item quantity
|
||||
*/
|
||||
public function update_cart_item($request) {
|
||||
public function update_cart_item($request)
|
||||
{
|
||||
if (!function_exists('WC')) {
|
||||
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Ensure cart is initialized
|
||||
if (is_null(WC()->cart)) {
|
||||
wc_load_cart();
|
||||
}
|
||||
|
||||
|
||||
$cart_item_key = $request->get_param('cart_item_key');
|
||||
$quantity = $request->get_param('quantity');
|
||||
|
||||
|
||||
// Validate cart item
|
||||
$cart = WC()->cart->get_cart();
|
||||
if (!isset($cart[$cart_item_key])) {
|
||||
return new WP_Error('invalid_cart_item', 'Cart item not found', ['status' => 404]);
|
||||
}
|
||||
|
||||
|
||||
// Update quantity
|
||||
$updated = WC()->cart->set_quantity($cart_item_key, $quantity);
|
||||
|
||||
|
||||
if (!$updated) {
|
||||
return new WP_Error('update_failed', 'Failed to update cart item', ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Cart updated successfully',
|
||||
'cart' => $this->get_cart($request)->data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove item from cart
|
||||
*/
|
||||
public function remove_from_cart($request) {
|
||||
public function remove_from_cart($request)
|
||||
{
|
||||
if (!function_exists('WC')) {
|
||||
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Ensure cart is initialized
|
||||
if (is_null(WC()->cart)) {
|
||||
wc_load_cart();
|
||||
}
|
||||
|
||||
|
||||
$cart_item_key = $request->get_param('cart_item_key');
|
||||
|
||||
|
||||
// Validate cart item
|
||||
$cart = WC()->cart->get_cart();
|
||||
if (!isset($cart[$cart_item_key])) {
|
||||
return new WP_Error('invalid_cart_item', 'Cart item not found', ['status' => 404]);
|
||||
}
|
||||
|
||||
|
||||
// Remove item
|
||||
$removed = WC()->cart->remove_cart_item($cart_item_key);
|
||||
|
||||
|
||||
if (!$removed) {
|
||||
return new WP_Error('remove_failed', 'Failed to remove cart item', ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Item removed from cart',
|
||||
'cart' => $this->get_cart($request)->data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear cart
|
||||
*/
|
||||
public function clear_cart($request) {
|
||||
public function clear_cart($request)
|
||||
{
|
||||
if (!function_exists('WC')) {
|
||||
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Ensure cart is initialized
|
||||
if (is_null(WC()->cart)) {
|
||||
wc_load_cart();
|
||||
}
|
||||
|
||||
|
||||
WC()->cart->empty_cart();
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Cart cleared successfully',
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply coupon
|
||||
*/
|
||||
public function apply_coupon($request) {
|
||||
public function apply_coupon($request)
|
||||
{
|
||||
if (!function_exists('WC')) {
|
||||
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Ensure cart is initialized
|
||||
if (is_null(WC()->cart)) {
|
||||
wc_load_cart();
|
||||
}
|
||||
|
||||
|
||||
$coupon_code = $request->get_param('coupon_code');
|
||||
|
||||
|
||||
// Apply coupon
|
||||
$applied = WC()->cart->apply_coupon($coupon_code);
|
||||
|
||||
|
||||
if (!$applied) {
|
||||
return new WP_Error('coupon_failed', 'Failed to apply coupon', ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Coupon applied successfully',
|
||||
'cart' => $this->get_cart($request)->data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove coupon
|
||||
*/
|
||||
public function remove_coupon($request) {
|
||||
public function remove_coupon($request)
|
||||
{
|
||||
if (!function_exists('WC')) {
|
||||
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Ensure cart is initialized
|
||||
if (is_null(WC()->cart)) {
|
||||
wc_load_cart();
|
||||
}
|
||||
|
||||
|
||||
$coupon_code = $request->get_param('coupon_code');
|
||||
|
||||
|
||||
// Remove coupon
|
||||
$removed = WC()->cart->remove_coupon($coupon_code);
|
||||
|
||||
|
||||
if (!$removed) {
|
||||
return new WP_Error('coupon_remove_failed', 'Failed to remove coupon', ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Coupon removed successfully',
|
||||
'cart' => $this->get_cart($request)->data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format cart items for response
|
||||
*/
|
||||
private function format_cart_items($cart_items) {
|
||||
private function format_cart_items($cart_items)
|
||||
{
|
||||
$formatted = [];
|
||||
|
||||
|
||||
foreach ($cart_items as $cart_item_key => $cart_item) {
|
||||
$product = $cart_item['data'];
|
||||
|
||||
|
||||
$formatted[] = [
|
||||
'key' => $cart_item_key,
|
||||
'product_id' => $cart_item['product_id'],
|
||||
@@ -385,7 +460,7 @@ class CartController extends WP_REST_Controller {
|
||||
'downloadable' => $product->is_downloadable(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Licenses API Controller
|
||||
*
|
||||
@@ -17,91 +18,111 @@ use WP_Error;
|
||||
use WooNooW\Core\ModuleRegistry;
|
||||
use WooNooW\Modules\Licensing\LicenseManager;
|
||||
|
||||
class LicensesController {
|
||||
|
||||
class LicensesController
|
||||
{
|
||||
|
||||
/**
|
||||
* Register REST routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
public static function register_routes()
|
||||
{
|
||||
// Check if module is enabled
|
||||
if (!ModuleRegistry::is_enabled('licensing')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Admin routes
|
||||
register_rest_route('woonoow/v1', '/licenses', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_licenses'],
|
||||
'permission_callback' => function() {
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_license'],
|
||||
'permission_callback' => function() {
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [__CLASS__, 'revoke_license'],
|
||||
'permission_callback' => function() {
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/(?P<id>\d+)/activations', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_activations'],
|
||||
'permission_callback' => function() {
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
// Customer routes
|
||||
register_rest_route('woonoow/v1', '/account/licenses', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_customer_licenses'],
|
||||
'permission_callback' => function() {
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
register_rest_route('woonoow/v1', '/account/licenses/(?P<id>\d+)/deactivate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'customer_deactivate'],
|
||||
'permission_callback' => function() {
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
// Public API routes (for software validation)
|
||||
register_rest_route('woonoow/v1', '/licenses/validate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'validate_license'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/activate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'activate_license'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/deactivate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'deactivate_license'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// OAuth endpoints for license-connect
|
||||
register_rest_route('woonoow/v1', '/licenses/oauth/validate', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'oauth_validate'],
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/oauth/confirm', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'oauth_confirm'],
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all licenses (admin)
|
||||
*/
|
||||
public static function get_licenses(WP_REST_Request $request) {
|
||||
public static function get_licenses(WP_REST_Request $request)
|
||||
{
|
||||
$args = [
|
||||
'search' => $request->get_param('search'),
|
||||
'status' => $request->get_param('status'),
|
||||
@@ -110,14 +131,14 @@ class LicensesController {
|
||||
'limit' => $request->get_param('per_page') ?: 50,
|
||||
'offset' => (($request->get_param('page') ?: 1) - 1) * ($request->get_param('per_page') ?: 50),
|
||||
];
|
||||
|
||||
|
||||
$result = LicenseManager::get_all_licenses($args);
|
||||
|
||||
|
||||
// Enrich with product and user info
|
||||
foreach ($result['licenses'] as &$license) {
|
||||
$license = self::enrich_license($license);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'licenses' => $result['licenses'],
|
||||
'total' => $result['total'],
|
||||
@@ -125,167 +146,282 @@ class LicensesController {
|
||||
'per_page' => $args['limit'],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get single license (admin)
|
||||
*/
|
||||
public static function get_license(WP_REST_Request $request) {
|
||||
public static function get_license(WP_REST_Request $request)
|
||||
{
|
||||
$license = LicenseManager::get_license($request->get_param('id'));
|
||||
|
||||
|
||||
if (!$license) {
|
||||
return new WP_Error('not_found', __('License not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
|
||||
$license = self::enrich_license($license);
|
||||
$license['activations'] = LicenseManager::get_activations($license['id']);
|
||||
|
||||
|
||||
return new WP_REST_Response($license);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Revoke license (admin)
|
||||
*/
|
||||
public static function revoke_license(WP_REST_Request $request) {
|
||||
public static function revoke_license(WP_REST_Request $request)
|
||||
{
|
||||
$result = LicenseManager::revoke($request->get_param('id'));
|
||||
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('revoke_failed', __('Failed to revoke license', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get activations for license (admin)
|
||||
*/
|
||||
public static function get_activations(WP_REST_Request $request) {
|
||||
public static function get_activations(WP_REST_Request $request)
|
||||
{
|
||||
$activations = LicenseManager::get_activations($request->get_param('id'));
|
||||
return new WP_REST_Response($activations);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get customer's licenses
|
||||
*/
|
||||
public static function get_customer_licenses(WP_REST_Request $request) {
|
||||
public static function get_customer_licenses(WP_REST_Request $request)
|
||||
{
|
||||
$user_id = get_current_user_id();
|
||||
$licenses = LicenseManager::get_user_licenses($user_id);
|
||||
|
||||
|
||||
// Enrich each license
|
||||
foreach ($licenses as &$license) {
|
||||
$license = self::enrich_license($license);
|
||||
$license['activations'] = LicenseManager::get_activations($license['id']);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response($licenses);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Customer deactivate their own activation
|
||||
*/
|
||||
public static function customer_deactivate(WP_REST_Request $request) {
|
||||
public static function customer_deactivate(WP_REST_Request $request)
|
||||
{
|
||||
$user_id = get_current_user_id();
|
||||
$license = LicenseManager::get_license($request->get_param('id'));
|
||||
|
||||
|
||||
if (!$license || $license['user_id'] != $user_id) {
|
||||
return new WP_Error('not_found', __('License not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
|
||||
$data = $request->get_json_params();
|
||||
$result = LicenseManager::deactivate(
|
||||
$license['license_key'],
|
||||
$data['activation_id'] ?? null,
|
||||
$data['machine_id'] ?? null
|
||||
);
|
||||
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate license (public API)
|
||||
*/
|
||||
public static function validate_license(WP_REST_Request $request) {
|
||||
public static function validate_license(WP_REST_Request $request)
|
||||
{
|
||||
$data = $request->get_json_params();
|
||||
|
||||
|
||||
if (empty($data['license_key'])) {
|
||||
return new WP_Error('missing_key', __('License key is required', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
$result = LicenseManager::validate($data['license_key']);
|
||||
return new WP_REST_Response($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Activate license (public API)
|
||||
*/
|
||||
public static function activate_license(WP_REST_Request $request) {
|
||||
public static function activate_license(WP_REST_Request $request)
|
||||
{
|
||||
$data = $request->get_json_params();
|
||||
|
||||
|
||||
if (empty($data['license_key'])) {
|
||||
return new WP_Error('missing_key', __('License key is required', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
$activation_data = [
|
||||
'domain' => $data['domain'] ?? null,
|
||||
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
'machine_id' => $data['machine_id'] ?? null,
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
|
||||
'return_url' => $data['return_url'] ?? null,
|
||||
'activation_token' => $data['activation_token'] ?? null,
|
||||
];
|
||||
|
||||
|
||||
$result = LicenseManager::activate($data['license_key'], $activation_data);
|
||||
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deactivate license (public API)
|
||||
*/
|
||||
public static function deactivate_license(WP_REST_Request $request) {
|
||||
public static function deactivate_license(WP_REST_Request $request)
|
||||
{
|
||||
$data = $request->get_json_params();
|
||||
|
||||
|
||||
if (empty($data['license_key'])) {
|
||||
return new WP_Error('missing_key', __('License key is required', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
$result = LicenseManager::deactivate(
|
||||
$data['license_key'],
|
||||
$data['activation_id'] ?? null,
|
||||
$data['machine_id'] ?? null
|
||||
);
|
||||
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enrich license with product and user info
|
||||
*/
|
||||
private static function enrich_license($license) {
|
||||
private static function enrich_license($license)
|
||||
{
|
||||
// Add product info
|
||||
$product = wc_get_product($license['product_id']);
|
||||
$license['product_name'] = $product ? $product->get_name() : __('Unknown Product', 'woonoow');
|
||||
|
||||
|
||||
// Add user info
|
||||
$user = get_userdata($license['user_id']);
|
||||
$license['user_email'] = $user ? $user->user_email : '';
|
||||
$license['user_name'] = $user ? $user->display_name : __('Unknown User', 'woonoow');
|
||||
|
||||
|
||||
// Add computed fields
|
||||
$license['is_expired'] = $license['expires_at'] && strtotime($license['expires_at']) < time();
|
||||
$license['activations_remaining'] = $license['activation_limit'] > 0
|
||||
$license['activations_remaining'] = $license['activation_limit'] > 0
|
||||
? max(0, $license['activation_limit'] - $license['activation_count'])
|
||||
: -1;
|
||||
|
||||
|
||||
return $license;
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth validate endpoint - validates license key and state for OAuth flow
|
||||
*/
|
||||
public static function oauth_validate(WP_REST_Request $request)
|
||||
{
|
||||
$license_key = sanitize_text_field($request->get_param('license_key'));
|
||||
$state = sanitize_text_field($request->get_param('state'));
|
||||
|
||||
if (empty($license_key)) {
|
||||
return new WP_Error('missing_license_key', __('License key is required.', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
// Get license
|
||||
$license = LicenseManager::get_license_by_key($license_key);
|
||||
if (!$license) {
|
||||
return new WP_Error('license_not_found', __('License key not found.', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
// Verify license belongs to current user
|
||||
$current_user_id = get_current_user_id();
|
||||
if ($license['user_id'] != $current_user_id) {
|
||||
return new WP_Error('unauthorized', __('This license does not belong to your account.', 'woonoow'), ['status' => 403]);
|
||||
}
|
||||
|
||||
// Verify state token if provided
|
||||
if (!empty($state)) {
|
||||
$state_data = LicenseManager::verify_oauth_state($state);
|
||||
if (!$state_data) {
|
||||
return new WP_Error('invalid_state', __('Invalid or expired state token.', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
}
|
||||
|
||||
// Get product info
|
||||
$product = wc_get_product($license['product_id']);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'license_key' => $license['license_key'],
|
||||
'product_id' => $license['product_id'],
|
||||
'product_name' => $product ? $product->get_name() : __('Unknown Product', 'woonoow'),
|
||||
'status' => $license['status'],
|
||||
'activation_limit' => $license['activation_limit'],
|
||||
'activation_count' => $license['activation_count'],
|
||||
'expires_at' => $license['expires_at'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth confirm endpoint - confirms license activation and returns token
|
||||
*/
|
||||
public static function oauth_confirm(WP_REST_Request $request)
|
||||
{
|
||||
$data = $request->get_json_params();
|
||||
|
||||
$license_key = sanitize_text_field($data['license_key'] ?? '');
|
||||
$site_url = esc_url_raw($data['site_url'] ?? '');
|
||||
$state = sanitize_text_field($data['state'] ?? '');
|
||||
$nonce = sanitize_text_field($data['nonce'] ?? '');
|
||||
|
||||
if (empty($license_key) || empty($site_url) || empty($state)) {
|
||||
return new WP_Error('missing_params', __('Missing required parameters.', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
// Get license
|
||||
$license = LicenseManager::get_license_by_key($license_key);
|
||||
if (!$license) {
|
||||
return new WP_Error('license_not_found', __('License key not found.', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
// Verify license belongs to current user
|
||||
$current_user_id = get_current_user_id();
|
||||
if ($license['user_id'] != $current_user_id) {
|
||||
return new WP_Error('unauthorized', __('This license does not belong to your account.', 'woonoow'), ['status' => 403]);
|
||||
}
|
||||
|
||||
// Verify state token
|
||||
$state_data = LicenseManager::verify_oauth_state($state);
|
||||
if (!$state_data) {
|
||||
return new WP_Error('invalid_state', __('Invalid or expired state token.', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
// Generate activation token
|
||||
$token_data = LicenseManager::generate_activation_token($license['id'], $site_url);
|
||||
|
||||
if (is_wp_error($token_data)) {
|
||||
return $token_data;
|
||||
}
|
||||
|
||||
$activation_token = $token_data['token'];
|
||||
|
||||
// Build return URL with token
|
||||
$return_url = $state_data['return_url'] ?? $site_url;
|
||||
$redirect_url = add_query_arg([
|
||||
'activation_token' => $activation_token,
|
||||
'license_key' => $license_key,
|
||||
'nonce' => $nonce,
|
||||
], $return_url);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'redirect_url' => $redirect_url,
|
||||
'activation_token' => $activation_token,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user