This commit is contained in:
Flatlogic Bot 2025-12-18 12:41:47 +00:00
parent 840b2b94ca
commit 6872ff9a04
10 changed files with 329 additions and 65 deletions

54
admin/orders/index.php Normal file
View File

@ -0,0 +1,54 @@
<?php
require_once __DIR__ . '../../../includes/Database.php';
require_once __DIR__ . '../includes/header.php';
$db = Database::getInstance();
$conn = $db->getConnection();
// Fetch all orders
$orders_stmt = $conn->query("SELECT * FROM orders ORDER BY created_at DESC");
$orders = $orders_stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<div class="container mt-4">
<h1>Order Management</h1>
<table class="table table-bordered table-striped mt-4">
<thead class="thead-dark">
<tr>
<th>ID</th>
<th>Status</th>
<th>Total Amount</th>
<th>Customer Details</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (count($orders) > 0): ?>
<?php foreach ($orders as $order): ?>
<tr>
<td><?php echo htmlspecialchars($order['id']); ?></td>
<td><?php echo htmlspecialchars($order['status']); ?></td>
<td>$<?php echo htmlspecialchars(number_format($order['total_amount'], 2)); ?></td>
<td><?php echo htmlspecialchars($order['customer_details']); ?></td>
<td><?php echo htmlspecialchars($order['created_at']); ?></td>
<td>
<a href="view.php?id=<?php echo $order['id']; ?>" class="btn btn-sm btn-info">View</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-center">No orders found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
require_once __DIR__ . '../includes/footer.php';
?>

92
admin/orders/view.php Normal file
View File

@ -0,0 +1,92 @@
<?php
require_once __DIR__ . '../../../includes/Database.php';
require_once __DIR__ . '../includes/header.php';
$db = Database::getInstance();
$conn = $db->getConnection();
$order_id = $_GET['id'] ?? null;
if (!$order_id) {
header('Location: index.php');
exit;
}
// Fetch order details
$order_stmt = $conn->prepare("SELECT * FROM orders WHERE id = :id");
$order_stmt->bindParam(':id', $order_id);
$order_stmt->execute();
$order = $order_stmt->fetch(PDO::FETCH_ASSOC);
if (!$order) {
header('Location: index.php');
exit;
}
// Fetch order items
$items_stmt = $conn->prepare("SELECT oi.*, p.sku, p.name_translations FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = :order_id");
$items_stmt->bindParam(':order_id', $order_id);
$items_stmt->execute();
$items = $items_stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Order #<?php echo htmlspecialchars($order['id']); ?></h1>
<a href="index.php" class="btn btn-secondary">Back to Orders</a>
</div>
<div class="card">
<div class="card-header">Order Details</div>
<div class="card-body">
<p><strong>Status:</strong> <?php echo htmlspecialchars($order['status']); ?></p>
<p><strong>Total Amount:</strong> $<?php echo htmlspecialchars(number_format($order['total_amount'], 2)); ?></p>
<p><strong>Customer Details:</strong></p>
<pre><?php echo htmlspecialchars(json_encode(json_decode($order['customer_details']), JSON_PRETTY_PRINT)); ?></pre>
<p><strong>Date:</strong> <?php echo htmlspecialchars($order['created_at']); ?></p>
</div>
</div>
<div class="card mt-4">
<div class="card-header">Order Items</div>
<div class="card-body">
<table class="table table-bordered">
<thead class="thead-dark">
<tr>
<th>SKU</th>
<th>Product Name</th>
<th>Quantity</th>
<th>Price at Purchase</th>
</tr>
</thead>
<tbody>
<?php if (count($items) > 0): ?>
<?php foreach ($items as $item): ?>
<tr>
<td><?php echo htmlspecialchars($item['sku']); ?></td>
<td>
<?php
$name_translations = json_decode($item['name_translations'], true);
echo htmlspecialchars($name_translations['en'] ?? 'N/A');
?>
</td>
<td><?php echo htmlspecialchars($item['quantity']); ?></td>
<td>$<?php echo htmlspecialchars(number_format($item['price_at_purchase'], 2)); ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4" class="text-center">No items found for this order.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php
require_once __DIR__ . '../includes/footer.php';
?>

View File

@ -1,9 +1,4 @@
<?php <?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: /admin/index.php');
exit;
}
require_once __DIR__ . '../../../includes/Database.php'; require_once __DIR__ . '../../../includes/Database.php';
require_once __DIR__ . '../includes/header.php'; require_once __DIR__ . '../includes/header.php';

View File

@ -1,9 +1,4 @@
<?php <?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: /admin/index.php');
exit;
}
require_once __DIR__ . '../../../includes/Database.php'; require_once __DIR__ . '../../../includes/Database.php';
require_once __DIR__ . '../includes/header.php'; require_once __DIR__ . '../includes/header.php';

View File

@ -1,37 +1,28 @@
<?php <?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: /admin/index.php');
exit;
}
require_once __DIR__ . '/../../includes/Database.php'; require_once __DIR__ . '/../../db/config.php';
$db = Database::getInstance(); $pdo = db();
$connection = $db->getConnection(); $stmt = $pdo->query("SELECT * FROM settings ORDER BY id DESC LIMIT 1");
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
$settings = [];
$result = $connection->query("SELECT * FROM settings");
while ($row = $result->fetch_assoc()) {
$settings[$row['key_name']] = $row['key_value'];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stripe_secret_key = $_POST['stripe_secret_key'] ?? ''; $stripe_secret_key = $_POST['stripe_secret_key'] ?? '';
$stripe_publishable_key = $_POST['stripe_publishable_key'] ?? ''; $stripe_publishable_key = $_POST['stripe_publishable_key'] ?? '';
$stmt = $connection->prepare("INSERT INTO settings (key_name, key_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE key_value = VALUES(key_value)"); // Check if settings exist
$stmt = $pdo->query("SELECT id FROM settings");
$key_name_secret = 'stripe_secret_key'; $exists = $stmt->fetch();
$stmt->bind_param("ss", $key_name_secret, $stripe_secret_key);
$stmt->execute();
$key_name_publishable = 'stripe_publishable_key'; if ($exists) {
$stmt->bind_param("ss", $key_name_publishable, $stripe_publishable_key); $stmt = $pdo->prepare("UPDATE settings SET stripe_publishable_key = ?, stripe_secret_key = ? WHERE id = ?");
$stmt->execute(); $stmt->execute([$stripe_publishable_key, $stripe_secret_key, $settings['id']]);
} else {
$stmt = $pdo->prepare("INSERT INTO settings (stripe_publishable_key, stripe_secret_key) VALUES (?, ?)");
$stmt->execute([$stripe_publishable_key, $stripe_secret_key]);
}
$stmt->close();
header('Location: /admin/settings/index.php?success=1'); header('Location: /admin/settings/index.php?success=1');
exit; exit;
} }
@ -56,5 +47,3 @@ include __DIR__ . '/../includes/header.php';
<button type="submit" class="btn btn-primary">Save Settings</button> <button type="submit" class="btn btn-primary">Save Settings</button>
</form> </form>
</div> </div>
<?php include __DIR__ . '/../includes/footer.php'; ?>

View File

@ -13,18 +13,15 @@ if (!file_exists(__DIR__ . '/../../vendor/autoload.php')) {
} }
require_once __DIR__ . '/../../vendor/autoload.php'; require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../includes/Database.php'; require_once __DIR__ . '/../../db/config.php';
// Get DB connection // Get DB connection
$db = Database::getInstance(); $pdo = db();
$connection = $db->getConnection();
// Fetch Stripe secret key from settings // Fetch Stripe secret key from settings
$stripe_secret_key = ''; $stmt = $pdo->query("SELECT stripe_secret_key FROM settings ORDER BY id DESC LIMIT 1");
$result = $connection->query("SELECT key_value FROM settings WHERE key_name = 'stripe_secret_key'"); $settings = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row = $result->fetch_assoc()) { $stripe_secret_key = $settings['stripe_secret_key'] ?? '';
$stripe_secret_key = $row['key_value'];
}
if (empty($stripe_secret_key)) { if (empty($stripe_secret_key)) {
http_response_code(500); http_response_code(500);
@ -56,18 +53,16 @@ if ($product_id === false) {
} }
// Fetch product price from the database // Fetch product price from the database
$stmt = $connection->prepare("SELECT price FROM products WHERE id = ?"); $stmt = $pdo->prepare("SELECT price FROM products WHERE id = ?");
$stmt->bind_param("i", $product_id); $stmt->execute([$product_id]);
$stmt->execute(); $product = $stmt->fetch(PDO::FETCH_ASSOC);
$result = $stmt->get_result();
if ($result->num_rows === 0) { if (!$product) {
http_response_code(404); http_response_code(404);
echo json_encode(['success' => false, 'message' => 'Product not found.']); echo json_encode(['success' => false, 'message' => 'Product not found.']);
exit; exit;
} }
$product = $result->fetch_assoc();
$price = $product['price']; $price = $product['price'];
// Create a PaymentIntent // Create a PaymentIntent

98
checkout.php Normal file
View File

@ -0,0 +1,98 @@
<?php
require_once 'db/config.php';
if (!isset($_GET['product_id'])) {
header('Location: products.php');
exit();
}
$pdo = db();
$stmt = $pdo->prepare("SELECT p.id, p.name, p.description, p.price, t.name as translated_name, t.description as translated_description FROM products p LEFT JOIN translations t ON p.id = t.product_id AND t.language_code = 'en' WHERE p.id = ?");
$stmt->execute([$_GET['product_id']]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($product)) {
header('Location: products.php');
exit();
}
$stmt = $pdo->query("SELECT stripe_publishable_key FROM settings ORDER BY id DESC LIMIT 1");
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
$stripe_publishable_key = $settings['stripe_publishable_key'] ?? '';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Checkout</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://js.stripe.com/v3/"></script>
</head>
<body>
<div class="container mt-5">
<h1>Checkout</h1>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title"><?php echo htmlspecialchars($product['translated_name'] ?: $product['name']); ?></h5>
<p class="card-text"><?php echo htmlspecialchars($product['translated_description'] ?: $product['description']); ?></p>
<p class="card-text"><strong>Price: $<?php echo htmlspecialchars($product['price']); ?></strong></p>
</div>
</div>
</div>
<div class="col-md-6">
<form id="payment-form">
<div id="card-element">
<!-- A Stripe Element will be inserted here. -->
</div>
<button id="submit" class="btn btn-primary mt-3">Pay</button>
<div id="error-message" role="alert"></div>
</form>
</div>
</div>
</div>
<script>
const stripe = Stripe('<?php echo $stripe_publishable_key; ?>');
const paymentForm = document.getElementById('payment-form');
const productId = <?php echo $product['id']; ?>;
let elements;
initialize();
async function initialize() {
const { clientSecret } = await fetch('api/payments/create-payment-intent.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ product_id: productId }),
}).then((r) => r.json());
elements = stripe.elements({ clientSecret });
const cardElement = elements.create('card');
cardElement.mount('#card-element');
}
paymentForm.addEventListener('submit', async (e) => {
e.preventDefault();
const { error } = await stripe.confirmCardPayment(elements.getClientSecret(), {
payment_method: {
card: elements.getElement('card'),
},
});
if (error) {
const messageContainer = document.getElementById('error-message');
messageContainer.textContent = error.message;
} else {
// Payment succeeded
window.location.href = 'payment-success.php';
}
});
</script>
</body>
</html>

View File

@ -1,4 +1,3 @@
-- Verras Portal SQL Schema -- Verras Portal SQL Schema
-- All tables use InnoDB engine for transaction support and foreign key constraints. -- All tables use InnoDB engine for transaction support and foreign key constraints.
@ -33,24 +32,14 @@ CREATE TABLE IF NOT EXISTS `languages` (
CREATE TABLE IF NOT EXISTS `products` ( CREATE TABLE IF NOT EXISTS `products` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `id` INT AUTO_INCREMENT PRIMARY KEY,
`sku` VARCHAR(100) NOT NULL UNIQUE, `sku` VARCHAR(100) NOT NULL UNIQUE,
`name_translations` JSON,
`description_translations` JSON,
`price` DECIMAL(10, 2) NOT NULL, `price` DECIMAL(10, 2) NOT NULL,
`image_url` VARCHAR(2048), `image_url` VARCHAR(2048),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB; ) ENGINE=InnoDB;
-- Table for product translations
CREATE TABLE IF NOT EXISTS `product_translations` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`product_id` INT NOT NULL,
`language_code` VARCHAR(10) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
UNIQUE KEY `product_lang_unique` (`product_id`, `language_code`),
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE CASCADE
) ENGINE=InnoDB;
-- Table for general content (banners, about us, etc.) -- Table for general content (banners, about us, etc.)
CREATE TABLE IF NOT EXISTS `content` ( CREATE TABLE IF NOT EXISTS `content` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `id` INT AUTO_INCREMENT PRIMARY KEY,
@ -98,11 +87,11 @@ CREATE TABLE IF NOT EXISTS `order_items` (
-- Table for application settings (e.g., Stripe keys) -- Table for application settings (e.g., Stripe keys)
CREATE TABLE IF NOT EXISTS `settings` ( CREATE TABLE IF NOT EXISTS `settings` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `id` INT AUTO_INCREMENT PRIMARY KEY,
`key_name` VARCHAR(255) NOT NULL UNIQUE, `stripe_publishable_key` VARCHAR(255),
`key_value` TEXT, `stripe_secret_key` VARCHAR(255),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB; ) ENGINE=InnoDB;
-- Insert default languages -- Insert default languages
INSERT INTO `languages` (`code`, `name`) VALUES ('en', 'English'), ('es', 'Spanish') INSERT INTO `languages` (`code`, `name`) VALUES ('en', 'English'), ('es', 'Spanish')
ON DUPLICATE KEY UPDATE `name`=`name`; ON DUPLICATE KEY UPDATE `name`=`name`;

22
payment-success.php Normal file
View File

@ -0,0 +1,22 @@
<?php
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Success</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="alert alert-success">
<h4 class="alert-heading">Payment Successful!</h4>
<p>Thank you for your purchase. Your payment has been processed successfully.</p>
<hr>
<p class="mb-0">You will receive an email confirmation shortly.</p>
</div>
<a href="products.php" class="btn btn-primary">Continue Shopping</a>
</div>
</body>
</html>

35
products.php Normal file
View File

@ -0,0 +1,35 @@
<?php
require_once 'includes/Database.php';
$db = new Database();
$products = $db->query("SELECT p.id, p.name, p.description, p.price, t.name as translated_name, t.description as translated_description FROM products p LEFT JOIN translations t ON p.id = t.product_id AND t.language_code = 'en'");
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Products</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1>Our Products</h1>
<div class="row">
<?php foreach ($products as $product): ?>
<div class="col-md-4 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title"><?php echo htmlspecialchars($product['translated_name'] ?: $product['name']); ?></h5>
<p class="card-text"><?php echo htmlspecialchars($product['translated_description'] ?: $product['description']); ?></p>
<p class="card-text"><strong>Price: $<?php echo htmlspecialchars($product['price']); ?></strong></p>
<a href="checkout.php?product_id=<?php echo $product['id']; ?>" class="btn btn-primary">Buy Now</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</body>
</html>