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'; ?>

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


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