'', 'api_identifier' => '', 'api_secret' => '', 'cache_duration' => 3600, 'default_currency' => 'USD', 'show_decimals' => true, 'debug_mode' => false ); $settings = get_option('whmcs_pricing_settings', $defaults); return wp_parse_args($settings, $defaults); } /** * Make API request to WHMCS */ private static function make_request($action, $params = array()) { $settings = self::get_settings(); // Validate settings if (empty($settings['api_url']) || empty($settings['api_identifier']) || empty($settings['api_secret'])) { self::log_error('API credentials not configured'); return new WP_Error('missing_credentials', __('WHMCS API credentials not configured', 'whmcs-pricing')); } // Prepare API request $api_url = rtrim($settings['api_url'], '/') . '/includes/api.php'; $post_data = array_merge(array( 'action' => $action, 'identifier' => $settings['api_identifier'], 'secret' => $settings['api_secret'], 'responsetype' => 'json' ), $params); // Make request $response = wp_remote_post($api_url, array( 'timeout' => 15, 'body' => $post_data, 'sslverify' => true )); // Check for errors if (is_wp_error($response)) { self::log_error('API request failed: ' . $response->get_error_message()); return $response; } // Parse response $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { self::log_error('Invalid JSON response from WHMCS API'); return new WP_Error('invalid_response', __('Invalid response from WHMCS API', 'whmcs-pricing')); } // Check API result if (isset($data['result']) && $data['result'] === 'error') { $error_message = isset($data['message']) ? $data['message'] : __('Unknown API error', 'whmcs-pricing'); self::log_error('WHMCS API error: ' . $error_message); return new WP_Error('api_error', $error_message); } return $data; } /** * Get product details */ public static function get_product($product_id) { $cache_key = 'whmcs_product_' . $product_id; $cached_data = get_transient($cache_key); if (false !== $cached_data && !self::is_debug_mode()) { return $cached_data; } // Make API request $response = self::make_request('GetProducts', array( 'pid' => $product_id )); if (is_wp_error($response)) { return $response; } // Extract product data if (!isset($response['products']) || !isset($response['products']['product'])) { self::log_error('Product not found: ' . $product_id); return new WP_Error('product_not_found', __('Product not found', 'whmcs-pricing')); } $product_data = is_array($response['products']['product']) && isset($response['products']['product'][0]) ? $response['products']['product'][0] : $response['products']['product']; // Debug log the product data structure if (self::is_debug_mode()) { self::log_error('Product data for ID ' . $product_id . ': ' . print_r($product_data, true)); } // Cache the result $settings = self::get_settings(); set_transient($cache_key, $product_data, absint($settings['cache_duration'])); return $product_data; } /** * Get product price */ public static function get_price($product_id, $args = array()) { $defaults = array( 'billing_cycle' => 'monthly', 'currency' => null, 'format' => 'default' ); $args = wp_parse_args($args, $defaults); $settings = self::get_settings(); // Use default currency if not specified if (empty($args['currency'])) { $args['currency'] = $settings['default_currency']; } // Get product data $product = self::get_product($product_id); if (is_wp_error($product)) { return $product; } // Find pricing for the requested currency and billing cycle $price = self::extract_price($product, $args['billing_cycle'], $args['currency']); if (is_wp_error($price)) { return $price; } // Format the price return self::format_price($price, $args['currency'], $args['format']); } /** * Extract price from product data */ private static function extract_price($product, $billing_cycle, $currency) { // Normalize billing cycle $cycle_map = array( 'monthly' => 'monthly', 'quarterly' => 'quarterly', 'semiannually' => 'semiannually', 'annually' => 'annually', 'biennially' => 'biennially', 'triennially' => 'triennially', 'month' => 'monthly', 'quarter' => 'quarterly', 'year' => 'annually', 'annual' => 'annually' ); $billing_cycle = strtolower($billing_cycle); $billing_cycle = isset($cycle_map[$billing_cycle]) ? $cycle_map[$billing_cycle] : $billing_cycle; // Debug pricing structure if (self::is_debug_mode()) { self::log_error("Looking for price: currency={$currency}, cycle={$billing_cycle}"); if (isset($product['pricing'])) { self::log_error('Available pricing: ' . print_r($product['pricing'], true)); } } // Look for pricing in product data if (isset($product['pricing'][$currency][$billing_cycle])) { $price = floatval($product['pricing'][$currency][$billing_cycle]); // WHMCS uses -1.00 to indicate billing cycle not available if ($price < 0) { if (self::is_debug_mode()) { self::log_error("Price is -1 (not available) for cycle: {$billing_cycle}"); } return new WP_Error('price_not_available', __('This billing cycle is not available for this product', 'whmcs-pricing')); } if (self::is_debug_mode()) { self::log_error("Found price: {$price}"); } return $price; } // Try to find any available pricing and log it if (self::is_debug_mode() && isset($product['pricing'])) { self::log_error("Available currencies: " . implode(', ', array_keys($product['pricing']))); if (isset($product['pricing'][$currency])) { self::log_error("Available cycles for {$currency}: " . implode(', ', array_keys($product['pricing'][$currency]))); } } self::log_error("Price not found for product {$product['pid']}, cycle: {$billing_cycle}, currency: {$currency}"); return new WP_Error('price_not_found', __('Price not available for selected options', 'whmcs-pricing')); } /** * Format price */ private static function format_price($price, $currency, $format) { $settings = self::get_settings(); // Apply decimal formatting if ($format === 'no_decimals' || !$settings['show_decimals']) { $price = round($price); $formatted_price = number_format($price, 0); } else { $formatted_price = number_format($price, 2); } // Get currency symbol $currency_symbol = self::get_currency_symbol($currency); return array( 'raw' => $price, 'formatted' => $formatted_price, 'currency' => $currency, 'symbol' => $currency_symbol ); } /** * Get currency symbol */ private static function get_currency_symbol($currency) { $symbols = array( 'USD' => '$', 'EUR' => '€', 'GBP' => '£', 'AUD' => 'A$', 'CAD' => 'C$', 'JPY' => '¥', 'CNY' => '¥', 'INR' => '₹', 'BRL' => 'R$', 'RUB' => '₽', 'CHF' => 'Fr', 'SEK' => 'kr', 'NOK' => 'kr', 'DKK' => 'kr', 'PLN' => 'zł', 'THB' => '฿', 'MYR' => 'RM', 'SGD' => 'S$', 'NZD' => 'NZ$', 'MXN' => 'Mex$', 'ZAR' => 'R' ); return isset($symbols[$currency]) ? $symbols[$currency] : $currency; } /** * Clear cache for specific product */ public static function clear_cache($product_id) { delete_transient('whmcs_product_' . $product_id); } /** * Clear all cached products */ public static function clear_all_cache() { global $wpdb; $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_whmcs_product_%' OR option_name LIKE '_transient_timeout_whmcs_product_%'" ); } /** * Check if debug mode is enabled */ private static function is_debug_mode() { $settings = self::get_settings(); return !empty($settings['debug_mode']); } /** * Log error message */ private static function log_error($message) { if (self::is_debug_mode() && function_exists('error_log')) { error_log('[WHMCS Pricing] ' . $message); } } }