PHP WebShell

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

Просмотр файла: single-wallet.php

<?php
// Always use full PHP tags; short tags can be disabled on some servers.
include '../common/header.php';
include '../../includes/wallets/single-walllet.php'; // assumes this sets $wallet_id, $coin, $wallet_address, $wallet_balance, $wallet_qr, $coin_label, etc.
require_once '../../config/db_config.php'; // for site_settings

/**
 * Assumptions / Hooks:
 * - $userEmail is available (user email for Paystack).
 * - $wallet_id and $coin are already set by single-walllet.php.
 * - $paystack_public_key is available (from your gateways table or config).
 */

// ---------- site_settings helper ----------
function get_site_setting(mysqli $conn, string $key, $default = null) {
    $stmt = $conn->prepare("SELECT setting_value FROM site_settings WHERE `setting_key` = ? LIMIT 1");
    if ($stmt) {
        $stmt->bind_param('s', $key);
        $stmt->execute();
        $stmt->bind_result($val);
        if ($stmt->fetch()) {
            $stmt->close();
            return $val;
        }
        $stmt->close();
    }
    return $default;
}

// NEW: read how fees should be handled for NGN deposits
$add_naira_deposit_fee = (int) get_site_setting($conn, 'add_naira_deposit_fee', 1); // 1=add on top (user pays), 0=deduct from amount

// ---------- Amount formatting helpers ----------
if (!function_exists('coin_decimals_ui')) {
    function coin_decimals_ui(string $coin): int {
        $coin = strtoupper($coin);
        return match ($coin) {
            'BTC' => 8,
            'ETH' => 10,
            'SOL' => 9,
            'TRX' => 6,
            'USDT', 'USDC' => 6,
            'USD', 'NGN' => 2,
            default => 8,
        };
    }
}
if (!function_exists('fmt_coin_amount')) {
    function fmt_coin_amount($amount, string $coin): string {
        $scale = coin_decimals_ui($coin);
        return number_format((float)$amount, $scale, '.', '');
    }
}

// Allowed Paystack channels (prioritize bank transfer)
$paystack_channels = ['bank_transfer', 'card', 'ussd', 'mobile_money'];

// (Optional) fallback if not already set
if (!isset($paystack_public_key)) {
    // TODO: replace with your actual pull-from-DB/config
    $paystack_public_key = 'pk_live_6ae29fb1abed2559c70edc72d79001704e488772';
}

// Mask NGN account number (last 4 digits)
$masked_wallet_address = $wallet_address ?? '';
$coin = strtoupper($coin ?? '');
if ($coin === 'NGN' && !empty($masked_wallet_address)) {
    // Keep original formatting; mask only last 4 digits
    $masked_wallet_address = preg_replace_callback('/(\d)(?=(?:\D*\d){4}$)/', fn($m) => $m[1], $masked_wallet_address);
    $masked_wallet_address = preg_replace('/(\d)(?=(?:\D*\d){0,3}$)/', '*', $masked_wallet_address);
}

// Build links / deposit button
if ($coin === 'NGN') {
    $link = '../fiat/send_fiat.php?wallet_id=' . urlencode($wallet_id);
    $deposit = '<div class="col-4 me-3"><button type="button" class="btn btn-outline-primary w-100" data-bs-toggle="modal" data-bs-target="#depositModal">Deposit</button></div>';
} else {
    $link = '../crypto/send_crypto.php?coin=' . urlencode($coin);
    $deposit = '';
}

// Compose display balance text with proper decimals
$display_balance = fmt_coin_amount($wallet_balance ?? 0, $coin) . ' ' . $coin;
$userFName = $userFName ?? '';
$userLName = $userLName ?? '';
$coin_label = $coin_label ?? $coin;
$wallet_qr = $wallet_qr ?? '';
$wallet_address = $wallet_address ?? '';
$userEmail = $userEmail ?? 'user@example.com';
?>
<!-- Main Container -->
<div class="container mt-3">
    <div class="row">
        <?php include '../common/nav.php'; ?>

        <!-- Main Content -->
        <main class="col-md-9 col-lg-10 px-md-5 mb-5">
            <?php include '../common/page-header.php'; ?>
            <div class="container my-5">
                <div class="row g-4">
                    <!-- Center Column -->
                    <div class="offset-md-3 col-md-6 mt-2">

                        <div class="card card-body mt-5 text-center">
                            <h4 class="fw-bold mt-3 mb-0"><?= htmlspecialchars($userFName . ' ' . $userLName); ?></h4>

                            <h5 class="mb-2 mt-3"><?= htmlspecialchars($coin_label); ?></h5>
                            <center>
                                <img class="img img-fluid" style="width: 200px;"
                                     src="<?= '../../assets/qr_codes/' . htmlspecialchars($wallet_qr); ?>"
                                     alt="Wallet QR">
                            </center>

                            <div class="badge bg-light text-dark border mt-3 px-3 py-2">
                                <div id="walletid" class="text-break text-wrap" style="cursor: pointer;" onclick="copyWalletID(this)">
                                    <?= $coin === 'NGN' ? htmlspecialchars($masked_wallet_address) : htmlspecialchars($wallet_address); ?>
                                </div>
                            </div>
                            <small style="font-size: 10px; color: #8f8e94;">Tap to copy</small>
                            <div id="copied-msg" class="mt-3" style="display: none; color: green;">
                                Copied!
                            </div>

                            <div class="open-business bg-light mt-3 mb-3">
                                <i class="bi bi-briefcase me-1"></i>
                                <?= htmlspecialchars($display_balance); ?>
                            </div>
                        </div>

                        <div class="mt-3 d-flex justify-content-center">
                            <?= $deposit; ?>
                            <div class="col-4">
                                <a href="<?= htmlspecialchars($link); ?>" class="btn btn-primary w-100">Transfer</a>
                            </div>
                        </div>

                    </div>
                </div>
            </div>
        </main>
    </div>
</div>

<?php if ($coin === 'NGN'): ?>
<!-- Deposit Amount Modal -->
<div class="modal fade" id="depositModal" tabindex="-1" aria-labelledby="depositModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <form id="depositForm" class="modal-content" onsubmit="startPaystack(event)">
      <div class="modal-header">
        <h5 class="modal-title" id="depositModalLabel">Deposit (NGN)</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
          <div class="mb-3">
              <label for="deposit_amount" class="form-label">Amount (NGN)</label>
              <input type="number" min="100" step="50" class="form-control" id="deposit_amount" name="amount" placeholder="e.g. 5000" required>
              <div class="form-text" id="fee_hint">
                <?php if ($add_naira_deposit_fee): ?>
                  You’ll be charged Paystack fees on top, so your wallet receives the full amount.
                <?php else: ?>
                  Paystack fees will be deducted from this amount before crediting your wallet.
                <?php endif; ?>
              </div>
          </div>
          <div class="small" id="preview_line" style="display:none;"></div>
          <input type="hidden" id="wallet_id" value="<?= htmlspecialchars($wallet_id); ?>">
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
        <button type="submit" class="btn btn-primary">Continue</button>
      </div>
    </form>
  </div>
</div>
<?php endif; ?>

<script>
  function copyWalletID(element) {
    const text = element.textContent;
    navigator.clipboard.writeText(text).then(() => {
      const msg = document.getElementById("copied-msg");
      msg.style.display = "inline";
      setTimeout(() => msg.style.display = "none", 1500);
    });
  }
</script>

<?php if ($coin === 'NGN'): ?>
<!-- Paystack Inline Script -->
<script src="https://js.paystack.co/v1/inline.js"></script>
<script>
  // configure endpoints
  const verifyUrlBase = '../../models/fiat/verify_paystack.php';
  const addFeeMode = <?= (int)$add_naira_deposit_fee ?>; // 1=add on top; 0=deduct from amount

  // Simple Paystack fee estimator (fallback only; server will trust Paystack's fees if present)
  // Nigeria local fee: 1.5% + ₦100 (for amounts > ₦2500), capped at ₦2000. (VAT not included here)
  function estimatePaystackFee(amountNgn) {
    const pct = 0.015;
    const extra = amountNgn > 2500 ? 100 : 0;
    const cap = 2000;
    let fee = (amountNgn * pct) + extra;
    if (fee > cap) fee = cap;
    return Math.round(fee * 100) / 100; // 2dp
  }

  // For "add" mode, we need to gross-up so that (entered) arrives net
  // amountGross = ceil((amountNet + extra) / (1 - pct)) but Paystack adds ₦100 only if gross>2500; we’ll do a simple iterate.
  function grossUpForFee(netAmount) {
    // simple iterative approach to hit target within a few Naira
    let gross = netAmount;
    for (let i=0;i<6;i++) {
      const fee = estimatePaystackFee(gross);
      const newGross = netAmount + fee;
      if (Math.abs(newGross - gross) < 1) return Math.round(newGross);
      gross = newGross;
    }
    return Math.round(gross);
  }

  // Update preview line
  document.getElementById('deposit_amount').addEventListener('input', () => {
    const amt = parseFloat(document.getElementById('deposit_amount').value || '0');
    const line = document.getElementById('preview_line');
    if (!(amt > 0)) { line.style.display='none'; return; }

    if (addFeeMode === 1) {
      const gross = grossUpForFee(amt);
      const fee = gross - amt;
      line.textContent = `You will pay ₦${gross.toLocaleString()} (includes ~₦${fee.toLocaleString()} Paystack fees). Wallet receives ₦${amt.toLocaleString()}.`;
    } else {
      const fee = estimatePaystackFee(amt);
      const net = Math.max(0, amt - fee);
      line.textContent = `You will pay ₦${amt.toLocaleString()}. Estimated Paystack fees: ~₦${fee.toLocaleString()}. Wallet receives ~₦${net.toLocaleString()}.`;
    }
    line.style.display='block';
  });

  function startPaystack(e){
    e.preventDefault();
    const amountField = document.getElementById('deposit_amount');
    let amountNgn   = parseInt(amountField.value, 10);
    if (isNaN(amountNgn) || amountNgn <= 0) {
      amountField.focus();
      return;
    }

    let chargeAmount = amountNgn; // what Paystack will charge
    if (addFeeMode === 1) {
      // add-on-top: charge gross so wallet receives the entered amount
      chargeAmount = grossUpForFee(amountNgn);
    }

    const amountKobo = chargeAmount * 100;
    const ref = 'MWR-' + Date.now() + '-' + Math.floor(Math.random()*1000000);

    const handler = PaystackPop.setup({
      key: '<?= addslashes($paystack_public_key); ?>',
      email: '<?= addslashes($userEmail); ?>',
      amount: amountKobo,
      currency: 'NGN',
      ref: ref,
      channels: <?= json_encode($paystack_channels); ?>,
      metadata: {
        custom_fields: [
          { display_name: "Wallet ID", variable_name: "wallet_id", value: document.getElementById('wallet_id').value }
        ],
        // Pass the user's intended wallet credit (net) and the mode so server can finalize precisely
        add_fee_mode: addFeeMode,
        intended_wallet_credit: amountNgn
      },
      callback: function(response){
        // Close modal
        const modalEl = document.getElementById('depositModal');
        if (modalEl) {
          const modal = bootstrap.Modal.getInstance(modalEl);
          if (modal) modal.hide();
        }

        // Verify on server (simple redirect)
        const verifyUrl = verifyUrlBase
          + '?reference=' + encodeURIComponent(response.reference)
          + '&wallet_id=' + encodeURIComponent(document.getElementById('wallet_id').value)
          + '&amount=' + encodeURIComponent(amountNgn) // the user-entered amount (target credit in add-mode; pay amount in deduct-mode)
          + '&mode=' + encodeURIComponent(addFeeMode); // echo for server
        window.location.href = verifyUrl;
      },
      onClose: function(){
        // optional: no-op or go back
      }
    });

    handler.openIframe();
  }
</script>
<?php endif; ?>

<?php include '../common/footer.php'; ?>

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


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