PHP WebShell
Текущая директория: /var/www/bitcardoApp/cron
Просмотр файла: update_online_rates.php
<?php
/**
* cron/update_online_rates.php
*
* Fetch real rates for BTC, ETH, SOL, USDT, NGN (base = USDT) every 3 minutes.
* Store:
* - rate: real online rate (USDT per COIN; for NGN it's NGN per USDT)
* - buy_rate = rate - (margin_percent% of rate)
* - sell_rate = rate + (margin_percent% of rate)
*
* IMPORTANT: margin_percent is ADMIN-CONTROLLED.
* This script NEVER writes or updates margin_percent. It only READS it.
*/
declare(strict_types=1);
ini_set('display_errors', '0');
error_reporting(E_ALL);
require_once __DIR__ . "/../config/db_config.php"; // $conn (mysqli)
$COINS = ['BTC','ETH','SOL','USDT','NGN'];
$LOG_FILE = __DIR__ . "/logs/update_online_rates.log";
@is_dir(dirname($LOG_FILE)) || @mkdir(dirname($LOG_FILE), 0775, true);
function log_line(string $msg) {
global $LOG_FILE;
@file_put_contents($LOG_FILE, "[".date('Y-m-d H:i:s')."] $msg\n", FILE_APPEND);
}
function curl_json(string $url): ?array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 6,
CURLOPT_TIMEOUT => 6,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'bitcardo-rates-cron/3.0',
]);
$res = curl_exec($ch);
$err = curl_error($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($err || $code < 200 || $code >= 300 || !$res) return null;
$json = json_decode($res, true);
return json_last_error() === JSON_ERROR_NONE ? $json : null;
}
/** Crypto: USDT per COIN (USDT base). USDT=1.0 */
function get_crypto_rate(string $coin): ?float {
$coin = strtoupper($coin);
if ($coin === 'USDT') return 1.0;
// Primary: Binance
$binance = ['BTC'=>'BTCUSDT','ETH'=>'ETHUSDT','SOL'=>'SOLUSDT'];
if (isset($binance[$coin])) {
$j = curl_json("https://api.binance.com/api/v3/ticker/price?symbol={$binance[$coin]}");
if ($j && isset($j['price']) && is_numeric($j['price'])) return (float)$j['price'];
}
// Fallback: CoinGecko (USD ~= USDT)
$cg = ['BTC'=>'bitcoin','ETH'=>'ethereum','SOL'=>'solana'];
if (isset($cg[$coin])) {
$j = curl_json("https://api.coingecko.com/api/v3/simple/price?ids={$cg[$coin]}&vs_currencies=usd");
if ($j && isset($j[$cg[$coin]]['usd']) && is_numeric($j[$cg[$coin]]['usd'])) return (float)$j[$cg[$coin]]['usd'];
}
return null;
}
/** NGN per USDT (≈ NGN per USD) */
function get_ngn_per_usdt(): ?float {
$j = curl_json("https://api.exchangerate.host/latest?base=USD&symbols=NGN");
if ($j && isset($j['rates']['NGN']) && is_numeric($j['rates']['NGN'])) return (float)$j['rates']['NGN'];
$j = curl_json("https://open.er-api.com/v6/latest/USD");
if ($j && isset($j['rates']['NGN']) && is_numeric($j['rates']['NGN'])) return (float)$j['rates']['NGN'];
return null;
}
/**
* Read margin_percent for a coin without modifying it.
* Priority: online_coin_rates.margin_percent -> coin_rates.margin_percent -> 0
*/
function read_margin_percent(mysqli $conn, string $coin): float {
$coin = strtoupper($coin);
// 1) online_coin_rates
$sql1 = "SELECT margin_percent FROM online_coin_rates WHERE UPPER(coin)=?";
$stmt = $conn->prepare($sql1);
$stmt->bind_param("s", $coin);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
$stmt->close();
if ($row && $row['margin_percent'] !== null) {
return max(0.0, (float)$row['margin_percent']);
}
// 2) coin_rates
$sql2 = "SELECT margin_percent FROM coin_rates WHERE UPPER(coin)=?";
$stmt = $conn->prepare($sql2);
$stmt->bind_param("s", $coin);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
$stmt->close();
if ($row && $row['margin_percent'] !== null) {
return max(0.0, (float)$row['margin_percent']);
}
// 3) default
return 0.0;
}
/** Upsert without touching margin_percent */
function upsert_rate(mysqli $conn, string $coin, float $rate, float $buy_rate, float $sell_rate, string $source): bool {
$sql = "
INSERT INTO online_coin_rates (coin, rate, buy_rate, sell_rate, source, fetched_at)
VALUES (?, ?, ?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
rate = VALUES(rate),
buy_rate = VALUES(buy_rate),
sell_rate = VALUES(sell_rate),
source = VALUES(source),
fetched_at= VALUES(fetched_at)
";
$stmt = $conn->prepare($sql);
if (!$stmt) return false;
$coin = strtoupper($coin);
$stmt->bind_param("sddds", $coin, $rate, $buy_rate, $sell_rate, $source);
$ok = $stmt->execute();
$stmt->close();
return $ok;
}
// Concurrency guard
$gotLock = false;
if ($r = $conn->query("SELECT GET_LOCK('update_online_rates_lock_v4', 10) AS l")) {
$gotLock = ((int)$r->fetch_assoc()['l'] === 1);
$r->close();
}
if (!$gotLock) {
log_line("Another instance running; exiting.");
exit;
}
// Main
try {
$summary = [];
foreach ($COINS as $coin) {
$coin = strtoupper($coin);
$rate = ($coin === 'NGN') ? get_ngn_per_usdt() : get_crypto_rate($coin);
$src = ($coin === 'NGN') ? 'fx:exchangeratehost|erapi' : 'crypto:binance|coingecko';
if ($rate && $rate > 0) {
$margin = read_margin_percent($conn, $coin); // read-only
$delta = $rate * ($margin / 100.0);
$buy = $rate - $delta; // coin -> USDT
$sell = $rate + $delta; // USDT -> coin
$ok = upsert_rate($conn, $coin, $rate, $buy, $sell, $src);
$summary[] = "$coin=" . ($ok ? "OK" : "FAIL") . " rate:$rate margin:$margin";
} else {
$summary[] = "$coin=NO_DATA";
}
}
log_line("Update complete: " . implode(', ', $summary));
} catch (Throwable $e) {
log_line("ERROR: " . $e->getMessage());
} finally {
@$conn->query("SELECT RELEASE_LOCK('update_online_rates_lock_v4')");
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!