PHP WebShell

Текущая директория: /var/www/bitcardoApp/user/crypto

Просмотр файла: swap.php

<?php
ob_start();
require_once "../../config/db_config.php";
if (!isset($_SESSION)) { session_start(); }
if (!isset($_SESSION['user_id'])) { header("Location: ../../auth/login.php"); exit(); }
$user_id = (int)$_SESSION['user_id'];

function get_setting(mysqli $conn, string $key, $default = null) {
  $sql = "SELECT setting_value FROM site_settings WHERE setting_key=? LIMIT 1";
  $stmt = $conn->prepare($sql);
  $stmt->bind_param("s", $key);
  $stmt->execute();
  $row = $stmt->get_result()->fetch_assoc();
  $stmt->close();
  return $row['setting_value'] ?? $default;
}

function table_has_column(mysqli $conn, string $table, string $column): bool {
  $sql = "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
          WHERE TABLE_SCHEMA = DATABASE()
            AND TABLE_NAME = ?
            AND COLUMN_NAME = ?
          LIMIT 1";
  $stmt = $conn->prepare($sql);
  $stmt->bind_param("ss", $table, $column);
  $stmt->execute();
  $res = $stmt->get_result();
  $ok  = $res && $res->num_rows > 0;
  $stmt->close();
  return $ok;
}

function norm_coin(string $coin): string {
  $coin = strtoupper(trim($coin));
  $map = [
    'USDT-TRC20' => 'USDT',
    'TRON'       => 'TRX',
  ];
  return $map[$coin] ?? $coin;
}

function usd_price(mysqli $conn, string $coin): float {
  $coin = norm_coin($coin);

  $stmt = $conn->prepare("SELECT rate FROM online_coin_rates WHERE UPPER(coin)=? LIMIT 1");
  $stmt->bind_param("s", $coin);
  $stmt->execute();
  $row = $stmt->get_result()->fetch_assoc();
  $stmt->close();
  $usd = (float)($row['rate'] ?? 0.0);
  if ($usd > 0) return $usd;

  if (table_has_column($conn,'coin_rates','usd_price')) {
    $stmt = $conn->prepare("SELECT usd_price FROM coin_rates WHERE UPPER(coin)=? LIMIT 1");
    $stmt->bind_param("s", $coin);
    $stmt->execute();
    $row = $stmt->get_result()->fetch_assoc();
    $stmt->close();
    return (float)($row['usd_price'] ?? 0.0);
  }
  return 0.0;
}

function min_swap(mysqli $conn, string $coin): float {
  $coin = norm_coin($coin);
  $stmt = $conn->prepare("SELECT COALESCE(min_swap,0) AS m FROM coin_rates WHERE UPPER(coin)=? LIMIT 1");
  $stmt->bind_param("s", $coin);
  $stmt->execute();
  $row = $stmt->get_result()->fetch_assoc();
  $stmt->close();
  return (float)($row['m'] ?? 0.0);
}

/* ---------- AJAX quote ---------- */
if (isset($_GET['ajax']) && $_GET['ajax'] === 'quote') {
  ini_set('display_errors', '0');
  while (ob_get_level() > 0) { ob_end_clean(); }
  header('Content-Type: application/json; charset=utf-8');

  $from = strtoupper($_GET['from'] ?? '');
  $to   = strtoupper($_GET['to'] ?? '');
  $amt  = (float)($_GET['amount'] ?? 0);

  if (!$from || !$to || $amt <= 0) { echo json_encode(['error'=>'Invalid input']); exit; }

  $factor = (float) get_setting($conn, 'swap_coin_to_usdt_factor', '0.8000');
  if ($factor < 0.10) $factor = 0.10;
  if ($factor > 1.00) $factor = 1.00;

  $min_cfg = min_swap($conn, $from);
  if ($min_cfg > 0) {
    if ($from === 'NGN') {
      if ($amt < $min_cfg) { echo json_encode(['error'=>"Min ₦".number_format($min_cfg,0)]); exit; }
    } else {
      if ($amt < $min_cfg) { echo json_encode(['error'=>"Min $".number_format($min_cfg,2)]); exit; }
    }
  }

  $to_is_usdt = in_array($to, ['USDT','USDT-TRC20'], true);

  // COIN->USDT: user enters USD, payout USDT = USD * factor
  if ($from !== 'NGN' && $to_is_usdt) {
    $to_amount = $amt * $factor;
    echo json_encode(['rate'=>$factor, 'to_amount'=>round($to_amount, 8)]);
    exit;
  }

  // COIN->COIN: USD -> to coin
  if ($from !== 'NGN' && $to !== 'NGN') {
    $p = usd_price($conn, $to);
    if ($p <= 0) { echo json_encode(['error'=>"Missing USD price for {$to}. Run cron."]); exit; }
    $to_amount = $amt / $p;
    echo json_encode(['rate'=>($to_amount/$amt), 'to_amount'=>round($to_amount, 8)]);
    exit;
  }

  // NGN->COIN
  if ($from === 'NGN' && $to !== 'NGN') {
    $to_n = norm_coin($to);
    $stmt = $conn->prepare("SELECT buy_rate FROM coin_rates WHERE UPPER(coin)=? LIMIT 1");
    $stmt->bind_param("s", $to_n);
    $stmt->execute();
    $r = $stmt->get_result()->fetch_assoc();
    $stmt->close();
    $ngn_per_usd = (float)($r['buy_rate'] ?? 0);
    if ($ngn_per_usd <= 0) { echo json_encode(['error'=>"Missing NGN/$ buy rate for {$to}"]); exit; }

    $usd = $amt / $ngn_per_usd;
    $p  = usd_price($conn, $to);
    if ($p <= 0) { echo json_encode(['error'=>"Missing USD price for {$to}. Run cron."]); exit; }

    $to_amount = $usd / $p;
    echo json_encode(['rate'=>($to_amount/$amt), 'to_amount'=>round($to_amount, 8)]);
    exit;
  }

  // COIN->NGN
  if ($from !== 'NGN' && $to === 'NGN') {
    $from_n = norm_coin($from);
    $stmt = $conn->prepare("SELECT sell_rate FROM coin_rates WHERE UPPER(coin)=? LIMIT 1");
    $stmt->bind_param("s", $from_n);
    $stmt->execute();
    $r = $stmt->get_result()->fetch_assoc();
    $stmt->close();
    $ngn_per_usd = (float)($r['sell_rate'] ?? 0);
    if ($ngn_per_usd <= 0) { echo json_encode(['error'=>"Missing NGN/$ sell rate for {$from}"]); exit; }

    $to_amount = $amt * $ngn_per_usd;
    echo json_encode(['rate'=>($to_amount/$amt), 'to_amount'=>round($to_amount, 2)]);
    exit;
  }

  echo json_encode(['error'=>'Invalid pair']);
  exit;
}

/* ---------- Page data ---------- */
$hasWalletStatus = table_has_column($conn, 'user_wallets', 'wallet_status');
$activeClause = $hasWalletStatus ? " AND (LOWER(uw.wallet_status)='active' OR uw.wallet_status IS NULL) " : "";

/* You send */
$sendCoins = [];
$balances  = [];
$min_swaps = [];
$usdPrices = [];

$stmt = $conn->prepare("SELECT uw.coin, uw.balance FROM user_wallets uw WHERE uw.user_id=? $activeClause AND uw.balance>0");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$rs = $stmt->get_result();
while ($row = $rs->fetch_assoc()) {
  $c = strtoupper($row['coin']);
  $sendCoins[]   = $c;
  $balances[$c]  = (float)$row['balance'];
  $min_swaps[$c] = min_swap($conn, $c);
  $usdPrices[$c] = ($c === 'NGN') ? 0.0 : usd_price($conn, $c);
}
$stmt->close();

/* You get wallets */
$activeToCoins = [];
$stmt = $conn->prepare("SELECT DISTINCT UPPER(uw.coin) AS coin FROM user_wallets uw WHERE uw.user_id=? $activeClause");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$rt = $stmt->get_result();
while ($row = $rt->fetch_assoc()) { $activeToCoins[] = $row['coin']; }
$stmt->close();

/* allowed directions */
$directions = [];
$d = $conn->query("SELECT from_coin,to_coin FROM swap_directions WHERE is_active=1");
while ($row = $d->fetch_assoc()) {
  $f = strtoupper($row['from_coin']);
  $t = strtoupper($row['to_coin']);
  $directions[$f][] = $t;
}

$quotePath = $_SERVER['PHP_SELF'];
?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">

<div class="py-2">
  <form action="../../models/crypto/swap.php" method="POST">
    <div class="mb-3">
      <label class="form-label ms-2 d-flex justify-content-between align-items-center">
        <span>You send</span>
        <span id="balanceMsg" class="text-danger small d-none"></span>
      </label>

      <div class="input-group">
        <input type="number" name="fromAmount" id="amountFrom" class="form-control" placeholder="100.00" step="0.01" required>
        <button class="btn btn-white-outline btn-sm pyx-0 border" type="button" id="useMaxBtn">Max</button>

        <select id="currencyFrom" name="currencyFrom" class="form-select" style="max-width:140px;" required>
          <?php foreach($sendCoins as $i=>$coin): ?>
            <option value="<?=$coin?>" <?=$i===0?'selected':''?>><?=$coin?></option>
          <?php endforeach; ?>
        </select>
      </div>
            <div>
              <span class="small text-muted ms-1 mt-1" id="entryHint"></span> | 
              <span class="small text-muted ms-1 mt-1" id="minSwapText"></span>
            </div>
    </div>

    <div class="mb-3 mt-3">
      <label class="form-label ms-2">You Get</label>
      <div class="input-group">
        <input type="number" id="amountTo" name="toAmount" class="form-control" placeholder="0.00" readonly>
        <select id="currencyTo" name="currencyTo" class="form-select" style="max-width:140px;" required></select>
      </div>
    </div>

    <div class="d-grid mt-4">
      <button class="btn btn-primary" id="convertBtn">Convert</button>
    </div>

    <div style="font-size: small; text-align: center;">
      This action is instant. Wallets are updated immediately.<br>
      <a href="#" class="text-decoration-none text-secondary"><small>Terms & Condtions Apply</small></a>
    </div>
  </form>

  <script>
  const balances      = <?= json_encode($balances) ?>;
  const minSwaps      = <?= json_encode($min_swaps) ?>;
  const usdPrices     = <?= json_encode($usdPrices) ?>;
  const activeToCoins = new Set(<?= json_encode($activeToCoins) ?>);
  const allowedDirs   = <?= json_encode($directions) ?>;
  const quotePath     = <?= json_encode($quotePath) ?>;

  function variants(c){
    c = (c||'').toUpperCase();
    const out = new Set([c]);
    if (c === 'USDT') out.add('USDT-TRC20');
    if (c === 'USDT-TRC20') out.add('USDT');
    if (c === 'TRON') out.add('TRX');
    if (c === 'TRX') out.add('TRON');
    return Array.from(out);
  }

  function fmtMoney(n, d){
    return Number(n).toLocaleString(undefined, { minimumFractionDigits:d, maximumFractionDigits:d });
  }

  function setInsufficient(msgText){
    const btn = document.getElementById('convertBtn');
    const msg = document.getElementById('balanceMsg');
    const out = document.getElementById('amountTo');
    btn.disabled = true;
    out.value = '';
    msg.textContent = msgText || 'Insufficient balance';
    msg.classList.remove('d-none');
  }

  function clearBalanceMsg(){
    const msg = document.getElementById('balanceMsg');
    msg.classList.add('d-none');
    msg.textContent = '';
  }

  function maxSpendableUsd(from){
    // NGN returns NGN balance, crypto returns USD equivalent of coin balance
    const bal = parseFloat(balances[from] || 0);
    if (from === 'NGN') return bal;

    const p = parseFloat(usdPrices[from] || 0);
    if (bal <= 0 || p <= 0) return 0;
    return bal * p;
  }

  function updateEntryModeUI(){
    const from = document.getElementById('currencyFrom').value;
    const input = document.getElementById('amountFrom');
    const hint  = document.getElementById('entryHint');

    if (from === 'NGN') {
      input.step = '0.01';
      input.placeholder = '1000.00';
      hint.textContent = 'Enter exact amount in ₦ (Naira).';
    } else {
      input.step = '0.01';
      input.placeholder = '100.00';
      hint.textContent = 'Enter USD value (e.g., 100).';
    }
  }

  function updateMinText(){
    const from = document.getElementById('currencyFrom').value;
    const m = parseFloat(minSwaps[from] || 0);
    const el = document.getElementById('minSwapText');
    if (!m || m <= 0) { el.textContent = ''; return; }
    el.textContent = (from === 'NGN') ? `Min ₦${fmtMoney(m,0)}` : `Min $${fmtMoney(m,2)}`;
  }

  function updateCurrencyTo(){
    const from = document.getElementById('currencyFrom').value;
    const toSel = document.getElementById('currencyTo');
    toSel.innerHTML = '';

    const fromVars = variants(from);
    const allowedSet = new Set();
    fromVars.forEach(fv => {
      (allowedDirs[fv] || []).forEach(tc => allowedSet.add(tc));
    });

    const filtered = Array.from(allowedSet).filter(tc => {
      const tcVars = variants(tc);
      return tcVars.some(v => activeToCoins.has(v));
    });

    if (!filtered.length) {
      const o = document.createElement('option');
      o.value = '';
      o.text = 'Not available';
      o.disabled = true;
      o.selected = true;
      toSel.appendChild(o);
    } else {
      filtered.forEach(c => {
        const o = document.createElement('option');
        o.value = c;
        o.text = c;
        toSel.appendChild(o);
      });
      toSel.value = filtered[0];
    }

    updateEntryModeUI();
    updateMinText();
    getSwapQuote();
  }

  function getSwapQuote(){
    const from = document.getElementById('currencyFrom').value;
    const to = document.getElementById('currencyTo').value;
    const amount = parseFloat(document.getElementById('amountFrom').value);

    const btn = document.getElementById('convertBtn');
    const out = document.getElementById('amountTo');

    if (!from || !to || !isFinite(amount) || amount <= 0) {
      out.value = '';
      btn.disabled = true;
      clearBalanceMsg();
      return;
    }

    // Balance enforcement:
    // - NGN: amount <= NGN balance
    // - Crypto: USD amount <= (coin_balance * coin_usd_price)
    const maxVal = maxSpendableUsd(from);
    if (maxVal <= 0) {
      // if crypto price missing, prevent swaps to avoid accidental negative
      if (from !== 'NGN') return setInsufficient('Price unavailable. Try again later.');
      return setInsufficient('Insufficient balance');
    }
    if (amount > maxVal + 1e-9) {
      return setInsufficient('Insufficient balance');
    }

    clearBalanceMsg();
    btn.disabled = false;

    const url = `${quotePath}?ajax=quote&from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&amount=${encodeURIComponent(amount)}`;

    fetch(url, { headers: { 'Accept': 'application/json' } })
      .then(async (r) => {
        const text = await r.text();
        try { return JSON.parse(text); } catch (e) { throw new Error(text.slice(0, 300)); }
      })
      .then(d => {
        if (d.error) {
          setInsufficient(d.error);
          return;
        }
        out.value = d.to_amount ?? '0.00';
      })
      .catch(() => {
        setInsufficient('Quote failed');
      });
  }

  document.getElementById('amountFrom').addEventListener('input', getSwapQuote);
  document.getElementById('currencyFrom').addEventListener('change', updateCurrencyTo);
  document.getElementById('currencyTo').addEventListener('change', getSwapQuote);
  document.addEventListener('DOMContentLoaded', () => setTimeout(updateCurrencyTo, 0));

  // Max: NGN => balance; Crypto => max USD = coinBal * usdPrice
  document.getElementById('useMaxBtn').addEventListener('click', () => {
    const from = document.getElementById('currencyFrom').value;
    const input = document.getElementById('amountFrom');

    const maxVal = maxSpendableUsd(from);
    input.value = (from === 'NGN') ? maxVal.toFixed(2) : maxVal.toFixed(2);

    getSwapQuote();
  });
  </script>
</div>

Выполнить команду


Для локальной разработки. Не используйте в интернете!