From a9e84a855e5d487357f14f4feaf047950e3b4951 Mon Sep 17 00:00:00 2001 From: Pinga <121483313+getpinga@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:36:46 +0200 Subject: [PATCH] Per registrar currency now fully available --- automation/auto-approve-transfer.php | 12 +- automation/composer.json | 3 +- automation/domain-lifecycle-manager.php | 12 +- automation/helpers.php | 238 +++++++++++---- cp/app/Controllers/ApplicationsController.php | 10 +- cp/app/Controllers/DomainsController.php | 59 ++-- cp/bootstrap/app.php | 1 - cp/bootstrap/helper.php | 206 +++++++++++-- cp/composer.json | 3 +- cp/resources/exchange_rates.json | 8 + docs/install.sh | 2 +- docs/update1015.sh | 2 + epp/composer.json | 3 +- epp/src/epp-check.php | 15 +- epp/src/epp-create.php | 5 +- epp/src/epp-delete.php | 17 +- epp/src/epp-renew.php | 7 +- epp/src/epp-transfer.php | 12 +- epp/src/epp-update.php | 28 +- epp/src/helpers.php | 281 ++++++++++++++---- 20 files changed, 681 insertions(+), 243 deletions(-) create mode 100644 cp/resources/exchange_rates.json diff --git a/automation/auto-approve-transfer.php b/automation/auto-approve-transfer.php index dc20aad..698ab2f 100644 --- a/automation/auto-approve-transfer.php +++ b/automation/auto-approve-transfer.php @@ -32,7 +32,15 @@ try { $price = 0; $domain_id = $id; - [$registrar_balance, $creditLimit] = $dbh->query("SELECT accountBalance,creditLimit FROM registrar WHERE id = '$reid' LIMIT 1")->fetch(PDO::FETCH_NUM); + $stmt = $dbh->prepare(" + SELECT accountBalance, creditLimit, currency + FROM registrar + WHERE id = ? + LIMIT 1 + "); + $stmt->execute([$reid]); + + [$registrar_balance, $creditLimit, $currency] = $stmt->fetch(PDO::FETCH_NUM); if ($transfer_exdate) { [$date_add] = $dbh->query("SELECT PERIOD_DIFF(DATE_FORMAT(transfer_exdate, '%Y%m'), DATE_FORMAT(exdate, '%Y%m')) AS intval FROM domain WHERE name = '$name' LIMIT 1")->fetch(PDO::FETCH_NUM); @@ -52,7 +60,7 @@ try { } } - $returnValue = getDomainPrice($dbh, $name, $tld_id, $date_add, 'transfer', $reid); + $returnValue = getDomainPrice($dbh, $name, $tld_id, $date_add, 'transfer', $reid, $currency); $price = $returnValue['price']; if (($registrar_balance + $creditLimit) < $price) { diff --git a/automation/composer.json b/automation/composer.json index b766db8..98ea26b 100644 --- a/automation/composer.json +++ b/automation/composer.json @@ -12,6 +12,7 @@ "guzzlehttp/guzzle": "^7.9", "league/flysystem-ftp": "^3.29", "phpmailer/phpmailer": "^6.9", - "league/plates": "^3.6" + "league/plates": "^3.6", + "moneyphp/money": "^4.6" } } diff --git a/automation/domain-lifecycle-manager.php b/automation/domain-lifecycle-manager.php index c2d042f..c369e3c 100644 --- a/automation/domain-lifecycle-manager.php +++ b/automation/domain-lifecycle-manager.php @@ -128,18 +128,22 @@ class DomainLifecycleManager { } private function handleAutoRenewal($domain_id, $name, $tldid, $exdate, $clid) { - // Get registrar balance and credit limit + // Get registrar balance, credit limit, and currency $sthRegistrar = $this->dbh->prepare(" - SELECT accountBalance, creditLimit + SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = ? LIMIT 1 "); $sthRegistrar->execute([$clid]); - list($registrar_balance, $creditLimit) = $sthRegistrar->fetch(PDO::FETCH_NUM); + $registrar = $sthRegistrar->fetch(PDO::FETCH_ASSOC); + + $registrar_balance = $registrar['accountBalance']; + $creditLimit = $registrar['creditLimit']; + $currency = $registrar['currency']; // Get domain price - $returnValue = getDomainPrice($this->dbh, $name, $tldid, 12, 'renew', $clid); + $returnValue = getDomainPrice($this->dbh, $name, $tldid, 12, 'renew', $clid, $currency); $price = $returnValue['price']; if (($registrar_balance + $creditLimit) > $price) { diff --git a/automation/helpers.php b/automation/helpers.php index 051e093..382187a 100644 --- a/automation/helpers.php +++ b/automation/helpers.php @@ -9,6 +9,11 @@ use Monolog\Formatter\LineFormatter; use Ds\Map; use Swoole\Coroutine; use Swoole\Coroutine\Http\Client; +use Money\Money; +use Money\Currency; +use Money\Converter; +use Money\Currencies\ISOCurrencies; +use Money\Exchange\FixedExchange; /** * Sets up and returns a Logger instance. @@ -132,76 +137,191 @@ function processAbuseDetection($pdo, $domain, $clid, $abuseType, $evidenceLink, } } -function getDomainPrice($pdo, $domain_name, $tld_id, $date_add = 12, $command = 'create', $registrar_id = null) { - // Check if the domain is a premium domain - $stmt = $pdo->prepare(" - SELECT c.category_price - FROM premium_domain_pricing p - JOIN premium_domain_categories c ON p.category_id = c.category_id - WHERE p.domain_name = ? AND p.tld_id = ? - "); - $stmt->execute([$domain_name, $tld_id]); - if ($stmt->rowCount() > 0) { - return ['type' => 'premium', 'price' => $stmt->fetch()['category_price']]; +function getDomainPrice($pdo, $domain_name, $tld_id, $date_add = 12, $command = 'create', $registrar_id = null, $currency = 'USD') { + $cacheKey = "domain_price_{$domain_name}_{$tld_id}_{$date_add}_{$command}_{$registrar_id}_{$currency}"; + + // Try fetching from cache + if (function_exists('apcu_fetch')) { + $cached = apcu_fetch($cacheKey); + if ($cached !== false) { + return $cached; + } } - // Check if there is a promotion for the domain + $exchangeRates = getExchangeRates(); + $baseCurrency = $exchangeRates['base_currency'] ?? 'USD'; + $exchangeRate = $exchangeRates['rates'][$currency] ?? 1.0; + + // Check for premium pricing + $premiumPrice = apcu_fetch("premium_price_{$domain_name}_{$tld_id}") ?: fetchSingleValue( + $pdo, + 'SELECT c.category_price + FROM premium_domain_pricing p + JOIN premium_domain_categories c ON p.category_id = c.category_id + WHERE p.domain_name = ? AND p.tld_id = ?', + [$domain_name, $tld_id] + ); + + if (!is_null($premiumPrice) && $premiumPrice !== false) { + $money = convertMoney(new Money((int) ($premiumPrice * 100), new Currency($baseCurrency)), $exchangeRate, $currency); + $result = ['type' => 'premium', 'price' => formatMoney($money)]; + + apcu_store($cacheKey, $result, 1800); + return $result; + } + + // Check for active promotions $currentDate = date('Y-m-d'); - $stmt = $pdo->prepare(" - SELECT discount_percentage, discount_amount - FROM promotion_pricing - WHERE tld_id = ? - AND promo_type = 'full' - AND status = 'active' - AND start_date <= ? - AND end_date >= ? - "); - $stmt->execute([$tld_id, $currentDate, $currentDate]); - if ($stmt->rowCount() > 0) { - $promo = $stmt->fetch(); - $discount = null; - - // Determine discount based on percentage or amount - if (!empty($promo['discount_percentage'])) { - $discount = $promo['discount_percentage']; // Percentage discount - } elseif (!empty($promo['discount_amount'])) { - $discount = $promo['discount_amount']; // Fixed amount discount - } - } else { - $discount = null; + $promo = apcu_fetch("promo_{$tld_id}") ?: fetchSingleRow( + $pdo, + "SELECT discount_percentage, discount_amount + FROM promotion_pricing + WHERE tld_id = ? + AND promo_type = 'full' + AND status = 'active' + AND start_date <= ? + AND end_date >= ?", + [$tld_id, $currentDate, $currentDate] + ); + + if ($promo) { + apcu_store("promo_{$tld_id}", $promo, 3600); } - // Get regular price for the specified period - $priceColumn = "m" . $date_add; - $sql = " - SELECT $priceColumn - FROM domain_price - WHERE tldid = ? - AND command = ? - AND (registrar_id = ? OR registrar_id IS NULL) - ORDER BY registrar_id DESC - LIMIT 1 - "; - $stmt = $pdo->prepare($sql); - $stmt->execute([$tld_id, $command, $registrar_id]); - - if ($stmt->rowCount() > 0) { - $regularPrice = $stmt->fetch()[$priceColumn]; + // Get regular price from DB + $priceColumn = "m" . (int) $date_add; + $regularPrice = apcu_fetch("regular_price_{$tld_id}_{$command}_{$registrar_id}") ?: fetchSingleValue( + $pdo, + "SELECT $priceColumn + FROM domain_price + WHERE tldid = ? AND command = ? + AND (registrar_id = ? OR registrar_id IS NULL) + ORDER BY registrar_id DESC LIMIT 1", + [$tld_id, $command, $registrar_id] + ); - if ($discount !== null) { - if (isset($promo['discount_percentage'])) { - $discountAmount = $regularPrice * ($promo['discount_percentage'] / 100); + if (!is_null($regularPrice) && $regularPrice !== false) { + apcu_store("regular_price_{$tld_id}_{$command}_{$registrar_id}", $regularPrice, 1800); + + $finalPrice = $regularPrice * 100; // Convert DB float to cents + if ($promo) { + if (!empty($promo['discount_percentage'])) { + $discountAmount = (int) ($finalPrice * ($promo['discount_percentage'] / 100)); } else { - $discountAmount = $discount; + $discountAmount = (int) ($promo['discount_amount'] * 100); } - $price = $regularPrice - $discountAmount; - return ['type' => 'promotion', 'price' => $price]; + $finalPrice = max(0, $finalPrice - $discountAmount); + $type = 'promotion'; + } else { + $type = 'regular'; } - - return ['type' => 'regular', 'price' => $regularPrice]; + + $money = convertMoney(new Money($finalPrice, new Currency($baseCurrency)), $exchangeRate, $currency); + $result = ['type' => $type, 'price' => formatMoney($money)]; + + apcu_store($cacheKey, $result, 1800); + return $result; } - return ['type' => 'not_found', 'price' => 0]; + return ['type' => 'not_found', 'price' => formatMoney(new Money(0, new Currency($currency)))]; +} + +/** + * Load exchange rates from JSON file with APCu caching. + */ +function getExchangeRates() { + $cacheKey = 'exchange_rates'; + + if (function_exists('apcu_fetch')) { + $cached = apcu_fetch($cacheKey); + if ($cached !== false) { + return $cached; + } + } + + $filePath = "/var/www/cp/resources/exchange_rates.json"; + $defaultRates = [ + 'base_currency' => 'USD', + 'rates' => [ + 'USD' => 1.0 // Ensure USD always exists + ], + 'last_updated' => date('c') // ISO 8601 timestamp + ]; + + if (!file_exists($filePath) || !is_readable($filePath)) { + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $defaultRates, 3600); + } + return $defaultRates; + } + + $json = file_get_contents($filePath); + $data = json_decode($json, true); + + if (!isset($data['base_currency'], $data['rates']) || !is_array($data['rates'])) { + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $defaultRates, 3600); + } + return $defaultRates; + } + + // Ensure base currency exists + if (!isset($data['rates'][$data['base_currency']])) { + $data['rates'][$data['base_currency']] = 1.0; + } + + // Ensure every currency defaults to 1.0 if missing + foreach ($data['rates'] as $currency => $rate) { + if (!is_numeric($rate)) { + $data['rates'][$currency] = 1.0; + } + } + + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $data, 3600); + } + + return $data; +} + +/** + * Convert MoneyPHP object to the target currency. + */ +function convertMoney(Money $amount, float $exchangeRate, string $currency) { + $currencies = new ISOCurrencies(); + $exchange = new FixedExchange([ + $amount->getCurrency()->getCode() => [ + $currency => (string) $exchangeRate // Convert float to string + ] + ]); + $converter = new Converter($currencies, $exchange); + + return $converter->convert($amount, new Currency($currency)); +} + +/** + * Format Money object back to a string (e.g., "10.00"). + */ +function formatMoney(Money $money) { + return number_format($money->getAmount() / 100, 2, '.', ''); +} + +/** + * Fetch a single value from the database using PDO. + */ +function fetchSingleValue($pdo, string $query, array $params) { + $stmt = $pdo->prepare($query); + $stmt->execute($params); + return $stmt->fetchColumn(); +} + +/** + * Fetch a single row from the database using PDO. + */ +function fetchSingleRow($pdo, string $query, array $params) { + $stmt = $pdo->prepare($query); + $stmt->execute($params); + return $stmt->fetch(PDO::FETCH_ASSOC); } function generateAuthInfo(): string { diff --git a/cp/app/Controllers/ApplicationsController.php b/cp/app/Controllers/ApplicationsController.php index eb75f1e..e0cbaa6 100644 --- a/cp/app/Controllers/ApplicationsController.php +++ b/cp/app/Controllers/ApplicationsController.php @@ -216,12 +216,13 @@ class ApplicationsController extends Controller $date_add = 0; - $result = $db->selectRow('SELECT accountBalance, creditLimit FROM registrar WHERE id = ?', [$clid]); + $result = $db->selectRow('SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = ?', [$clid]); $registrar_balance = $result['accountBalance']; $creditLimit = $result['creditLimit']; + $currency = $result['currency']; - $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'create', $clid); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'create', $clid, $currency); $price = $returnValue['price']; if (!$price) { @@ -1073,14 +1074,15 @@ class ApplicationsController extends Controller $clid = $registrar_id_domain; } - $result = $db->selectRow('SELECT accountBalance, creditLimit FROM registrar WHERE id = ?', [$clid]); + $result = $db->selectRow('SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = ?', [$clid]); $registrar_balance = $result['accountBalance']; $creditLimit = $result['creditLimit']; + $currency = $result['currency']; $date_add = 12; - $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'create', $clid); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'create', $clid, $currency); $price = $returnValue['price']; try { diff --git a/cp/app/Controllers/DomainsController.php b/cp/app/Controllers/DomainsController.php index f804dfb..7d8abcb 100644 --- a/cp/app/Controllers/DomainsController.php +++ b/cp/app/Controllers/DomainsController.php @@ -357,12 +357,13 @@ class DomainsController extends Controller $clid = $registrar_id; } - $result = $db->selectRow('SELECT accountBalance, creditLimit FROM registrar WHERE id = ?', [$clid]); + $result = $db->selectRow('SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = ?', [$clid]); $registrar_balance = $result['accountBalance']; $creditLimit = $result['creditLimit']; + $currency = $result['currency']; - $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'create', $clid); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'create', $clid, $currency); $price = $returnValue['price']; if (!$price) { @@ -1768,12 +1769,13 @@ class DomainsController extends Controller $date_add = 0; $date_add = ($renewalYears * 12); - $result = $db->selectRow('SELECT accountBalance, creditLimit FROM registrar WHERE id = ?', [$clid]); + $result = $db->selectRow('SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = ?', [$clid]); $registrar_balance = $result['accountBalance']; $creditLimit = $result['creditLimit']; + $currency = $result['currency']; - $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'renew', $clid); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'renew', $clid, $currency); $price = $returnValue['price']; if (!$price) { @@ -2088,7 +2090,8 @@ class DomainsController extends Controller ] ); if ($addPeriod_id) { - $returnValue = getDomainPrice($db, $domainName, $tld_id, $addPeriod, 'create', $clid); + $currency = $db->selectValue('SELECT currency FROM registrar WHERE id = ?', [$clid]); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $addPeriod, 'create', $clid, $currency); $price = $returnValue['price']; if (!$price) { @@ -2215,7 +2218,8 @@ class DomainsController extends Controller ] ); if ($autoRenewPeriod_id) { - $returnValue = getDomainPrice($db, $domainName, $tld_id, $autoRenewPeriod, 'renew', $clid); + $currency = $db->selectValue('SELECT currency FROM registrar WHERE id = ?', [$clid]); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $autoRenewPeriod, 'renew', $clid, $currency); $price = $returnValue['price']; if (!$price) { @@ -2242,7 +2246,8 @@ class DomainsController extends Controller ] ); if ($renewPeriod_id) { - $returnValue = getDomainPrice($db, $domainName, $tld_id, $renewPeriod, 'renew', $clid); + $currency = $db->selectValue('SELECT currency FROM registrar WHERE id = ?', [$clid]); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $renewPeriod, 'renew', $clid, $currency); $price = $returnValue['price']; if (!$price) { @@ -2269,7 +2274,8 @@ class DomainsController extends Controller ] ); if ($transferPeriod_id) { - $returnValue = getDomainPrice($db, $domainName, $tld_id, $transferPeriod, 'renew', $clid); + $currency = $db->selectValue('SELECT currency FROM registrar WHERE id = ?', [$clid]); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $transferPeriod, 'renew', $clid, $currency); $price = $returnValue['price']; if (!$price) { @@ -2477,11 +2483,12 @@ class DomainsController extends Controller $date_add = $transferYears * 12; if ($date_add > 0) { - $result = $db->selectRow('SELECT accountBalance, creditLimit FROM registrar WHERE id = ?', [$clid]); + $result = $db->selectRow('SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = ?', [$clid]); $registrar_balance = $result['accountBalance']; $creditLimit = $result['creditLimit']; + $currency = $result['currency']; - $returnValue = getDomainPrice($db, $domainName, $tldid, $date_add, 'transfer', $clid); + $returnValue = getDomainPrice($db, $domainName, $tldid, $date_add, 'transfer', $clid, $currency); $price = $returnValue['price']; if (!$price) { @@ -2750,9 +2757,10 @@ class DomainsController extends Controller $date_add = 0; $price = 0; - $result = $db->selectRow('SELECT accountBalance, creditLimit FROM registrar WHERE id = ?', [$reid]); + $result = $db->selectRow('SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = ?', [$reid]); $registrar_balance = $result['accountBalance']; $creditLimit = $result['creditLimit']; + $currency = $result['currency']; if ($transfer_exdate) { $date_add = $db->selectValue( @@ -2762,7 +2770,7 @@ class DomainsController extends Controller ] ); - $returnValue = getDomainPrice($db, $domainName, $tldid, $date_add, 'transfer', $clid); + $returnValue = getDomainPrice($db, $domainName, $tldid, $date_add, 'transfer', $clid, $currency); $price = $returnValue['price']; if (($registrar_balance + $creditLimit) < $price) { @@ -3289,32 +3297,17 @@ class DomainsController extends Controller $domain = $db->selectRow('SELECT tldid, exdate FROM domain WHERE name = ? LIMIT 1', [ $domainName ]); $tldid = $domain['tldid']; - - $result = $db->selectRow('SELECT accountBalance, creditLimit FROM registrar WHERE id = ?', [$clid]); + + $result = $db->selectRow('SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = ?', [$clid]); $registrar_balance = $result['accountBalance']; $creditLimit = $result['creditLimit']; + $currency = $result['currency']; - $renew_price = $db->selectValue( - "SELECT m12 - FROM domain_price - WHERE tldid = ? - AND command = ? - AND (registrar_id = ? OR registrar_id IS NULL) - ORDER BY registrar_id DESC - LIMIT 1", - [$tldid, 'renew', $clid] - ); + $returnValue = getDomainPrice($db, $domainName, $tldid, 12, 'renew', $clid, $currency); + $renew_price = $returnValue['price']; - $restore_price = $db->selectValue( - "SELECT price - FROM domain_restore_price - WHERE tldid = ? - AND (registrar_id = ? OR registrar_id IS NULL) - ORDER BY registrar_id DESC - LIMIT 1", - [$tldid, $clid] - ); + $restore_price = getDomainRestorePrice($db, $tldid, $clid, $currency); if (($registrar_balance + $creditLimit) < ($renew_price + $restore_price)) { $this->container->get('flash')->addMessage('error', 'There is no money on the account for restore and renew'); diff --git a/cp/bootstrap/app.php b/cp/bootstrap/app.php index 46b0182..6325d11 100644 --- a/cp/bootstrap/app.php +++ b/cp/bootstrap/app.php @@ -208,7 +208,6 @@ $container->set('view', function ($container) { // Make it accessible in templates $view->getEnvironment()->addGlobal('currency', $currency); - $view->getEnvironment()->addGlobal('registry_currency', $_SESSION['registry_currency']); // Check if the user is impersonated from the admin, otherwise default to false $isAdminImpersonation = isset($_SESSION['impersonator']) ? $_SESSION['impersonator'] : false; diff --git a/cp/bootstrap/helper.php b/cp/bootstrap/helper.php index 0b19e1c..caa52c7 100644 --- a/cp/bootstrap/helper.php +++ b/cp/bootstrap/helper.php @@ -29,6 +29,12 @@ use libphonenumber\PhoneNumberUtil; use libphonenumber\PhoneNumberFormat; use libphonenumber\NumberParseException; use ZxcvbnPhp\Zxcvbn; +use Money\Money; +use Money\Currencies\ISOCurrencies; +use Money\Currency; +use Money\Exchange\FixedExchange; +use Money\Converter; +use Money\CurrencyPair; /** * @return mixed|string|string[] @@ -435,9 +441,23 @@ function extractDomainAndTLD($urlString) { return ['domain' => $sld, 'tld' => $tld]; } -function getDomainPrice($db, $domain_name, $tld_id, $date_add = 12, $command = 'create', $registrar_id = null) { - // Check if the domain is a premium domain - $premiumDomain = $db->selectRow( +function getDomainPrice($db, $domain_name, $tld_id, $date_add = 12, $command = 'create', $registrar_id = null, $currency = 'USD') { + $cacheKey = "domain_price_{$domain_name}_{$tld_id}_{$date_add}_{$command}_{$registrar_id}_{$currency}"; + + // Try fetching from cache + if (function_exists('apcu_fetch')) { + $cached = apcu_fetch($cacheKey); + if ($cached !== false) { + return $cached; + } + } + + $exchangeRates = getExchangeRates(); + $baseCurrency = $exchangeRates['base_currency'] ?? 'USD'; + $exchangeRate = $exchangeRates['rates'][$currency] ?? 1.0; + + // Check for premium pricing + $premiumPrice = apcu_fetch("premium_price_{$domain_name}_{$tld_id}") ?: $db->selectValue( 'SELECT c.category_price FROM premium_domain_pricing p JOIN premium_domain_categories c ON p.category_id = c.category_id @@ -445,13 +465,17 @@ function getDomainPrice($db, $domain_name, $tld_id, $date_add = 12, $command = ' [$domain_name, $tld_id] ); - if ($premiumDomain) { - return ['type' => 'premium', 'price' => $premiumDomain['category_price']]; + if (!is_null($premiumPrice) && $premiumPrice !== false) { + $money = convertMoney(new Money((int) ($premiumPrice * 100), new Currency($baseCurrency)), $exchangeRate, $currency); + $result = ['type' => 'premium', 'price' => formatMoney($money)]; + + apcu_store($cacheKey, $result, 1800); + return $result; } - // Check if there is a promotion for the domain + // Check for active promotions $currentDate = date('Y-m-d'); - $promo = $db->selectRow( + $promo = apcu_fetch("promo_{$tld_id}") ?: $db->selectRow( "SELECT discount_percentage, discount_amount FROM promotion_pricing WHERE tld_id = ? @@ -462,37 +486,167 @@ function getDomainPrice($db, $domain_name, $tld_id, $date_add = 12, $command = ' [$tld_id, $currentDate, $currentDate] ); - $discount = null; if ($promo) { - if (!empty($promo['discount_percentage'])) { - $discount = $promo['discount_percentage']; // Percentage discount - } elseif (!empty($promo['discount_amount'])) { - $discount = $promo['discount_amount']; // Fixed amount discount - } + apcu_store("promo_{$tld_id}", $promo, 3600); } - // Get regular price for the specified period - $priceColumn = "m" . $date_add; - $regularPrice = $db->selectValue( - "SELECT $priceColumn FROM domain_price WHERE tldid = ? AND command = ? AND (registrar_id = ? OR registrar_id IS NULL) ORDER BY registrar_id DESC LIMIT 1", + // Get regular price from DB + $priceColumn = "m" . (int) $date_add; + $regularPrice = apcu_fetch("regular_price_{$tld_id}_{$command}_{$registrar_id}") ?: $db->selectValue( + "SELECT $priceColumn + FROM domain_price + WHERE tldid = ? AND command = ? + AND (registrar_id = ? OR registrar_id IS NULL) + ORDER BY registrar_id DESC LIMIT 1", [$tld_id, $command, $registrar_id] ); - if ($regularPrice !== false) { - if ($discount !== null) { - if (isset($promo['discount_percentage'])) { - $discountAmount = $regularPrice * ($promo['discount_percentage'] / 100); + if (!is_null($regularPrice) && $regularPrice !== false) { + apcu_store("regular_price_{$tld_id}_{$command}_{$registrar_id}", $regularPrice, 1800); + + $finalPrice = $regularPrice * 100; // Convert DB float to cents + if ($promo) { + if (!empty($promo['discount_percentage'])) { + $discountAmount = (int) ($finalPrice * ($promo['discount_percentage'] / 100)); } else { - $discountAmount = $discount; + $discountAmount = (int) ($promo['discount_amount'] * 100); } - $price = $regularPrice - $discountAmount; - return ['type' => 'promotion', 'price' => $price]; + $finalPrice = max(0, $finalPrice - $discountAmount); + $type = 'promotion'; + } else { + $type = 'regular'; } - return ['type' => 'regular', 'price' => $regularPrice]; + $money = convertMoney(new Money($finalPrice, new Currency($baseCurrency)), $exchangeRate, $currency); + $result = ['type' => $type, 'price' => formatMoney($money)]; + + apcu_store($cacheKey, $result, 1800); + return $result; } - return ['type' => 'not_found', 'price' => 0]; + return ['type' => 'not_found', 'price' => formatMoney(new Money(0, new Currency($currency)))]; +} + +function getDomainRestorePrice($db, $tld_id, $registrar_id = null, $currency = 'USD') { + $cacheKey = "domain_restore_price_{$tld_id}_{$registrar_id}_{$currency}"; + + // Try fetching from cache + if (function_exists('apcu_fetch')) { + $cached = apcu_fetch($cacheKey); + if ($cached !== false) { + return $cached; + } + } + + // Fetch exchange rates + $exchangeRates = getExchangeRates(); + $baseCurrency = $exchangeRates['base_currency'] ?? 'USD'; + $exchangeRate = $exchangeRates['rates'][$currency] ?? 1.0; + + // Fetch restore price from DB + $restorePrice = $db->selectValue( + "SELECT price + FROM domain_restore_price + WHERE tldid = ? + AND (registrar_id = ? OR registrar_id IS NULL) + ORDER BY registrar_id DESC + LIMIT 1", + [$tld_id, $registrar_id] + ); + + // If no restore price is found, return 0.00 + if (is_null($restorePrice) || $restorePrice === false) { + return '0.00'; + } + + // Convert to Money object for precision + $money = convertMoney(new Money((int) ($restorePrice * 100), new Currency($baseCurrency)), $exchangeRate, $currency); + + // Format and cache the result + $formattedPrice = formatMoney($money); + apcu_store($cacheKey, $formattedPrice, 1800); // Cache for 30 minutes + + return $formattedPrice; +} + +/** + * Load exchange rates from JSON file with APCu caching. + */ +function getExchangeRates() { + $cacheKey = 'exchange_rates'; + + if (function_exists('apcu_fetch')) { + $cached = apcu_fetch($cacheKey); + if ($cached !== false) { + return $cached; + } + } + + $filePath = "/var/www/cp/resources/exchange_rates.json"; + $defaultRates = [ + 'base_currency' => 'USD', + 'rates' => [ + 'USD' => 1.0 // Ensure USD always exists + ], + 'last_updated' => date('c') // ISO 8601 timestamp + ]; + + if (!file_exists($filePath) || !is_readable($filePath)) { + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $defaultRates, 3600); + } + return $defaultRates; + } + + $json = file_get_contents($filePath); + $data = json_decode($json, true); + + if (!isset($data['base_currency'], $data['rates']) || !is_array($data['rates'])) { + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $defaultRates, 3600); + } + return $defaultRates; + } + + // Ensure base currency exists + if (!isset($data['rates'][$data['base_currency']])) { + $data['rates'][$data['base_currency']] = 1.0; + } + + // Ensure every currency defaults to 1.0 if missing + foreach ($data['rates'] as $currency => $rate) { + if (!is_numeric($rate)) { + $data['rates'][$currency] = 1.0; + } + } + + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $data, 3600); + } + + return $data; +} + +/** + * Convert MoneyPHP object to the target currency. + */ +function convertMoney(Money $amount, float $exchangeRate, string $currency) { + $currencies = new ISOCurrencies(); + $exchange = new FixedExchange([ + $amount->getCurrency()->getCode() => [ + $currency => (string) $exchangeRate // Convert float to string + ] + ]); + $converter = new Converter($currencies, $exchange); + + return $converter->convert($amount, new Currency($currency)); +} + +/** + * Format Money object back to a string (e.g., "10.00"). + */ +function formatMoney(Money $money) { + return number_format($money->getAmount() / 100, 2, '.', ''); } function createUuidFromId($id) { diff --git a/cp/composer.json b/cp/composer.json index 7662728..fa68c92 100644 --- a/cp/composer.json +++ b/cp/composer.json @@ -47,7 +47,8 @@ "utopia-php/messaging": "^0.12.0", "brick/postcode": "^0.3.3", "utopia-php/system": "^0.9.0", - "bjeavons/zxcvbn-php": "^1.4" + "bjeavons/zxcvbn-php": "^1.4", + "moneyphp/money": "^4.6" }, "autoload": { "psr-4": { diff --git a/cp/resources/exchange_rates.json b/cp/resources/exchange_rates.json new file mode 100644 index 0000000..cdbe805 --- /dev/null +++ b/cp/resources/exchange_rates.json @@ -0,0 +1,8 @@ +{ + "base_currency": "USD", + "rates": { + "EUR": 0.91, + "GBP": 0.78 + }, + "last_updated": "2025-02-12T12:00:00Z" +} diff --git a/docs/install.sh b/docs/install.sh index c355674..c6fdf4d 100644 --- a/docs/install.sh +++ b/docs/install.sh @@ -117,7 +117,7 @@ if [[ ("$OS" == "Ubuntu" && "$VER" == "22.04") || ("$OS" == "Ubuntu" && "$VER" = apt update -y echo "Installing PHP and required extensions..." - apt install -y ${PHP_VERSION} ${PHP_VERSION}-cli ${PHP_VERSION}-common ${PHP_VERSION}-curl ${PHP_VERSION}-ds ${PHP_VERSION}-fpm ${PHP_VERSION}-gd ${PHP_VERSION}-gmp ${PHP_VERSION}-gnupg ${PHP_VERSION}-igbinary ${PHP_VERSION}-imap ${PHP_VERSION}-intl ${PHP_VERSION}-mbstring ${PHP_VERSION}-opcache ${PHP_VERSION}-readline ${PHP_VERSION}-redis ${PHP_VERSION}-soap ${PHP_VERSION}-swoole ${PHP_VERSION}-uuid ${PHP_VERSION}-xml + apt install -y ${PHP_VERSION} ${PHP_VERSION}-apcu ${PHP_VERSION}-bcmath ${PHP_VERSION}-cli ${PHP_VERSION}-common ${PHP_VERSION}-curl ${PHP_VERSION}-ds ${PHP_VERSION}-fpm ${PHP_VERSION}-gd ${PHP_VERSION}-gmp ${PHP_VERSION}-gnupg ${PHP_VERSION}-igbinary ${PHP_VERSION}-imap ${PHP_VERSION}-intl ${PHP_VERSION}-mbstring ${PHP_VERSION}-opcache ${PHP_VERSION}-readline ${PHP_VERSION}-redis ${PHP_VERSION}-soap ${PHP_VERSION}-swoole ${PHP_VERSION}-uuid ${PHP_VERSION}-xml # Set timezone to UTC if it's not already currentTimezone=$(timedatectl status | grep "Time zone" | awk '{print $3}') diff --git a/docs/update1015.sh b/docs/update1015.sh index 2af6fb1..8a27f01 100644 --- a/docs/update1015.sh +++ b/docs/update1015.sh @@ -64,6 +64,8 @@ systemctl stop msg_worker echo "Clearing cache..." php /var/www/cp/bin/clear_cache.php +apt install -y php8.3-apcu php8.3-bcmath + # Clone the new version of the repository echo "Cloning v1.0.15 from the repository..." git clone --branch v1.0.15 --single-branch https://github.com/getnamingo/registry /opt/registry1015 diff --git a/epp/composer.json b/epp/composer.json index 2a0aa07..4882902 100644 --- a/epp/composer.json +++ b/epp/composer.json @@ -6,6 +6,7 @@ "guzzlehttp/guzzle": "^7.9.2", "league/flysystem": "^3.28", "selective/xmldsig": "^3.1", - "namingo/rately": "^0.1.0" + "namingo/rately": "^0.1.0", + "moneyphp/money": "^4.6" } } diff --git a/epp/src/epp-check.php b/epp/src/epp-check.php index 5c1fff2..d75a38d 100644 --- a/epp/src/epp-check.php +++ b/epp/src/epp-check.php @@ -373,18 +373,9 @@ function processDomainCheck($conn, $db, $xml, $trans, $clid) { // Calculate or retrieve fee for this command $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, $commandName, $clid); $price = $returnValue['price']; - - $sth = $db->prepare(" - SELECT price - FROM domain_restore_price - WHERE tldid = ? - AND (registrar_id = ? OR registrar_id IS NULL) - ORDER BY registrar_id DESC - LIMIT 1 - "); - $sth->execute([$tld_id, $clid]); - $restore_price = $sth->fetchColumn(); - + + $restore_price = getDomainRestorePrice($db, $tld_id, $clid, $currency); + if ($commandName == 'restore') { $feeResponses[] = [ 'command' => $commandName, diff --git a/epp/src/epp-create.php b/epp/src/epp-create.php index c5b47b6..b4757d6 100644 --- a/epp/src/epp-create.php +++ b/epp/src/epp-create.php @@ -866,14 +866,15 @@ function processDomainCreate($conn, $db, $xml, $clid, $database_type, $trans, $m $clid = $stmt->fetch(PDO::FETCH_ASSOC); $clid = $clid['id']; - $stmt = $db->prepare("SELECT accountBalance, creditLimit FROM registrar WHERE id = :registrar_id LIMIT 1"); + $stmt = $db->prepare("SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = :registrar_id LIMIT 1"); $stmt->bindParam(':registrar_id', $clid, PDO::PARAM_INT); $stmt->execute(); $result = $stmt->fetch(PDO::FETCH_ASSOC); $registrar_balance = $result['accountBalance']; $creditLimit = $result['creditLimit']; + $currency = $result['currency']; - $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'create', $clid); + $returnValue = getDomainPrice($db, $domainName, $tld_id, $date_add, 'create', $clid, $currency); $price = $returnValue['price']; if (!$price) { diff --git a/epp/src/epp-delete.php b/epp/src/epp-delete.php index 63a065a..b735429 100644 --- a/epp/src/epp-delete.php +++ b/epp/src/epp-delete.php @@ -225,12 +225,13 @@ function processDomainDelete($conn, $db, $xml, $clid, $database_type, $trans) { $renewPeriod = $result['renewPeriod']; $renewedDate = $result['renewedDate']; $transferPeriod = $result['transferPeriod']; - - $stmt = $db->prepare("SELECT id FROM registrar WHERE clid = :clid LIMIT 1"); + + $stmt = $db->prepare("SELECT id, currency FROM registrar WHERE clid = :clid LIMIT 1"); $stmt->bindParam(':clid', $clid, PDO::PARAM_STR); $stmt->execute(); - $clid = $stmt->fetch(PDO::FETCH_ASSOC); - $clid = $clid['id']; + $result2 = $stmt->fetch(PDO::FETCH_ASSOC); + $clid = $result2['id']; + $currency = $result2['currency']; if ($clid != $registrar_id_domain) { sendEppError($conn, $db, 2201, 'Domain belongs to another registrar', $clTRID, $trans); @@ -307,7 +308,7 @@ function processDomainDelete($conn, $db, $xml, $clid, $database_type, $trans) { $addPeriod_id = $stmt->fetchColumn(); if ($addPeriod_id) { - $returnValue = getDomainPrice($db, $domainName, $tldid, $addPeriod, 'create', $clid); + $returnValue = getDomainPrice($db, $domainName, $tldid, $addPeriod, 'create', $clid, $currency); $price = $returnValue['price']; if (!isset($price)) { @@ -365,7 +366,7 @@ function processDomainDelete($conn, $db, $xml, $clid, $database_type, $trans) { $autoRenewPeriod_id = $stmt->fetchColumn(); if ($autoRenewPeriod_id) { - $returnValue = getDomainPrice($db, $domainName, $tldid, $autoRenewPeriod, 'renew', $clid); + $returnValue = getDomainPrice($db, $domainName, $tldid, $autoRenewPeriod, 'renew', $clid, $currency); $price = $returnValue['price']; if (!isset($price)) { @@ -388,7 +389,7 @@ function processDomainDelete($conn, $db, $xml, $clid, $database_type, $trans) { $renewPeriod_id = $stmt->fetchColumn(); if ($renewPeriod_id) { - $returnValue = getDomainPrice($db, $domainName, $tldid, $renewPeriod, 'renew', $clid); + $returnValue = getDomainPrice($db, $domainName, $tldid, $renewPeriod, 'renew', $clid, $currency); $price = $returnValue['price']; if (!isset($price)) { @@ -413,7 +414,7 @@ function processDomainDelete($conn, $db, $xml, $clid, $database_type, $trans) { if ($transferPeriod_id) { // Return money if a transfer was also a renew if ($transferPeriod > 0) { - $returnValue = getDomainPrice($db, $domainName, $tldid, $transferPeriod, 'renew', $clid); + $returnValue = getDomainPrice($db, $domainName, $tldid, $transferPeriod, 'renew', $clid, $currency); $price = $returnValue['price']; if (!isset($price)) { diff --git a/epp/src/epp-renew.php b/epp/src/epp-renew.php index 81f1a72..9562a4c 100644 --- a/epp/src/epp-renew.php +++ b/epp/src/epp-renew.php @@ -106,14 +106,15 @@ function processDomainRenew($conn, $db, $xml, $clid, $database_type, $trans) { } // Check registrar account balance - $stmt = $db->prepare("SELECT accountBalance, creditLimit FROM registrar WHERE id = :registrarId LIMIT 1"); + $stmt = $db->prepare("SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = :registrarId LIMIT 1"); $stmt->bindParam(':registrarId', $clid['id'], PDO::PARAM_INT); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); $registrar_balance = $row['accountBalance']; $creditLimit = $row['creditLimit']; - - $returnValue = getDomainPrice($db, $domainData['name'], $domainData['tldid'], $date_add, 'renew', $clid); + $currency = $row['currency']; + + $returnValue = getDomainPrice($db, $domainData['name'], $domainData['tldid'], $date_add, 'renew', $clid['id'], $currency); $price = $returnValue['price']; if (($registrar_balance + $creditLimit) < $price) { diff --git a/epp/src/epp-transfer.php b/epp/src/epp-transfer.php index bae4769..584ee94 100644 --- a/epp/src/epp-transfer.php +++ b/epp/src/epp-transfer.php @@ -470,7 +470,12 @@ function processDomainTransfer($conn, $db, $xml, $clid, $database_type, $trans) $stmt->execute([$domainName]); $date_add = $stmt->fetchColumn(); - $returnValue = getDomainPrice($db, $domainName, $tldid, $date_add, 'transfer', $clid); + $stmt = $db->prepare("SELECT currency FROM registrar WHERE id = :registrar_id LIMIT 1"); + $stmt->execute([':registrar_id' => $clid]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $currency = $result["currency"]; + + $returnValue = getDomainPrice($db, $domainName, $tldid, $date_add, 'transfer', $clid, $currency); $price = $returnValue['price']; if (($registrar_balance + $creditLimit) < $price) { @@ -1022,13 +1027,14 @@ function processDomainTransfer($conn, $db, $xml, $clid, $database_type, $trans) return; } - $stmt = $db->prepare("SELECT accountBalance, creditLimit FROM registrar WHERE id = :registrar_id LIMIT 1"); + $stmt = $db->prepare("SELECT accountBalance, creditLimit, currency FROM registrar WHERE id = :registrar_id LIMIT 1"); $stmt->execute([':registrar_id' => $clid]); $result = $stmt->fetch(PDO::FETCH_ASSOC); $registrar_balance = $result["accountBalance"]; $creditLimit = $result["creditLimit"]; + $currency = $result["currency"]; - $returnValue = getDomainPrice($db, $domainName, $tldid, $date_add, 'transfer', $clid); + $returnValue = getDomainPrice($db, $domainName, $tldid, $date_add, 'transfer', $clid, $currency); $price = $returnValue['price']; if (($registrar_balance + $creditLimit) < $price) { diff --git a/epp/src/epp-update.php b/epp/src/epp-update.php index 34dbeba..4b582ec 100644 --- a/epp/src/epp-update.php +++ b/epp/src/epp-update.php @@ -1844,32 +1844,14 @@ function processDomainUpdate($conn, $db, $xml, $clid, $database_type, $trans) { $temp_id = $sth->fetchColumn(); if ($temp_id == 1) { - $sth = $db->prepare("SELECT accountBalance,creditLimit FROM registrar WHERE id = ?"); + $sth = $db->prepare("SELECT accountBalance,creditLimit,currency FROM registrar WHERE id = ?"); $sth->execute([$clid]); - list($registrar_balance, $creditLimit) = $sth->fetch(); + list($registrar_balance, $creditLimit, $currency) = $sth->fetch(); - $sth = $db->prepare(" - SELECT m12 - FROM domain_price - WHERE tldid = ? - AND command = 'renew' - AND (registrar_id = ? OR registrar_id IS NULL) - ORDER BY registrar_id DESC - LIMIT 1 - "); - $sth->execute([$row['tldid'], $clid]); - $renew_price = $sth->fetchColumn(); + $returnValue = getDomainPrice($db, $domainName, $row['tldid'], 12, 'renew', $clid, $currency); + $renew_price = $returnValue['price']; - $sth = $db->prepare(" - SELECT price - FROM domain_restore_price - WHERE tldid = ? - AND (registrar_id = ? OR registrar_id IS NULL) - ORDER BY registrar_id DESC - LIMIT 1 - "); - $sth->execute([$row['tldid'], $clid]); - $restore_price = $sth->fetchColumn(); + $restore_price = getDomainRestorePrice($db, $row['tldid'], $clid, $currency); if (($registrar_balance + $creditLimit) < ($renew_price + $restore_price)) { sendEppError($conn, $db, 2104, 'There is no money on the account for restore and renew', $clTRID, $trans); diff --git a/epp/src/helpers.php b/epp/src/helpers.php index bf49f0a..73d88cb 100644 --- a/epp/src/helpers.php +++ b/epp/src/helpers.php @@ -12,6 +12,11 @@ use League\Flysystem\Local\LocalFilesystemAdapter; use League\Flysystem\Filesystem; use MatthiasMullie\Scrapbook\Adapters\Flysystem as ScrapbookFlysystem; use MatthiasMullie\Scrapbook\Psr6\Pool; +use Money\Money; +use Money\Currency; +use Money\Converter; +use Money\Currencies\ISOCurrencies; +use Money\Exchange\FixedExchange; /** * Sets up and returns a Logger instance. @@ -602,76 +607,234 @@ function updatePermittedIPs($pool, $permittedIPsTable) { } } -function getDomainPrice($pdo, $domain_name, $tld_id, $date_add = 12, $command = 'create', $registrar_id = null) { - // Check if the domain is a premium domain - $stmt = $pdo->prepare(" - SELECT c.category_price - FROM premium_domain_pricing p - JOIN premium_domain_categories c ON p.category_id = c.category_id - WHERE p.domain_name = ? AND p.tld_id = ? - "); - $stmt->execute([$domain_name, $tld_id]); - if ($stmt->rowCount() > 0) { - return ['type' => 'premium', 'price' => number_format((float)$stmt->fetch()['category_price'], 2, '.', '')]; +function getDomainPrice($pdo, $domain_name, $tld_id, $date_add = 12, $command = 'create', $registrar_id = null, $currency = 'USD') { + $cacheKey = "domain_price_{$domain_name}_{$tld_id}_{$date_add}_{$command}_{$registrar_id}_{$currency}"; + + // Try fetching from cache + if (function_exists('apcu_fetch')) { + $cached = apcu_fetch($cacheKey); + if ($cached !== false) { + return $cached; + } } - // Check if there is a promotion for the domain + $exchangeRates = getExchangeRates(); + $baseCurrency = $exchangeRates['base_currency'] ?? 'USD'; + $exchangeRate = $exchangeRates['rates'][$currency] ?? 1.0; + + // Check for premium pricing + $premiumPrice = apcu_fetch("premium_price_{$domain_name}_{$tld_id}") ?: fetchSingleValue( + $pdo, + 'SELECT c.category_price + FROM premium_domain_pricing p + JOIN premium_domain_categories c ON p.category_id = c.category_id + WHERE p.domain_name = ? AND p.tld_id = ?', + [$domain_name, $tld_id] + ); + + if (!is_null($premiumPrice) && $premiumPrice !== false) { + $money = convertMoney(new Money((int) ($premiumPrice * 100), new Currency($baseCurrency)), $exchangeRate, $currency); + $result = ['type' => 'premium', 'price' => formatMoney($money)]; + + apcu_store($cacheKey, $result, 1800); + return $result; + } + + // Check for active promotions $currentDate = date('Y-m-d'); - $stmt = $pdo->prepare(" - SELECT discount_percentage, discount_amount - FROM promotion_pricing - WHERE tld_id = ? - AND promo_type = 'full' - AND status = 'active' - AND start_date <= ? - AND end_date >= ? - "); - $stmt->execute([$tld_id, $currentDate, $currentDate]); - if ($stmt->rowCount() > 0) { - $promo = $stmt->fetch(); - $discount = null; - - // Determine discount based on percentage or amount - if (!empty($promo['discount_percentage'])) { - $discount = $promo['discount_percentage']; // Percentage discount - } elseif (!empty($promo['discount_amount'])) { - $discount = $promo['discount_amount']; // Fixed amount discount - } - } else { - $discount = null; + $promo = apcu_fetch("promo_{$tld_id}") ?: fetchSingleRow( + $pdo, + "SELECT discount_percentage, discount_amount + FROM promotion_pricing + WHERE tld_id = ? + AND promo_type = 'full' + AND status = 'active' + AND start_date <= ? + AND end_date >= ?", + [$tld_id, $currentDate, $currentDate] + ); + + if ($promo) { + apcu_store("promo_{$tld_id}", $promo, 3600); } - // Get regular price for the specified period - $priceColumn = "m" . $date_add; - $sql = " - SELECT $priceColumn - FROM domain_price - WHERE tldid = ? - AND command = ? - AND (registrar_id = ? OR registrar_id IS NULL) - ORDER BY registrar_id DESC - LIMIT 1 - "; - $stmt = $pdo->prepare($sql); - $stmt->execute([$tld_id, $command, $registrar_id]); - - if ($stmt->rowCount() > 0) { - $regularPrice = $stmt->fetch()[$priceColumn]; + // Get regular price from DB + $priceColumn = "m" . (int) $date_add; + $regularPrice = apcu_fetch("regular_price_{$tld_id}_{$command}_{$registrar_id}") ?: fetchSingleValue( + $pdo, + "SELECT $priceColumn + FROM domain_price + WHERE tldid = ? AND command = ? + AND (registrar_id = ? OR registrar_id IS NULL) + ORDER BY registrar_id DESC LIMIT 1", + [$tld_id, $command, $registrar_id] + ); - if ($discount !== null) { - if (isset($promo['discount_percentage'])) { - $discountAmount = $regularPrice * ($promo['discount_percentage'] / 100); + if (!is_null($regularPrice) && $regularPrice !== false) { + apcu_store("regular_price_{$tld_id}_{$command}_{$registrar_id}", $regularPrice, 1800); + + $finalPrice = $regularPrice * 100; // Convert DB float to cents + if ($promo) { + if (!empty($promo['discount_percentage'])) { + $discountAmount = (int) ($finalPrice * ($promo['discount_percentage'] / 100)); } else { - $discountAmount = $discount; + $discountAmount = (int) ($promo['discount_amount'] * 100); } - $price = $regularPrice - $discountAmount; - return ['type' => 'promotion', 'price' => number_format((float)$price, 2, '.', '')]; + $finalPrice = max(0, $finalPrice - $discountAmount); + $type = 'promotion'; + } else { + $type = 'regular'; } - - return ['type' => 'regular', 'price' => number_format((float)$regularPrice, 2, '.', '')]; + + $money = convertMoney(new Money($finalPrice, new Currency($baseCurrency)), $exchangeRate, $currency); + $result = ['type' => $type, 'price' => formatMoney($money)]; + + apcu_store($cacheKey, $result, 1800); + return $result; } - return ['type' => 'not_found', 'price' => 0]; + return ['type' => 'not_found', 'price' => formatMoney(new Money(0, new Currency($currency)))]; +} + +function getDomainRestorePrice($pdo, $tld_id, $registrar_id = null, $currency = 'USD') { + $cacheKey = "domain_restore_price_{$tld_id}_{$registrar_id}_{$currency}"; + + // Try fetching from cache + if (function_exists('apcu_fetch')) { + $cached = apcu_fetch($cacheKey); + if ($cached !== false) { + return $cached; + } + } + + // Fetch exchange rates + $exchangeRates = getExchangeRates(); + $baseCurrency = $exchangeRates['base_currency'] ?? 'USD'; + $exchangeRate = $exchangeRates['rates'][$currency] ?? 1.0; + + // Fetch restore price from DB + $restorePrice = fetchSingleValue( + $pdo, + "SELECT price + FROM domain_restore_price + WHERE tldid = ? + AND (registrar_id = ? OR registrar_id IS NULL) + ORDER BY registrar_id DESC + LIMIT 1", + [$tld_id, $registrar_id] + ); + + // If no restore price is found, return 0.00 + if (is_null($restorePrice) || $restorePrice === false) { + return '0.00'; + } + + // Convert to Money object for precision + $money = convertMoney(new Money((int) ($restorePrice * 100), new Currency($baseCurrency)), $exchangeRate, $currency); + + // Format and cache the result + $formattedPrice = formatMoney($money); + apcu_store($cacheKey, $formattedPrice, 1800); // Cache for 30 minutes + + return $formattedPrice; +} + +/** + * Load exchange rates from JSON file with APCu caching. + */ +function getExchangeRates() { + $cacheKey = 'exchange_rates'; + + if (function_exists('apcu_fetch')) { + $cached = apcu_fetch($cacheKey); + if ($cached !== false) { + return $cached; + } + } + + $filePath = "/var/www/cp/resources/exchange_rates.json"; + $defaultRates = [ + 'base_currency' => 'USD', + 'rates' => [ + 'USD' => 1.0 // Ensure USD always exists + ], + 'last_updated' => date('c') // ISO 8601 timestamp + ]; + + if (!file_exists($filePath) || !is_readable($filePath)) { + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $defaultRates, 3600); + } + return $defaultRates; + } + + $json = file_get_contents($filePath); + $data = json_decode($json, true); + + if (!isset($data['base_currency'], $data['rates']) || !is_array($data['rates'])) { + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $defaultRates, 3600); + } + return $defaultRates; + } + + // Ensure base currency exists + if (!isset($data['rates'][$data['base_currency']])) { + $data['rates'][$data['base_currency']] = 1.0; + } + + // Ensure every currency defaults to 1.0 if missing + foreach ($data['rates'] as $currency => $rate) { + if (!is_numeric($rate)) { + $data['rates'][$currency] = 1.0; + } + } + + if (function_exists('apcu_store')) { + apcu_store($cacheKey, $data, 3600); + } + + return $data; +} + +/** + * Convert MoneyPHP object to the target currency. + */ +function convertMoney(Money $amount, float $exchangeRate, string $currency) { + $currencies = new ISOCurrencies(); + $exchange = new FixedExchange([ + $amount->getCurrency()->getCode() => [ + $currency => (string) $exchangeRate // Convert float to string + ] + ]); + $converter = new Converter($currencies, $exchange); + + return $converter->convert($amount, new Currency($currency)); +} + +/** + * Format Money object back to a string (e.g., "10.00"). + */ +function formatMoney(Money $money) { + return number_format($money->getAmount() / 100, 2, '.', ''); +} + +/** + * Fetch a single value from the database using PDO. + */ +function fetchSingleValue($pdo, string $query, array $params) { + $stmt = $pdo->prepare($query); + $stmt->execute($params); + return $stmt->fetchColumn(); +} + +/** + * Fetch a single row from the database using PDO. + */ +function fetchSingleRow($pdo, string $query, array $params) { + $stmt = $pdo->prepare($query); + $stmt->execute($params); + return $stmt->fetch(PDO::FETCH_ASSOC); } function generateAuthInfo(): string {