This commit is contained in:
Flatlogic Bot 2025-11-09 01:47:20 +00:00
parent b3bb8a479f
commit 79236554fd
8 changed files with 515 additions and 34 deletions

View File

@ -1,3 +1,29 @@
<?php
session_start();
// If user is not logged in, redirect to login page
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
require_once 'db/config.php';
$user_id = $_SESSION['user_id'];
$user = null;
$balance = 0;
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT full_name, balance FROM users WHERE id = :id");
$stmt->execute(['id' => $user_id]);
$user = $stmt->fetch();
$balance = $user['balance'] ?? 0;
} catch (PDOException $e) {
// Handle db error
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
@ -16,10 +42,15 @@
<i class="bi bi-wallet2"></i> UBPay
</a>
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="#">
<i class="bi bi-person-circle"></i> Profile
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> <?php echo htmlspecialchars($user['full_name'] ?? 'User'); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Profile</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</li>
</ul>
</div>
@ -28,7 +59,7 @@
<main class="container mt-4">
<div class="row">
<div class="col-12">
<h1 class="h3 mb-4">Welcome, User!</h1>
<h1 class="h3 mb-4">Welcome, <?php echo htmlspecialchars(explode(' ', $user['full_name'])[0] ?? 'User'); ?>!</h1>
</div>
</div>
@ -38,7 +69,7 @@
<div class="card text-white" style="background: linear-gradient(135deg, #00A859 0%, #007B5F 100%);">
<div class="card-body">
<h5 class="card-title">Wallet Balance</h5>
<p class="display-4 fw-bold">R1,250.75</p>
<p class="display-4 fw-bold">R<?php echo number_format($balance, 2); ?></p>
<p class="card-text text-white-50">Available Funds</p>
</div>
</div>
@ -50,8 +81,8 @@
<div class="card-body">
<h5 class="card-title mb-3">Quick Actions</h5>
<div class="d-grid gap-2 d-sm-flex">
<button class="btn btn-primary flex-fill"><i class="bi bi-send"></i> Send Money</button>
<button class="btn btn-secondary flex-fill"><i class="bi bi-shop"></i> Pay Merchant</button>
<a href="send-money.php" class="btn btn-primary flex-fill"><i class="bi bi-send"></i> Send Money</a>
<a href="pay-merchant.php" class="btn btn-secondary flex-fill"><i class="bi bi-shop"></i> Pay Merchant</a>
<button class="btn btn-info flex-fill"><i class="bi bi-phone"></i> Buy Airtime</button>
</div>
</div>
@ -66,37 +97,23 @@
<div class="card-body">
<h5 class="card-title">Recent Transactions</h5>
<?php
require_once 'db/config.php';
try {
$pdo = db();
// Create transactions table if it doesn't exist
// Create table if not exists
$pdo->exec("CREATE TABLE IF NOT EXISTS transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
description VARCHAR(255) NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
type VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)");
// Clear existing transactions and insert sample data for demonstration
$pdo->exec("TRUNCATE TABLE transactions");
$transactions = [
['Payment to Shoprite', -120.50, 'Merchant Payment'],
['Received from J. Doe', 250.00, 'P2P Transfer'],
['Airtime Purchase (MTN)', -50.00, 'Bill Payment'],
['Payment to Pick n Pay', -340.75, 'Merchant Payment'],
['Received from A. Smith', 500.00, 'P2P Transfer'],
];
$stmt = $pdo->prepare("INSERT INTO transactions (description, amount, type) VALUES (?, ?, ?)");
foreach ($transactions as $tx) {
$stmt->execute($tx);
}
// Fetch transactions
$stmt = $pdo->query("SELECT description, amount, type, created_at FROM transactions ORDER BY created_at DESC");
// Fetch transactions for the logged-in user
$stmt = $pdo->prepare("SELECT description, amount, type, notes, created_at FROM transactions WHERE user_id = :user_id ORDER BY created_at DESC LIMIT 10");
$stmt->execute(['user_id' => $user_id]);
$transactions = $stmt->fetchAll();
if (count($transactions) > 0) {
@ -112,6 +129,9 @@
echo '<i class="bi ' . $icon . ' ' . $amount_class . '"></i>';
echo '<strong class="ms-2">' . htmlspecialchars($tx['description']) . '</strong>';
echo '<small class="d-block text-muted">' . htmlspecialchars($tx['type']) . '</small>';
if (!empty($tx['notes'])) {
echo '<small class="d-block text-muted fst-italic">' . htmlspecialchars($tx['notes']) . '</small>';
}
echo '</div>';
echo '<span class="' . $amount_class . ' fw-bold">' . $amount_prefix . ' ' . $formatted_amount . '</span>';
echo '</li>';
@ -121,7 +141,7 @@
echo '<p class="text-muted">No recent transactions.</p>';
}
} catch (PDOException $e) {
echo '<p class="text-danger">Database error: ' . htmlspecialchars($e->getMessage()) . '</p>';
echo '<p class="text-danger">Database error: Could not fetch transactions.</p>';
}
?>
</div>

View File

@ -91,9 +91,9 @@
<button class="btn btn-primary btn-lg" type="submit">Create Account</button>
</div>
</form>
<div class="text-center mt-3">
<a href="dashboard.php">View Dashboard (Bypass Login)</a>
</div>
<p class="text-center mt-3">
Already have an account? <a href="login.php">Login</a>
</p>
</div>
</div>

84
login.php Normal file
View File

@ -0,0 +1,84 @@
<?php
session_start();
ini_set('display_errors', 0);
// If user is already logged in, redirect to dashboard
if (isset($_SESSION['user_id'])) {
header("Location: dashboard.php");
exit();
}
require_once 'db/config.php';
$error_message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$mobile_number = trim($_POST['mobile_number'] ?? '');
$password = $_POST['password'] ?? '';
if (empty($mobile_number) || empty($password)) {
$error_message = 'Please enter both mobile number and password.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT id, password_hash FROM users WHERE mobile_number = :mobile_number");
$stmt->execute(['mobile_number' => $mobile_number]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
// Password is correct, start session
$_SESSION['user_id'] = $user['id'];
header("Location: dashboard.php");
exit();
} else {
$error_message = 'Invalid mobile number or password.';
}
} catch (PDOException $e) {
$error_message = 'An internal error occurred. Please try again later.';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - UBPay</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css">
</head>
<body>
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="card mt-5">
<div class="card-body">
<h3 class="card-title text-center mb-4">Login to UBPay</h3>
<?php if (!empty($error_message)): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="login.php" method="post">
<div class="mb-3">
<label for="mobile_number" class="form-label">Mobile Number</label>
<input type="text" class="form-control" id="mobile_number" name="mobile_number" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Login</button>
</div>
<p class="text-center mt-3">
Don't have an account? <a href="index.php">Register</a>
</p>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

22
logout.php Normal file
View File

@ -0,0 +1,22 @@
<?php
session_start();
// Unset all of the session variables.
$_SESSION = array();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
// Finally, destroy the session.
session_destroy();
// Redirect to login page
header("Location: login.php");
exit();

59
pay-merchant.php Normal file
View File

@ -0,0 +1,59 @@
<?php
session_start();
require_once 'db/config.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$user_id = $_SESSION['user_id'];
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$user_id]);
$user = $stmt->fetch();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pay Merchant - UBPay</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css">
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card mt-5">
<div class="card-body">
<h3 class="card-title text-center mb-4">Pay Merchant</h3>
<div class="text-center mb-4">
<p class="text-muted mb-0">Your current balance:</p>
<h4 class="fw-bold">$<?php echo number_format($user['balance'], 2); ?></h4>
</div>
<form action="process-pay-merchant.php" method="post">
<div class="mb-3">
<label for="merchant-code" class="form-label">Merchant Code</label>
<input type="text" class="form-control" id="merchant-code" name="merchant_code" required>
</div>
<div class="mb-3">
<label for="amount" class="form-label">Amount</label>
<input type="number" class="form-control" id="amount" name="amount" step="0.01" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Pay Now</button>
</div>
<p class="text-center mt-3">
<a href="dashboard.php">Back to Dashboard</a>
</p>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

76
process-pay-merchant.php Normal file
View File

@ -0,0 +1,76 @@
<?php
session_start();
require_once 'db/config.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
// For demonstration, we'll ensure a merchant user exists.
// In a real app, merchants would register separately.
$merchant_email = 'merchant@ubpay.com';
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
$stmt->execute([$merchant_email]);
$merchant = $stmt->fetch();
if (!$merchant) {
$stmt = $pdo->prepare('INSERT INTO users (name, email, password, balance) VALUES (?, ?, ?, ?)');
$stmt->execute(['Default Merchant', $merchant_email, password_hash('password', PASSWORD_DEFAULT), 10000]);
$merchant_id = $pdo->lastInsertId();
} else {
$merchant_id = $merchant['id'];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user_id = $_SESSION['user_id'];
$merchant_code = $_POST['merchant_code']; // In a real app, this would be validated more thoroughly
$amount = filter_input(INPUT_POST, 'amount', FILTER_VALIDATE_FLOAT);
if (!$merchant_code || !$amount || $amount <= 0) {
$_SESSION['error_message'] = 'Invalid input. Please check the merchant code and amount.';
header('Location: pay-merchant.php');
exit;
}
try {
$pdo->beginTransaction();
// Get sender's balance
$stmt = $pdo->prepare('SELECT balance FROM users WHERE id = ? FOR UPDATE');
$stmt->execute([$user_id]);
$sender = $stmt->fetch();
if ($sender['balance'] < $amount) {
$_SESSION['error_message'] = 'Insufficient funds.';
header('Location: pay-merchant.php');
$pdo->rollBack();
exit;
}
// Debit sender
$stmt = $pdo->prepare('UPDATE users SET balance = balance - ? WHERE id = ?');
$stmt->execute([$amount, $user_id]);
// Credit merchant (using the dummy merchant for this example)
$stmt = $pdo->prepare('UPDATE users SET balance = balance + ? WHERE id = ?');
$stmt->execute([$amount, $merchant_id]);
// Record transaction
$stmt = $pdo->prepare('INSERT INTO transactions (sender_id, receiver_id, amount, type, description) VALUES (?, ?, ?, ?, ?)');
$stmt->execute([$user_id, $merchant_id, $amount, 'merchant_payment', 'Payment to merchant ' . htmlspecialchars($merchant_code)]);
$pdo->commit();
$_SESSION['success_message'] = 'Payment of $' . number_format($amount, 2) . ' to merchant ' . htmlspecialchars($merchant_code) . ' was successful.';
header('Location: dashboard.php');
exit;
} catch (Exception $e) {
$pdo->rollBack();
$_SESSION['error_message'] = 'An error occurred. Please try again.';
error_log('Merchant Payment Error: ' . $e->getMessage());
header('Location: pay-merchant.php');
exit;
}
}

91
process-send-money.php Normal file
View File

@ -0,0 +1,91 @@
<?php
session_start();
require_once 'db/config.php';
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
// Check if 'notes' column exists and add it if not
try {
$pdo->query("SELECT notes FROM transactions LIMIT 1");
} catch (PDOException $e) {
if ($e->getCode() == '42S22') { // Column not found
$pdo->exec("ALTER TABLE transactions ADD COLUMN notes TEXT");
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$sender_id = $_SESSION['user_id'];
$recipient_mobile = $_POST['recipient'];
$amount = (float)$_POST['amount'];
$notes = !empty($_POST['notes']) ? trim($_POST['notes']) : null;
// Validate amount
if ($amount <= 0) {
$_SESSION['message'] = "Invalid amount.";
$_SESSION['message_type'] = "danger";
header("Location: send-money.php");
exit;
}
try {
$pdo->beginTransaction();
// Get sender
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ? FOR UPDATE");
$stmt->execute([$sender_id]);
$sender = $stmt->fetch();
// Get recipient
$stmt = $pdo->prepare("SELECT * FROM users WHERE mobile = ? FOR UPDATE");
$stmt->execute([$recipient_mobile]);
$recipient = $stmt->fetch();
if (!$recipient) {
throw new Exception("Recipient not found.");
}
if ($sender['id'] === $recipient['id']) {
throw new Exception("You cannot send money to yourself.");
}
if ($sender['balance'] < $amount) {
throw new Exception("Insufficient funds.");
}
// Perform transaction
$new_sender_balance = $sender['balance'] - $amount;
$stmt = $pdo->prepare("UPDATE users SET balance = ? WHERE id = ?");
$stmt->execute([$new_sender_balance, $sender_id]);
$new_recipient_balance = $recipient['balance'] + $amount;
$stmt = $pdo->prepare("UPDATE users SET balance = ? WHERE id = ?");
$stmt->execute([$new_recipient_balance, $recipient['id']]);
// Record transaction
$stmt = $pdo->prepare("INSERT INTO transactions (user_id, type, amount, description, notes) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$sender_id, 'debit', $amount, "Sent money to {$recipient['name']}", $notes]);
$stmt->execute([$recipient['id'], 'credit', $amount, "Received money from {$sender['name']}", $notes]);
$pdo->commit();
$_SESSION['message'] = "Money sent successfully!";
$_SESSION['message_type'] = "success";
header("Location: dashboard.php");
exit;
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$_SESSION['message'] = "Error: " . $e->getMessage();
$_SESSION['message_type'] = "danger";
header("Location: send-money.php");
exit;
}
} else {
header("Location: send-money.php");
exit;
}

129
send-money.php Normal file
View File

@ -0,0 +1,129 @@
<?php
session_start();
require_once 'db/config.php';
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
$user_id = $_SESSION['user_id'];
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Send Money - UBPay</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.css">
</head>
<body style="background-color: #F8F9FA;">
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="dashboard.php" style="color: #00A859; font-weight: bold;">
<i data-feather="dollar-sign" class="me-2"></i>UBPay
</a>
<div class="d-flex">
<a href="dashboard.php" class="btn btn-light">Back to Dashboard</a>
</div>
</div>
</nav>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-sm" style="border-radius: 0.5rem;">
<div class="card-body p-4">
<h2 class="card-title text-center mb-4" style="color: #00A859; font-weight: 600;">Send Money</h2>
<div class="alert alert-info">
Your current balance is: <strong>$<?php echo htmlspecialchars(number_format($user['balance'], 2)); ?></strong>
</div>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-<?php echo $_SESSION['message_type']; ?> alert-dismissible fade show" role="alert">
<?php echo $_SESSION['message']; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php unset($_SESSION['message']); unset($_SESSION['message_type']); ?>
<?php endif; ?>
<form id="send-money-form" action="process-send-money.php" method="POST">
<div class="mb-3">
<label for="recipient" class="form-label">Recipient's Mobile Number</label>
<input type="text" class="form-control" id="recipient" name="recipient" placeholder="Enter mobile number" required>
</div>
<div class="mb-3">
<label for="amount" class="form-label">Amount</label>
<div class="input-group">
<span class="input-group-text" style="color: #00A859;">$</span>
<input type="number" class="form-control" id="amount" name="amount" placeholder="0.00" step="0.01" min="0.01" max="<?php echo $user['balance']; ?>" required>
</div>
</div>
<div class="mb-3">
<label for="notes" class="form-label">Notes (Optional)</label>
<textarea class="form-control" id="notes" name="notes" rows="3" placeholder="Add a note..."></textarea>
</div>
<div class="d-grid">
<button type="button" class="btn btn-primary btn-lg" style="background-color: #00A859; border-color: #00A859;" data-bs-toggle="modal" data-bs-target="#confirmationModal">Send Money</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Confirmation Modal -->
<div class="modal fade" id="confirmationModal" tabindex="-1" aria-labelledby="confirmationModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmationModalLabel">Confirm Transaction</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Please confirm the details of your transaction:</p>
<ul class="list-group">
<li class="list-group-item"><strong>Recipient:</strong> <span id="confirm-recipient"></span></li>
<li class="list-group-item"><strong>Amount:</strong> $<span id="confirm-amount"></span></li>
<li class="list-group-item"><strong>Notes:</strong> <span id="confirm-notes"></span></li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-send-button" style="background-color: #00A859; border-color: #00A859;">Confirm & Send</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script>
feather.replace();
const confirmationModal = document.getElementById('confirmationModal');
confirmationModal.addEventListener('show.bs.modal', function (event) {
const recipient = document.getElementById('recipient').value;
const amount = document.getElementById('amount').value;
const notes = document.getElementById('notes').value;
document.getElementById('confirm-recipient').textContent = recipient;
document.getElementById('confirm-amount').textContent = parseFloat(amount).toFixed(2);
document.getElementById('confirm-notes').textContent = notes || 'N/A';
});
document.getElementById('confirm-send-button').addEventListener('click', function () {
document.getElementById('send-money-form').submit();
});
</script>
</body>
</html>