mirror of
https://gitlab.com/ArkHost/WP-WHMCS-Pricing.git
synced 2026-09-19 17:37:31 +02:00
325 lines
10 KiB
PHP
325 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* WHMCS API Handler
|
|
*
|
|
* Handles all communication with WHMCS API
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class WHMCS_Pricing_API {
|
|
|
|
/**
|
|
* Get settings
|
|
*/
|
|
private static function get_settings() {
|
|
$defaults = array(
|
|
'api_url' => '',
|
|
'api_identifier' => '',
|
|
'api_secret' => '',
|
|
'cache_duration' => 3600,
|
|
'default_currency' => 'USD',
|
|
'show_decimals' => true,
|
|
'decimal_separator' => '.',
|
|
'hide_currency_symbol' => false,
|
|
'hide_billing_cycle' => false,
|
|
'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();
|
|
|
|
// Get decimal separator from settings (default to '.')
|
|
$decimal_sep = isset($settings['decimal_separator']) ? $settings['decimal_separator'] : '.';
|
|
$thousands_sep = ($decimal_sep === '.') ? ',' : '.';
|
|
|
|
// Apply decimal formatting
|
|
if ($format === 'no_decimals' || !$settings['show_decimals']) {
|
|
$price = round($price);
|
|
$formatted_price = number_format($price, 0, $decimal_sep, $thousands_sep);
|
|
} else {
|
|
$formatted_price = number_format($price, 2, $decimal_sep, $thousands_sep);
|
|
}
|
|
|
|
// 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',
|
|
'ARS' => '$'
|
|
);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|