PHP WebShell

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

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

<?php
// user/crypto/buy.php
include '../common/header.php';
include '../../models/crypto/buy_crypto.php'; // provides $wallets, $buy_limit_usd
require_once '../../config/db_config.php';    // $paystackPublic

$paystack_public_key = $paystackPublic ?? '';
if (empty($paystack_public_key)) {
  $paystack_public_key = 'pk_live_6ae29fb1abed2559c70edc72d79001704e488772';
}

$paystack_channels = ['bank_transfer', 'card', 'ussd', 'mobile_money'];
?>

<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 id="topAlerts"></div>

      <?php if (!empty($_SESSION['flash_success'])): ?>
        <div class="alert alert-success text-break"><?= htmlspecialchars($_SESSION['flash_success']); ?></div>
        <?php unset($_SESSION['flash_success']); ?>
      <?php endif; ?>

      <?php if (!empty($_SESSION['flash_error'])): ?>
        <div class="alert alert-danger text-break"><?= htmlspecialchars($_SESSION['flash_error']); ?></div>
        <?php unset($_SESSION['flash_error']); ?>
      <?php endif; ?>

      <div class="row justify-content-center">
        <div class="col-md-7 col-lg-6">

          <div class="card shadow-sm border-0">
            <div class="card-body p-4">

              <h5 class="fw-bold mb-1">Buy Crypto</h5>
              <div class="text-muted small mb-4">
                Select a wallet, enter USD amount, and preview the cost in NGN.
              </div>

              <form method="post" id="buyCryptoForm" novalidate>
                <div class="mb-3">
                  <label class="form-label fw-semibold">Select wallet to fund</label>
                  <select class="form-select" name="wallet_id" id="wallet_id" required>
                    <option value="">Select crypto wallet</option>
                    <?php foreach ($wallets as $w): ?>
                      <option value="<?= htmlspecialchars($w['wallet_id']); ?>"
                              data-coin="<?= htmlspecialchars(strtoupper($w['coin'])); ?>">
                        <?= htmlspecialchars(($w['label'] ?: $w['coin']) . ' (' . strtoupper($w['coin']) . ')'); ?>
                      </option>
                    <?php endforeach; ?>
                  </select>
                </div>

                <div class="mb-3">
                  <label class="form-label fw-semibold">Amount (USD)</label>
                  <input type="number" class="form-control" name="amount_usd" id="amount_usd"
                         min="0" step="0.01" placeholder="e.g. 50" required>
                  <div class="form-text">
                    <?php if ($buy_limit_usd !== null): ?>
                      Your purchase limit: $<?= number_format((float)$buy_limit_usd, 2); ?>.
                    <?php endif; ?>
                    <span id="minSwapHint"></span>
                  </div>
                </div>

                <!-- Preview -->
                <div class="bg-light rounded-3 p-3 mb-3">
                  <div class="d-flex justify-content-between">
                    <div class="text-muted">Estimated cost (NGN)</div>
                    <div class="fw-bold" id="previewNgn">—</div>
                  </div>

                  <div class="d-flex justify-content-between mt-2">
                    <div class="text-muted">Payment Gateway Fees</div>
                    <div class="fw-semibold" id="previewFee">—</div>
                  </div>

                  <div class="d-flex justify-content-between mt-2">
                    <div class="text-muted">Total Payable (NGN)</div>
                    <div class="fw-bold" id="previewTotal">—</div>
                  </div>

                  <hr class="my-2">

                  <div class="d-flex justify-content-between mt-2">
                    <div class="text-muted">Estimated crypto amount</div>
                    <div class="fw-semibold" id="previewCrypto">—</div>
                  </div>

                  <div class="small text-muted mt-2" id="previewHint"></div>
                </div>

                <button type="submit" class="btn btn-primary w-100 fw-bold" id="buyNowBtn" disabled>
                  Buy Now
                </button>
              </form>

            </div>
          </div>

        </div>
      </div>

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

<script src="https://js.paystack.co/v1/inline.js"></script>
<script>
(function(){
  const PAYSTACK_KEY = '<?= addslashes($paystack_public_key); ?>';
  const PAYSTACK_CHANNELS = <?= json_encode($paystack_channels); ?>;

  const form          = document.getElementById('buyCryptoForm');
  const walletSel     = document.getElementById('wallet_id');
  const amountInp     = document.getElementById('amount_usd');
  const btn           = document.getElementById('buyNowBtn');

  const previewNgn    = document.getElementById('previewNgn');
  const previewFee    = document.getElementById('previewFee');
  const previewTotal  = document.getElementById('previewTotal');
  const previewCrypto = document.getElementById('previewCrypto');
  const previewHint   = document.getElementById('previewHint');
  const minSwapHint   = document.getElementById('minSwapHint');
  const alertsBox     = document.getElementById('topAlerts');

  function moneyNGN(n){
    return '₦' + Number(n).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  }
  function fmtQty(qty, coin){
    if (!isFinite(qty)) return '—';
    const s = qty.toLocaleString(undefined, { maximumFractionDigits: 8 });
    return s + ' ' + coin;
  }
  function setDisabled(disabled){
    btn.disabled = !!disabled;
  }

  let timer = null;

  async function updateQuote(){
    const wallet_id = (walletSel.value || '').trim();
    const amount_usd = Number(amountInp.value);

    if (!wallet_id || !(amount_usd > 0)) {
      previewNgn.textContent = '—';
      previewFee.textContent = '—';
      previewTotal.textContent = '—';
      previewCrypto.textContent = '—';
      previewHint.textContent = '';
      minSwapHint.textContent = '';
      setDisabled(true);
      return;
    }

    try {
      const url = '../../models/crypto/buy_crypto.php?action=quote&wallet_id=' + encodeURIComponent(wallet_id)
                + '&amount_usd=' + encodeURIComponent(amount_usd)
                + '&ts=' + Date.now();

      const res = await fetch(url, { credentials: 'include' });
      if (!res.ok) throw new Error('bad response');
      const data = await res.json();
      if (!data.ok) throw new Error('bad json');

      const ms = Number(data.limits?.min_swap_usd || 0);
      minSwapHint.textContent = ms > 0 ? (' Minimum: $' + ms.toFixed(2) + '.') : '';

      const ngn_cost = data.preview?.ngn_cost;
      const gateway_fee = data.preview?.gateway_fee_ngn;
      const total_payable = data.preview?.total_payable_ngn;

      const coin_qty = data.preview?.coin_qty;
      const coin     = data.coin || '';

      previewNgn.textContent = (ngn_cost != null) ? moneyNGN(ngn_cost) : '—';
      previewFee.textContent = (gateway_fee != null) ? moneyNGN(gateway_fee) : '—';
      previewTotal.textContent = (total_payable != null) ? moneyNGN(total_payable) : '—';
      previewCrypto.textContent = (coin_qty != null && coin) ? fmtQty(coin_qty, coin) : '—';

      previewHint.textContent = '';
      if (data.reasons && data.reasons.length) {
        if (data.reasons.includes('min_swap')) previewHint.textContent = 'Amount is below minimum.';
        else if (data.reasons.includes('buy_limit')) previewHint.textContent = 'Buy limit exceeded for Customer Level.';
        else if (data.reasons.includes('no_quote')) previewHint.textContent = 'Unable to fetch quote right now.';
      }

      setDisabled(!data.ready);

    } catch (e) {
      previewNgn.textContent = '—';
      previewFee.textContent = '—';
      previewTotal.textContent = '—';
      previewCrypto.textContent = '—';
      previewHint.textContent = 'Unable to fetch quote right now.';
      setDisabled(true);
    }
  }

  function scheduleQuote(){
    if (timer) clearTimeout(timer);
    timer = setTimeout(updateQuote, 250);
  }

  walletSel.addEventListener('change', updateQuote);
  amountInp.addEventListener('input', scheduleQuote);

  form.addEventListener('submit', async function(e){
    e.preventDefault();

    const wallet_id  = (walletSel.value || '').trim();
    const amount_usd = Number(amountInp.value);

    if (!wallet_id || !(amount_usd > 0) || btn.disabled) return;

    if (!PAYSTACK_KEY) {
      previewHint.textContent = 'Payment is temporarily unavailable.';
      return;
    }

    btn.disabled = true;
    const oldBtnText = btn.textContent;
    btn.textContent = 'Processing...';
    previewHint.textContent = '';

    try {
      const body = new URLSearchParams();
      body.set('action', 'init_paystack');
      body.set('wallet_id', wallet_id);
      body.set('amount_usd', amount_usd);

      const res = await fetch('../../models/crypto/buy_crypto.php', {
        method: 'POST',
        credentials: 'include',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: body.toString()
      });

      const raw = await res.text();
      let data = null;
      try { data = JSON.parse(raw); } catch (err) { data = null; }

      if (!data || !data.ok || !data.reference || !data.amount_kobo || !data.email) {
        previewHint.textContent = (data && data.msg) ? data.msg : 'Unable to start payment right now.';
        btn.textContent = oldBtnText;
        await updateQuote();
        return;
      }

      btn.textContent = oldBtnText;

      // ✅ NO metadata passed to Paystack at all.
      const handler = PaystackPop.setup({
        key: PAYSTACK_KEY,
        email: data.email,
        amount: Number(data.amount_kobo),
        currency: (data.currency || 'NGN'),
        ref: data.reference,
        channels: PAYSTACK_CHANNELS,
        callback: function(response){
          const ref = (response && response.reference) ? response.reference : data.reference;
          window.location.href = '/user/crypto/buy_confirm.php?reference=' + encodeURIComponent(ref);
        },
        onClose: function(){
          updateQuote();
        }
      });

      handler.openIframe();

    } catch (err) {
      previewHint.textContent = 'Unable to start payment right now.';
      btn.textContent = oldBtnText;
      await updateQuote();
    }
  });

  updateQuote();
})();
</script>

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

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


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