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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user