PHP WebShell
Текущая директория: /var/www/bitcardoApp/user/wallets
Просмотр файла: single-wallet.php
<?php
// user/wallets/single-wallet.php
include '../common/header.php';
include '../../includes/wallets/single-walllet.php'; // sets $wallet_id, $coin, $wallet_address, $wallet_balance, $wallet_qr, $coin_label, etc.
require_once '../../config/db_config.php'; // $conn
/**
* 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;
}
$add_naira_deposit_fee = (int) get_site_setting($conn, 'add_naira_deposit_fee', 1);
// ---------- 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, '.', '');
}
}
/**
* Normalize symbols to match online_coin_rates.coin
* (You already had TRX -> TRON issue before; we handle it here too.)
*/
function norm_rate_coin(string $coin): string {
$coin = strtoupper(trim($coin));
$map = [
'USDT-TRC20' => 'USDT',
'TRX' => 'TRON',
];
return $map[$coin] ?? $coin;
}
/**
* Fetch USD rate from online_coin_rates for this coin
*/
function fetch_usd_rate(mysqli $conn, string $coin): ?float {
$coin = strtoupper($coin);
if ($coin === 'NGN') return null;
// Stablecoin fallback
if (in_array($coin, ['USDT','USDC','USDT-TRC20'], true)) return 1.0;
$c1 = strtoupper($coin);
$c2 = strtoupper(norm_rate_coin($coin));
// try exact coin, then normalized
$stmt = $conn->prepare("SELECT rate FROM online_coin_rates WHERE UPPER(coin)=? LIMIT 1");
if ($stmt) {
$stmt->bind_param('s', $c1);
$stmt->execute();
$stmt->bind_result($rate);
if ($stmt->fetch()) {
$stmt->close();
$r = (float)$rate;
return ($r > 0) ? $r : null;
}
$stmt->close();
}
if ($c2 !== $c1) {
$stmt = $conn->prepare("SELECT rate FROM online_coin_rates WHERE UPPER(coin)=? LIMIT 1");
if ($stmt) {
$stmt->bind_param('s', $c2);
$stmt->execute();
$stmt->bind_result($rate);
if ($stmt->fetch()) {
$stmt->close();
$r = (float)$rate;
return ($r > 0) ? $r : null;
}
$stmt->close();
}
}
return null;
}
// Allowed Paystack channels
$paystack_channels = ['bank_transfer', 'card', 'ussd', 'mobile_money'];
// fallback key if not set
if (!isset($paystack_public_key)) {
$paystack_public_key = 'pk_live_6ae29fb1abed2559c70edc72d79001704e488772';
}
$coin = strtoupper($coin ?? '');
// Mask NGN account number (last 4 digits)
$masked_wallet_address = $wallet_address ?? '';
if ($coin === 'NGN' && !empty($masked_wallet_address)) {
$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);
}
// 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 = '';
}
$userFName = $userFName ?? '';
$userLName = $userLName ?? '';
$coin_label = $coin_label ?? $coin;
$wallet_qr = $wallet_qr ?? '';
$wallet_address = $wallet_address ?? '';
$userEmail = $userEmail ?? 'user@example.com';
// Initial display values
$wallet_balance_num = (float)($wallet_balance ?? 0);
$display_balance = fmt_coin_amount($wallet_balance_num, $coin) . ' ' . $coin;
// Initial USD rate and USD equivalent (for crypto)
$usd_rate = fetch_usd_rate($conn, $coin);
$usd_equiv = null;
if ($coin !== 'NGN' && $usd_rate !== null) {
$usd_equiv = $wallet_balance_num * $usd_rate;
}
?>
<!-- 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; font-size: 1.1.8em; font-weight: 500;" 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>
<!-- Balance (auto-updated) -->
<div class="open-business bg-light mt-3 mb-3">
<i class="bi bi-briefcase me-1"></i>
<!-- Coin amount -->
<span id="walletCoinText">
<?= htmlspecialchars(fmt_coin_amount($wallet_balance_num ?? 0, $coin) . ' ' . $coin); ?>
</span>
<?php if ($coin !== 'NGN'): ?>
<span class="mx-2">|</span>
<!-- USD equivalent -->
<span id="walletUsdText">
<?php if ($usd_equiv !== null): ?>
$<?= number_format((float)$usd_equiv, 2, '.', ','); ?>
<?php else: ?>
--
<?php endif; ?>
</span>
<?php endif; ?>
</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">Withdraw</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>
<!-- Live update (no refresh): coin amount + USD amount -->
<script>
(function () {
const coin = "<?= addslashes(strtoupper($coin)); ?>";
async function poll() {
try {
const res = await fetch("/user/dashboard/wallet_balances.php?ts=" + Date.now(), {
credentials: "include",
cache: "no-store"
});
if (!res.ok) return;
const data = await res.json();
if (!data.ok) return;
const w = (data.wallets && data.wallets[coin]) ? data.wallets[coin] : null;
if (!w) return;
// w.raw = coin amount (or ₦ amount for NGN)
// w.primary = $ amount for crypto (or ₦ amount for NGN)
const coinEl = document.getElementById("walletCoinText");
if (coinEl) {
if (coin === "NGN") {
// For NGN, raw should already include ₦ formatting
coinEl.textContent = (w.raw ?? "") + " NGN";
} else {
coinEl.textContent = (w.raw ?? "") + " " + coin;
}
}
const usdEl = document.getElementById("walletUsdText");
if (usdEl && coin !== "NGN") {
usdEl.textContent = (w.primary && String(w.primary).trim()) ? w.primary : "--";
}
} catch (e) {
// silent
}
}
poll();
setInterval(poll, 5000);
})();
</script>
<?php if ($coin === 'NGN'): ?>
<script src="https://js.paystack.co/v1/inline.js"></script>
<script>
const verifyUrlBase = '../../models/fiat/verify_paystack.php';
const addFeeMode = <?= (int)$add_naira_deposit_fee ?>;
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;
}
function grossUpForFee(netAmount) {
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);
}
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;
if (addFeeMode === 1) 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 }
],
add_fee_mode: addFeeMode,
intended_wallet_credit: amountNgn
},
callback: function(response){
const modalEl = document.getElementById('depositModal');
if (modalEl) {
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
}
const verifyUrl = verifyUrlBase
+ '?reference=' + encodeURIComponent(response.reference)
+ '&wallet_id=' + encodeURIComponent(document.getElementById('wallet_id').value)
+ '&amount=' + encodeURIComponent(amountNgn)
+ '&mode=' + encodeURIComponent(addFeeMode);
window.location.href = verifyUrl;
},
onClose: function(){}
});
handler.openIframe();
}
</script>
<?php endif; ?>
<?php include '../common/footer.php'; ?>
Выполнить команду
Для локальной разработки. Не используйте в интернете!