PHP WebShell
Текущая директория: /var/www/bitcardoApp/user/fiat
Просмотр файла: send_fiat.php
<?php
// user/fiat/send_fiat.php
include '../common/header.php';
require_once "../../config/db_config.php";
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
if (!isset($_SESSION["user_id"])) exit("Session expired. Please login again.");
$userId = (int)$_SESSION["user_id"];
/* --- read site_settings: add_paystack_naira_withdraw_fee (string '1'/'0') --- */
function get_setting(mysqli $conn, string $key, $default = null) {
$stmt = $conn->prepare("SELECT setting_value FROM site_settings WHERE setting_key = ? LIMIT 1");
if (!$stmt) return $default;
$stmt->bind_param("s", $key);
$stmt->execute();
$stmt->bind_result($val);
$ok = $stmt->fetch();
$stmt->close();
return $ok ? $val : $default;
}
$addWithdrawFee = get_setting($conn, 'add_paystack_naira_withdraw_fee', '1') === '1';
/* --- fetch user's NGN wallets --- */
$stmt = $conn->prepare("
SELECT wallet_id, wallet_add, balance, bank_name
FROM user_wallets
WHERE user_id = ? AND type = 'fiat' AND coin = 'NGN'
");
$stmt->bind_param("i", $userId);
$stmt->execute();
$res = $stmt->get_result();
$userWallets = [];
while ($row = $res->fetch_assoc()) $userWallets[] = $row;
$stmt->close();
/* --- load Paystack banks for autocomplete (server-side using secret key) --- */
$bankOptions = [];
$bankMapForSession = [];
if (!empty($paystackSecret)) {
$ch = curl_init("https://api.paystack.co/bank");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$paystackSecret}",
"Accept: application/json"
],
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
curl_close($ch);
$banks = json_decode($response, true);
if ($banks && !empty($banks["data"])) {
foreach ($banks["data"] as $bank) {
if (!empty($bank["code"]) && !empty($bank["name"])) {
$code = (string)$bank["code"];
$name = (string)$bank["name"];
$bankOptions[] = ["name"=>$name, "code"=>$code];
$bankMapForSession[$code] = $name;
}
}
}
}
$_SESSION['paystack_banks'] = ($_SESSION['paystack_banks'] ?? []);
if (!empty($bankMapForSession)) {
$_SESSION['paystack_banks'] = array_merge($_SESSION['paystack_banks'], $bankMapForSession);
}
$preselectedWalletId = isset($_GET["wallet_id"]) ? (int)$_GET["wallet_id"] : null;
?>
<style>
.form-container{max-width:600px;margin:2rem auto;padding:2rem;border-radius:12px;background:#f8f9fa;box-shadow:0 4px 12px rgba(0,0,0,0.05)}
.autocomplete-list{position:absolute;z-index:1000;background:#fff;border:1px solid #ddd;width:100%;max-height:220px;overflow-y:auto;border-radius:.5rem}
.autocomplete-item{padding:8px 12px;cursor:pointer}
.autocomplete-item:hover{background:#f1f1f1}
.insufficient-warning{color:#dc3545;margin-top:.25rem;display:none;font-size:.9rem}
.hint{font-size:.85rem;color:#6c757d}
</style>
<div class="container mt-3">
<div class="row">
<?php include '../common/nav.php'; ?>
<main class="col-md-9 col-lg-10 px-md-5 mb-5">
<?php include '../common/page-header.php'; ?>
<div class="form-container col-md-6">
<h3 class="mb-3 text-center">Send Naira to Bank Account</h3>
<p class="hint mb-4">
Funds are paid out from our Paystack merchant balance; your Bitcardo wallet will be debited internally.
<?php if ($addWithdrawFee): ?>
We’ll include the Paystack transfer fee in your debit.
<?php else: ?>
Paystack transfer fee is covered by us; you’ll be debited the amount only.
<?php endif; ?>
</p>
<form id="sendFiatForm" action="../../user/fiat/confirm_withdrawal.php" method="post" autocomplete="off" novalidate>
<div class="mb-3">
<label for="wallet_id" class="form-label">Select Wallet (to debit)</label>
<select class="form-select" name="wallet_id" id="wallet_id" required>
<option value="">Select Wallet</option>
<?php foreach ($userWallets as $w): ?>
<option value="<?= htmlspecialchars($w["wallet_id"]) ?>"
data-balance="<?= htmlspecialchars($w["balance"]) ?>"
<?= (count($userWallets)===1 || $preselectedWalletId===(int)$w["wallet_id"]) ? 'selected' : '' ?>>
<?= htmlspecialchars($w["bank_name"]) ?> (<?= htmlspecialchars($w["wallet_add"]) ?>)
— ₦<?= number_format($w["balance"], 2) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3 position-relative">
<label for="bank_search" class="form-label">Bank Name</label>
<input type="text" class="form-control" id="bank_search" placeholder="Start typing bank name..." autocomplete="off" inputmode="text" />
<div id="bank_suggestions" class="autocomplete-list d-none"></div>
<input type="hidden" name="bank_code" id="bank_code" required>
</div>
<div class="mb-3">
<label for="account_number" class="form-label">Account Number</label>
<input type="text" class="form-control" name="account_number" id="account_number" maxlength="10" inputmode="numeric" pattern="\d{10}" required>
<div class="invalid-feedback d-none" id="account_error_message"></div>
</div>
<div class="mb-3">
<label for="account_name" class="form-label">Account Name</label>
<input type="text" class="form-control" name="account_name" id="account_name" readonly required>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center">
<label for="amount" class="form-label mb-0">Amount (NGN)</label>
<span class="text-primary" id="balance_info"></span>
</div>
<input type="number" class="form-control" name="amount" id="amount" required min="100" step="1" />
<div class="insufficient-warning" id="insufficient_warning">Insufficient balance!</div>
<div class="hint mt-1" id="fee_hint">
<?php if ($addWithdrawFee): ?>
We’ll add Paystack transfer fee (≈ ₦10 / ₦25 / ₦50 tier) to your debit.
<?php else: ?>
You’ll be debited only the amount; we cover Paystack transfer fee.
<?php endif; ?>
</div>
</div>
<div class="mb-3">
<label for="reason" class="form-label">Remark <span class="text-muted">(Optional)</span></label>
<input type="text" class="form-control" name="reason" id="reason" maxlength="120">
</div>
<button type="submit" id="sendBtn" class="btn btn-success w-100" disabled>Send Naira</button>
</form>
</div>
</main>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const bankInput = document.getElementById('bank_search');
const bankSuggestions = document.getElementById('bank_suggestions');
const hiddenBankCode = document.getElementById('bank_code');
const bankList = <?= json_encode($bankOptions, JSON_UNESCAPED_UNICODE) ?>;
const walletSelect = document.getElementById('wallet_id');
const accountInput = document.getElementById('account_number');
const accountNameInput = document.getElementById('account_name');
const amountInput = document.getElementById('amount');
const reasonInput = document.getElementById('reason');
const sendBtn = document.getElementById('sendBtn');
const balanceInfo = document.getElementById('balance_info');
const warningDiv = document.getElementById('insufficient_warning');
const errorDiv = document.getElementById('account_error_message');
const ADD_WITHDRAW_FEE = <?= $addWithdrawFee ? 'true' : 'false' ?>;
let availableBalance = 0;
function updateBalanceInfo() {
const opt = walletSelect.options[walletSelect.selectedIndex];
availableBalance = parseFloat(opt?.getAttribute('data-balance')) || 0;
balanceInfo.textContent = availableBalance ? `Bal: ₦${availableBalance.toLocaleString(undefined,{minimumFractionDigits:2})}` : "";
}
if (walletSelect.value) updateBalanceInfo();
walletSelect.addEventListener('change', ()=>{ updateBalanceInfo(); checkForm(); });
accountInput.addEventListener('keypress', (e) => {
const ch = String.fromCharCode(e.which);
if (!/[0-9]/.test(ch)) e.preventDefault();
});
amountInput.addEventListener('input', () => { checkForm(); });
// client-side *estimate* of Paystack fee, to gate the button & warning only
function estimatePaystackFee(naira) {
if (!ADD_WITHDRAW_FEE) return 0;
if (!(naira > 0)) return 0;
if (naira <= 5000) return 10;
if (naira <= 50000) return 25;
return 50;
}
// Autocomplete for bank name
bankInput.addEventListener('input', function() {
const q = this.value.toLowerCase().trim();
bankSuggestions.innerHTML = '';
hiddenBankCode.value = '';
if (!q.length) { bankSuggestions.classList.add('d-none'); checkForm(); return; }
const matches = (bankList || []).filter(b => (b.name || '').toLowerCase().includes(q));
if (matches.length) {
matches.forEach(b => {
const div = document.createElement('div');
div.classList.add('autocomplete-item');
div.textContent = b.name;
div.dataset.bankCode = b.code;
div.addEventListener('click', () => {
bankInput.value = b.name;
hiddenBankCode.value = b.code;
bankSuggestions.classList.add('d-none');
tryResolveAccount();
checkForm();
});
bankSuggestions.appendChild(div);
});
bankSuggestions.classList.remove('d-none');
} else {
bankSuggestions.classList.add('d-none');
}
checkForm();
});
document.addEventListener('click', (e)=>{
if (!bankSuggestions.contains(e.target) && e.target !== bankInput) bankSuggestions.classList.add('d-none');
});
accountInput.addEventListener('input', tryResolveAccount);
function tryResolveAccount() {
accountInput.classList.remove('is-invalid');
errorDiv.classList.add('d-none'); errorDiv.textContent = '';
if (accountInput.value.length === 10 && hiddenBankCode.value) {
fetch('../../models/fiat/resolve_account.php?account_number=' + encodeURIComponent(accountInput.value) + '&bank_code=' + encodeURIComponent(hiddenBankCode.value))
.then(r => r.json())
.then(data => {
if (data.status && data.data?.account_name) {
accountNameInput.value = data.data.account_name;
accountInput.classList.remove('is-invalid');
errorDiv.classList.add('d-none'); errorDiv.textContent = '';
} else {
accountNameInput.value = '';
accountInput.classList.add('is-invalid');
errorDiv.textContent = 'Account could not be resolved.';
errorDiv.classList.remove('d-none');
}
checkForm();
})
.catch(()=>{
accountNameInput.value = '';
accountInput.classList.add('is-invalid');
errorDiv.textContent = 'Error connecting to Paystack.';
errorDiv.classList.remove('d-none');
checkForm();
});
} else {
accountNameInput.value = '';
accountInput.classList.remove('is-invalid');
errorDiv.classList.add('d-none'); errorDiv.textContent = '';
checkForm();
}
}
function checkForm() {
const amt = parseFloat(amountInput.value || '0');
const estFee = estimatePaystackFee(amt);
const total = ADD_WITHDRAW_FEE ? (amt + estFee) : amt;
if (total > availableBalance) warningDiv.style.display = 'block';
else warningDiv.style.display = 'none';
const ok = walletSelect.value && hiddenBankCode.value &&
accountInput.value.length === 10 &&
accountNameInput.value &&
amt >= 100 &&
total <= availableBalance;
sendBtn.disabled = !ok;
}
accountNameInput.addEventListener('input', checkForm);
reasonInput.addEventListener('input', checkForm);
});
</script>
<?php include '../common/footer.php'; ?>
Выполнить команду
Для локальной разработки. Не используйте в интернете!