PNG IHDR x sBIT|d pHYs + tEXtSoftware www.inkscape.org< ,tEXtComment
<?php
// secure/cd.php
// Certificates of Deposit page (complete)
require_once "../config.php"; // <-- your PDO connection (must set $pdo)
session_start();
// Auth check
if (!isset($_SESSION['auth'])) {
header("Location: ../login.php");
exit;
}
// Helper: normalize session user id to binary(16)
function toBinaryId($id) {
// if already binary (string length 16), return as-is
if (is_string($id) && strlen($id) === 16) return $id;
// if hex 32 chars, convert to binary
if (is_string($id) && ctype_xdigit($id) && strlen($id) === 32) return hex2bin($id);
// otherwise return as-is (caller must be correct)
return $id;
}
// Helper: present hex id for forms
function toHexId($bin) {
if ($bin === null) return '';
return is_string($bin) && strlen($bin) === 16 ? bin2hex($bin) : (is_string($bin) ? $bin : '');
}
$user_id_raw = $_SESSION['auth'];
$user_id = toBinaryId($user_id_raw); // binary(16) expected
// Basic variables
$message = '';
$messageType = ''; // 'success'|'error'
// Fetch accounts
$stmt = $pdo->prepare("SELECT id, account_number, currency, balance FROM accounts WHERE user_id = ?");
$stmt->execute([$user_id]);
$accounts_raw = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Convert accounts for display (id => hex)
$accounts = [];
foreach ($accounts_raw as $a) {
$accounts[] = [
'id_hex' => toHexId($a['id']),
'id_bin' => $a['id'],
'account_number' => $a['account_number'],
'currency' => $a['currency'],
'balance' => $a['balance']
];
}
// Handle POST (create CD)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
// sanitize inputs
$account_id_hex = $_POST['account_id'] ?? '';
$amount = isset($_POST['amount']) ? floatval($_POST['amount']) : 0;
$term_months = isset($_POST['term_months']) ? intval($_POST['term_months']) : 0;
$auto_renew = isset($_POST['auto_renew']) ? 1 : 0;
if (!$account_id_hex || $amount <= 0 || $term_months <= 0) {
throw new Exception("Please fill all fields with valid values.");
}
// Minimum deposit (example)
$min_deposit = 100.00;
if ($amount < $min_deposit) {
throw new Exception("Minimum deposit is {$min_deposit}.");
}
// Convert account id hex -> binary
$account_id_bin = hex2bin($account_id_hex);
if ($account_id_bin === false) throw new Exception("Invalid account selected.");
// Verify ownership & balance
$stmt = $pdo->prepare("SELECT id, account_number, currency, balance FROM accounts WHERE id = ? AND user_id = ?");
$stmt->execute([$account_id_bin, $user_id]);
$account = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$account) throw new Exception("Invalid account selected.");
if ($account['balance'] < $amount) throw new Exception("Insufficient balance in selected account.");
// APY by term
$apy_rates = [
3 => 2.50,
6 => 3.00,
12 => 3.75,
24 => 4.25,
36 => 4.75,
60 => 5.00
];
$apy = $apy_rates[$term_months] ?? 2.00;
// Maturity calculation (simple interest for demonstration)
$maturity_date = (new DateTime())->add(new DateInterval("P{$term_months}M"))->format('Y-m-d');
$interest_earned = ($amount * ($apy / 100) * ($term_months / 12.0));
$maturity_value = round($amount + $interest_earned, 2);
// Transaction: deduct account and insert CD
$pdo->beginTransaction();
// Deduct account balance
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
$stmt->execute([$amount, $account_id_bin]);
// Insert CD record (binary ids)
$cd_id = random_bytes(16);
$stmt = $pdo->prepare("
INSERT INTO certificates_of_deposit
(id, user_id, account_id, principal_amount, apy, term_months, maturity_date, maturity_value, auto_renew, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', NOW())
");
$stmt->execute([
$cd_id,
$user_id,
$account_id_bin,
$amount,
$apy,
$term_months,
$maturity_date,
$maturity_value,
$auto_renew ? 1 : 0
]);
$pdo->commit();
$message = "Certificate of Deposit created successfully. Maturity: " . (new DateTime($maturity_date))->format('M j, Y');
$messageType = 'success';
// refresh accounts and cds below after commit
$stmt = $pdo->prepare("SELECT id, account_number, currency, balance FROM accounts WHERE user_id = ?");
$stmt->execute([$user_id]);
$accounts_raw = $stmt->fetchAll(PDO::FETCH_ASSOC);
$accounts = [];
foreach ($accounts_raw as $a) {
$accounts[] = [
'id_hex' => toHexId($a['id']),
'id_bin' => $a['id'],
'account_number' => $a['account_number'],
'currency' => $a['currency'],
'balance' => $a['balance']
];
}
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
$message = $e->getMessage();
$messageType = 'error';
}
}
// Fetch active CDs for this user
$stmt = $pdo->prepare("
SELECT cd.*, a.account_number, a.currency,
DATEDIFF(cd.maturity_date, NOW()) AS days_until_maturity
FROM certificates_of_deposit cd
JOIN accounts a ON cd.account_id = a.id
WHERE cd.user_id = ?
ORDER BY cd.created_at DESC
");
$stmt->execute([$user_id]);
$cds = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Totals
$total_principal = 0.0;
$total_maturity = 0.0;
foreach ($cds as $cd) {
if ($cd['status'] === 'active') {
$total_principal += floatval($cd['principal_amount']);
$total_maturity += floatval($cd['maturity_value']);
}
}
$total_interest = $total_maturity - $total_principal;
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Certificates of Deposit | Chasedvault</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
<style>
:root{
--brand-1:#0b2545;
--brand-2:#005fa3;
--muted:#6b7280;
}
*{box-sizing:border-box}
body{font-family:Inter,system-ui,Arial; margin:0; background:#f3f4f6; color:#222}
header{background:linear-gradient(90deg,var(--brand-1),var(--brand-2));color:#fff;padding:18px 20px;display:flex;justify-content:space-between;align-items:center}
header h1{margin:0;font-size:18px;font-weight:600}
header nav a{color:rgba(255,255,255,0.92);margin-left:14px;text-decoration:none;font-weight:600}
.wrap{max-width:1020px;margin:28px auto;padding:22px;background:#fff;border-radius:12px;box-shadow:0 6px 30px rgba(2,6,23,0.06)}
.subtitle{color:var(--muted);margin-bottom:18px}
.summary-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-bottom:20px}
.card{padding:18px;border-radius:10px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}
.card.teal{background:linear-gradient(135deg,#00b4db,#0083b0)}
.card h3{margin:0;font-size:14px;opacity:0.92}
.card .val{font-size:20px;margin-top:10px;font-weight:700}
form{background:#fbfdff;border:1px solid #eef2f6;padding:18px;border-radius:10px;margin-bottom:22px}
label{display:block;font-weight:600;color:#123;color:#233;padding:6px 0 6px 0}
select,input{width:100%;padding:10px;border-radius:8px;border:1px solid #d6dbe3;font-size:15px}
.row{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}
.actions{display:flex;gap:12px;align-items:center;margin-top:12px}
.btn{background:var(--brand-1);color:#fff;padding:12px 16px;border-radius:10px;border:0;cursor:pointer;font-weight:700}
.btn.secondary{background:#f1f5f9;color:#0b2545;border:1px solid #dbe6f2}
table{width:100%;border-collapse:collapse;margin-top:12px}
th,td{padding:12px;text-align:center;border-bottom:1px solid #eee}
th{background:#f8fafc;color:#333;font-weight:700}
.message{padding:12px;border-radius:10px;margin-bottom:14px}
.success{background:#eaf8f1;color:#07572b;border:1px solid #cfead7}
.error{background:#fdecef;color:#7a1b1f;border:1px solid #f5c6c9}
.footer{margin-top:18px;text-align:center;color:var(--muted);font-size:13px}
@media(max-width:720px){.row{grid-template-columns:1fr}.summary-grid{grid-template-columns:1fr}}
/* modal */
.modal { position: fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,0.4); z-index:9999 }
.modal .box{background:#fff;padding:22px;border-radius:12px;max-width:520px;width:92%;box-shadow:0 8px 40px rgba(2,6,23,0.2);text-align:center}
.modal .box h3{margin:0 0 8px}
.modal .ok{margin-top:14px;background:var(--brand-2);color:#fff;padding:10px 16px;border-radius:8px;border:0;cursor:pointer}
</style>
</head>
<body>
<header>
<h1>Certificates of Deposit</h1>
<nav>
<a href="index.php">Dashboard</a>
<a href="investment.php">Investments</a>
<a href="savings.php">Savings</a>
<a href="logout.php">Logout</a>
</nav>
</header>
<div class="wrap">
<p class="subtitle">Lock funds for a fixed term and earn higher returns — choose an account, term and amount.</p>
<?php if ($message): ?>
<div class="message <?= $messageType === 'success' ? 'success' : 'error' ?>">
<?= htmlspecialchars($message) ?>
</div>
<?php endif; ?>
<!-- summary -->
<div class="summary-grid">
<div class="card">
<h3>Total Principal</h3>
<div class="val">$<?= number_format($total_principal, 2) ?></div>
</div>
<div class="card teal">
<h3>Total Expected Maturity</h3>
<div class="val">$<?= number_format($total_maturity, 2) ?></div>
</div>
<div class="card" style="background:linear-gradient(135deg,#11998e,#38ef7d)">
<h3>Expected Interest</h3>
<div class="val">$<?= number_format($total_interest, 2) ?></div>
</div>
</div>
<!-- form -->
<form method="post" id="cdForm" onsubmit="return confirmCreate();">
<h3 style="margin-top:0">Open a New CD</h3>
<label for="account_id">Select Funding Account</label>
<select name="account_id" id="account_id" required>
<option value="">-- choose account --</option>
<?php foreach ($accounts as $a): ?>
<option value="<?= htmlspecialchars($a['id_hex']) ?>">
<?= htmlspecialchars($a['currency']) ?> • <?= htmlspecialchars($a['account_number']) ?> — Balance: <?= htmlspecialchars($a['currency']) ?> <?= number_format($a['balance'], 2) ?>
</option>
<?php endforeach; ?>
</select>
<div class="row" style="margin-top:10px">
<div>
<label for="amount">Deposit Amount (min <?= number_format(100,2) ?>)</label>
<input type="number" name="amount" id="amount" step="0.01" min="0.01" required>
</div>
<div>
<label for="term_months">Term (months)</label>
<select name="term_months" id="term_months" required>
<option value="">-- select term --</option>
<option value="3">3 mo — 2.50% APY</option>
<option value="6">6 mo — 3.00% APY</option>
<option value="12">12 mo — 3.75% APY</option>
<option value="24">24 mo — 4.25% APY</option>
<option value="36">36 mo — 4.75% APY</option>
<option value="60">60 mo — 5.00% APY</option>
</select>
</div>
</div>
<label style="margin-top:10px">
<input type="checkbox" name="auto_renew" value="1"> Auto-renew on maturity
</label>
<div class="actions">
<button type="submit" class="btn">Create CD</button>
<button type="reset" class="btn secondary">Reset</button>
</div>
</form>
<!-- active CDs -->
<h3 style="margin-top:10px">Active Certificates</h3>
<table>
<thead>
<tr>
<th>Account</th>
<th>Principal</th>
<th>APY (%)</th>
<th>Term (mo)</th>
<th>Maturity Value</th>
<th>Days until maturity</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if (count($cds) === 0): ?>
<tr><td colspan="7" style="padding:18px;color:#777">No certificates of deposit.</td></tr>
<?php else: ?>
<?php foreach ($cds as $cd): ?>
<tr>
<td><?= htmlspecialchars($cd['currency']) ?> • <?= htmlspecialchars($cd['account_number']) ?></td>
<td><?= htmlspecialchars($cd['currency']) ?> <?= number_format($cd['principal_amount'], 2) ?></td>
<td><?= number_format($cd['apy'], 2) ?></td>
<td><?= intval($cd['term_months']) ?></td>
<td><?= htmlspecialchars($cd['currency']) ?> <?= number_format($cd['maturity_value'], 2) ?></td>
<td><?= intval($cd['days_until_maturity']) ?></td>
<td style="color:<?= $cd['status']==='active' ? 'green' : '#666' ?>"><?= htmlspecialchars(ucfirst($cd['status'])) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<div class="footer">© <?= date('Y') ?> Chasedvault</div>
</div>
<!-- modal (optional, can be used for JS alerts) -->
<div id="modal" class="modal" role="dialog" aria-hidden="true">
<div class="box">
<h3 id="modalTitle"></h3>
<p id="modalMessage"></p>
<button class="ok" onclick="closeModal()">OK</button>
</div>
</div>
<script>
function showModal(title, msg) {
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalMessage').textContent = msg;
document.getElementById('modal').style.display = 'flex';
}
function closeModal(){ document.getElementById('modal').style.display = 'none'; }
function confirmCreate(){
// lightweight client-side check to confirm user understands this will lock funds
const amt = parseFloat(document.getElementById('amount').value || '0');
if (amt <= 0) { showModal('Invalid amount','Please enter a valid deposit amount.'); return false; }
return confirm('Creating this Certificate of Deposit will move the amount from your selected account into the CD. Continue?');
}
</script>
</body>
</html>
b IDATxytVսϓ22 A@IR:hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-E