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'; ?>
Выполнить команду
Для локальной разработки. Не используйте в интернете!