wallet_manager = Wallet_Manager::get_instance(); // Hook into order status changes to process cashback add_action("woocommerce_order_status_changed", array($this, "process_order_cashback"), 10, 4); } /** * Check if cashback is enabled * * @return bool */ public function is_cashback_enabled() { return get_option("pisol_aww_enable_cashback", false); } /** * Process cashback when order status changes * * @param int $order_id Order ID * @param string $old_status Old status * @param string $new_status New status * @param object $order Order object * @return bool */ public function process_order_cashback($order_id, $old_status, $new_status, $order) { // Check if cashback is enabled if (!$this->is_cashback_enabled()) { return false; } // Get order processing statuses that trigger cashback $processing_statuses = get_option("pisol_aww_process_cashback_status", array("completed", "processing")); // Check if the new status is one that should trigger cashback if (!in_array( $new_status, $processing_statuses)) { return false; } // Get the user ID from the order $user_id = $order->get_user_id(); // Skip if there is no user ID (guest checkout) if (!$user_id) { return false; } // Check if user role is excluded if ($this->is_user_excluded($user_id)) { return false; } // Check if the order was paid completely with wallet balance $payment_method = $order->get_payment_method(); $wallet_payment = get_post_meta($order_id, '_wallet_payment', true); // Skip cashback if order was paid entirely with wallet balance if ($payment_method === 'pisol_wallet' || $wallet_payment === 'yes') { // Add a note to the order to explain why cashback wasn't granted $order->add_order_note( __('No cashback awarded because order was paid with wallet balance.', 'advanced-wallet-for-woocommerce') ); return false; } // Check if cashback has already been processed for this order $cashback_processed = $order->get_meta("_pisol_aww_cashback_processed"); if ($cashback_processed) { return false; } // Calculate cashback amount based on configured rule $cashback_rule = get_option("pisol_aww_cashback_rule", "cart"); $cashback_amount = 0; switch ($cashback_rule) { case "cart": $cashback_amount = $this->calculate_cart_cashback($order); break; case "product": $cashback_amount = $this->calculate_product_cashback($order); break; case "category": $cashback_amount = $this->calculate_category_cashback($order); break; } // Apply maximum cashback limit if set (this applies to all cashback types) $max_cashback = floatval(get_option("pisol_aww_max_cashback_amount", 0)); if ($max_cashback > 0 && $cashback_amount > $max_cashback) { $cashback_amount = $max_cashback; } // Process the cashback if amount is greater than zero if ($cashback_amount > 0) { $currency = $order->get_currency(); // Calculate expiry date if configured $expiry_days = absint(get_option('pisol_aww_cashback_expiry_days', 0)); $expiry_date = null; if ($expiry_days > 0) { // Calculate expiry date based on current date using gmdate which is timezone independent $expiry_date = $this->wallet_manager::get_expiry_date($expiry_days); } // Prepare credit params $credit_params = array( "user_id" => $user_id, "amount" => $cashback_amount, "currency" => $currency, "source" => "cashback", /* translators: %s: order number */ "note" => sprintf(__("Cashback for order #%s", "advanced-wallet-for-woocommerce"), $order->get_order_number()) ); // Add expiry date if set if ($expiry_date !== null) { $credit_params["expiry_date"] = $expiry_date; } // Add credit to user wallet $credit_id = $this->wallet_manager->add_credit($credit_params); if ($credit_id) { Email::send_email( $order->get_billing_email(), sprintf(__('Cashback Credited for Order #%s', 'advanced-wallet-for-woocommerce'), $order->get_order_number()), sprintf( __('You have received %1$s cashback for your order #%2$s.', 'advanced-wallet-for-woocommerce'), wc_price($cashback_amount, array('currency' => $currency)), $order->get_order_number() ), __('Cashback Credited', 'advanced-wallet-for-woocommerce') ); // Mark order as processed for cashback $order->update_meta_data("_pisol_aww_cashback_processed", "yes"); $order->update_meta_data("_pisol_aww_cashback_amount", $cashback_amount); $order->update_meta_data("_pisol_aww_cashback_credit_id", $credit_id); // Store expiry information if applicable if ($expiry_date !== null) { $order->update_meta_data("_pisol_aww_cashback_expires_on", $expiry_date); } $order->save(); // Add note to order $note_text = sprintf( // translators: %1$s: formatted price, %2$s: currency code __('%1$s %2$s cashback credited to customer wallet.', "advanced-wallet-for-woocommerce"), wc_price($cashback_amount), $currency ); // Add expiry information to the note if applicable if ($expiry_date !== null) { $note_text .= ' ' . sprintf( // translators: %s: expiry date __("Expires on %s.", "advanced-wallet-for-woocommerce"), $expiry_date ); } $order->add_order_note($note_text); return true; } } return false; } /** * Check if user is excluded from cashback * * @param int $user_id User ID * @return bool */ private function is_user_excluded($user_id) { $excluded_roles = get_option("pisol_aww_exclude_user_roles", array()); // If no roles are excluded, return false if (empty($excluded_roles)) { return false; } // Get user roles $user = get_userdata($user_id); if (!$user) { return true; // If user not found, exclude them } $user_roles = $user->roles; // Check if any of the user roles are excluded foreach ($user_roles as $role) { if (in_array($role, $excluded_roles)) { return true; } } return false; } /** * Calculate cashback for the entire cart * * @param object $order WooCommerce order object * @return float Cashback amount */ private function calculate_cart_cashback($order) { // Get cashback type (fixed or percentage) $cashback_type = get_option("pisol_aww_cashback_type", "percentage"); $cashback_amount = floatval(get_option("pisol_aww_cashback_amount", 0)); // Get order total $order_total = $order->get_total(); // Calculate cashback $calculated_amount = 0; if ($cashback_type === "fixed") { // Fixed amount cashback $calculated_amount = $cashback_amount; } else { // Percentage cashback $calculated_amount = ($order_total * $cashback_amount) / 100; } return $calculated_amount; } /** * Calculate cashback based on products * * @param object $order WooCommerce order object * @return float Cashback amount */ private function calculate_product_cashback($order) { // Get global cashback settings (will be used as fallback) $global_cashback_type = get_option("pisol_aww_cashback_type", "percentage"); $global_cashback_amount = floatval(get_option("pisol_aww_cashback_amount", 0)); $total_cashback = 0; // Loop through each product in the order foreach ($order->get_items() as $item_id => $item) { // Get product details $product = $item->get_product(); if (!$product) continue; $product_id = $product->get_id(); $parent_id = $product->get_parent_id(); // Will be 0 for simple products, parent ID for variations $quantity = $item->get_quantity(); $product_total = $item->get_total(); // Check if this product should be excluded from cashback $exclude_product = apply_filters('pisol_aww_exclude_product_from_cashback', false, $product_id, $order); if ($exclude_product) continue; // Default to global settings $cashback_type = $global_cashback_type; $cashback_amount = $global_cashback_amount; // Check for variation-specific cashback configuration (for variable products) $variation_cashback_found = false; if ($parent_id > 0) { // This is a variation $has_variation_cashback = get_post_meta($product_id, '_pisol_aww_has_custom_cashback', true); if ('yes' === $has_variation_cashback) { $variation_cashback_type = get_post_meta($product_id, '_pisol_aww_cashback_type', true); $variation_cashback_amount = get_post_meta($product_id, '_pisol_aww_cashback_amount', true); // Use variation-specific settings if they exist if (!empty($variation_cashback_type)) { $cashback_type = $variation_cashback_type; $variation_cashback_found = true; } if ('' !== $variation_cashback_amount) { $cashback_amount = floatval($variation_cashback_amount); $variation_cashback_found = true; } } } // If no variation-specific cashback was found, check for product-level cashback configuration if (!$variation_cashback_found) { // For variations, check the parent product instead $check_product_id = ($parent_id > 0) ? $parent_id : $product_id; $has_product_cashback = get_post_meta($check_product_id, '_pisol_aww_has_custom_cashback', true); if ('yes' === $has_product_cashback) { $product_cashback_type = get_post_meta($check_product_id, '_pisol_aww_cashback_type', true); $product_cashback_amount = get_post_meta($check_product_id, '_pisol_aww_cashback_amount', true); // Override global settings with product-specific settings if they exist if (!empty($product_cashback_type)) { $cashback_type = $product_cashback_type; } if ('' !== $product_cashback_amount) { $cashback_amount = floatval($product_cashback_amount); } } } // Allow third-party modifications to product cashback settings $cashback_settings = apply_filters( 'pisol_aww_product_cashback_settings', array( 'type' => $cashback_type, 'amount' => $cashback_amount ), $product_id, $order ); $cashback_type = $cashback_settings['type']; $cashback_amount = $cashback_settings['amount']; // Calculate cashback for this product $product_cashback = 0; if ('fixed' === $cashback_type) { // Fixed amount cashback (fixed amount × quantity) $product_cashback = $cashback_amount * $quantity; } else { // Percentage cashback (% of product total) $product_cashback = ($product_total * $cashback_amount) / 100; } // Allow filtering of final product cashback amount $product_cashback = apply_filters( 'pisol_aww_product_cashback_amount', $product_cashback, $product_id, $quantity, $product_total, $order, $cashback_type, $cashback_amount ); // Add to total cashback $total_cashback += $product_cashback; } return $total_cashback; } /** * Calculate cashback based on categories * * @param object $order WooCommerce order object * @return float Cashback amount */ private function calculate_category_cashback($order) { // Get global cashback settings (will be used as fallback) $global_cashback_type = get_option('pisol_aww_cashback_type', 'percentage'); $global_cashback_amount = floatval(get_option('pisol_aww_cashback_amount', 0)); $total_cashback = 0; // Loop through each product in the order foreach ($order->get_items() as $item_id => $item) { // Get product details $product = $item->get_product(); if (!$product) continue; $product_id = $product->get_id(); $quantity = $item->get_quantity(); $product_total = $item->get_total(); // Check if this product should be excluded from cashback $exclude_product = apply_filters('pisol_aww_exclude_product_from_cashback', false, $product_id, $order); if ($exclude_product) continue; // Default to global settings $cashback_type = $global_cashback_type; $cashback_amount = $global_cashback_amount; // Get product categories $category_ids = wc_get_product_term_ids($product_id, 'product_cat'); if (!empty($category_ids)) { // Sort categories by hierarchy (we'll process from parent to child) // This ensures child category settings override parent category settings $sorted_categories = $this->get_sorted_categories($category_ids); // Check each category for custom cashback settings foreach ($sorted_categories as $term_id) { $has_category_cashback = get_term_meta($term_id, '_pisol_aww_has_custom_cashback', true); if ('yes' === $has_category_cashback) { $category_cashback_type = get_term_meta($term_id, '_pisol_aww_cashback_type', true); $category_cashback_amount = get_term_meta($term_id, '_pisol_aww_cashback_amount', true); // Override previous settings with category-specific settings if they exist if (!empty($category_cashback_type)) { $cashback_type = $category_cashback_type; } if ('' !== $category_cashback_amount) { $cashback_amount = floatval($category_cashback_amount); } } } } // Allow third-party modifications to category cashback settings $cashback_settings = apply_filters( 'pisol_aww_category_cashback_settings', array( 'type' => $cashback_type, 'amount' => $cashback_amount ), $category_ids, $product_id, $order ); $cashback_type = $cashback_settings['type']; $cashback_amount = $cashback_settings['amount']; // Calculate cashback for this product based on its category settings $product_cashback = 0; if ('fixed' === $cashback_type) { // Fixed amount cashback (fixed amount × quantity) $product_cashback = $cashback_amount * $quantity; } else { // Percentage cashback (% of product total) $product_cashback = ($product_total * $cashback_amount) / 100; } // Allow filtering of final category-based product cashback amount $product_cashback = apply_filters( 'pisol_aww_category_product_cashback_amount', $product_cashback, $product_id, $category_ids, $quantity, $product_total, $order, $cashback_type, $cashback_amount ); // Add to total cashback $total_cashback += $product_cashback; } return $total_cashback; } /** * Sort category IDs by hierarchy (parents first, then children) * This ensures that child category settings will override parent category settings * * @param array $category_ids Array of category term IDs * @return array Sorted array of category term IDs */ private function get_sorted_categories($category_ids) { $sorted = array(); $categories = array(); // Get all category objects foreach ($category_ids as $term_id) { $term = get_term($term_id, 'product_cat'); if ($term && !is_wp_error($term)) { $categories[$term_id] = $term; } } // Build a hierarchy map $hierarchy = array(); foreach ($categories as $term_id => $term) { $parent_id = $term->parent; if (!isset($hierarchy[$parent_id])) { $hierarchy[$parent_id] = array(); } $hierarchy[$parent_id][] = $term_id; } // Helper function to build sorted list $process_level = function($parent_id) use (&$process_level, &$sorted, $hierarchy) { if (isset($hierarchy[$parent_id])) { foreach ($hierarchy[$parent_id] as $child_id) { $sorted[] = $child_id; $process_level($child_id); } } }; // Start with top-level categories (parent = 0) $process_level(0); // Include any categories not processed (possibly orphaned) foreach ($category_ids as $term_id) { if (!in_array($term_id, $sorted)) { $sorted[] = $term_id; } } return $sorted; } }