sad
This commit is contained in:
parent
817751394d
commit
e4e5346c0f
@ -2,6 +2,9 @@
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
use App\Repositories\PurchaseRepository;
|
||||
use App\Repositories\CarRepository;
|
||||
|
||||
if (!isset($_SESSION['user_id']) || ($_SESSION['role'] ?? '') !== 'admin') {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
@ -9,32 +12,40 @@ if (!isset($_SESSION['user_id']) || ($_SESSION['role'] ?? '') !== 'admin') {
|
||||
|
||||
$pdo = db();
|
||||
$message = '';
|
||||
$purchaseRepo = new PurchaseRepository();
|
||||
$carRepo = new CarRepository();
|
||||
|
||||
if (isset($_POST['action']) && isset($_POST['purchase_id'])) {
|
||||
$purchase_id = $_POST['purchase_id'];
|
||||
$action = $_POST['action'];
|
||||
$status = ($action === 'approve') ? 'approved' : 'rejected';
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Update purchase status
|
||||
$stmt = $pdo->prepare("UPDATE purchases SET status = ? WHERE id = ?");
|
||||
$stmt->execute([$status, $purchase_id]);
|
||||
|
||||
if ($status === 'approved') {
|
||||
// Get car ID
|
||||
if ($action === 'approve') {
|
||||
// Admin verifies -> move to held_in_escrow
|
||||
$stmt = $pdo->prepare("UPDATE purchases SET status = 'paid', escrow_status = 'held_in_escrow' WHERE id = ?");
|
||||
$stmt->execute([$purchase_id]);
|
||||
|
||||
// Get car ID and mark as sold
|
||||
$stmt = $pdo->prepare("SELECT car_id FROM purchases WHERE id = ?");
|
||||
$stmt->execute([$purchase_id]);
|
||||
$car_id = $stmt->fetchColumn();
|
||||
$carRepo->markAsSold($car_id);
|
||||
|
||||
// Mark car as sold
|
||||
$stmt = $pdo->prepare("UPDATE cars SET status = 'sold' WHERE id = ?");
|
||||
$stmt->execute([$car_id]);
|
||||
$message = "Transaction verified. Funds are now held in Escrow.";
|
||||
} elseif ($action === 'release') {
|
||||
// Admin releases payment to seller
|
||||
$stmt = $pdo->prepare("UPDATE purchases SET status = 'completed', escrow_status = 'released' WHERE id = ?");
|
||||
$stmt->execute([$purchase_id]);
|
||||
$message = "Payment released to seller. Transaction completed.";
|
||||
} elseif ($action === 'reject') {
|
||||
$stmt = $pdo->prepare("UPDATE purchases SET status = 'failed', escrow_status = 'cancelled' WHERE id = ?");
|
||||
$stmt->execute([$purchase_id]);
|
||||
$message = "Transaction rejected and cancelled.";
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
$message = "Purchase request " . ($status === 'approved' ? 'approved' : 'rejected') . " successfully.";
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
$message = "Error: " . $e->getMessage();
|
||||
@ -43,7 +54,7 @@ if (isset($_POST['action']) && isset($_POST['purchase_id'])) {
|
||||
|
||||
// Fetch all purchases with car and user info
|
||||
$stmt = $pdo->query("
|
||||
SELECT p.*, c.brand, c.model, c.price, c.year, u.name as buyer_user_name, ci.image_path
|
||||
SELECT p.*, c.brand, c.model, c.year, u.name as buyer_user_name, ci.image_path
|
||||
FROM purchases p
|
||||
JOIN cars c ON p.car_id = c.id
|
||||
JOIN users u ON p.user_id = u.id
|
||||
@ -57,7 +68,7 @@ $purchases = $stmt->fetchAll();
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Purchase Requests | Admin</title>
|
||||
<title>Enterprise Transactions | Admin</title>
|
||||
<link rel="stylesheet" href="assets/css/fonts.css">
|
||||
<link rel="stylesheet" href="assets/css/style.css?v=<?= time() ?>">
|
||||
</head>
|
||||
@ -65,11 +76,11 @@ $purchases = $stmt->fetchAll();
|
||||
<div class="dashboard-container">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<a href="index.php" class="sidebar-brand">AFGCARS</a>
|
||||
<a href="index.php" class="sidebar-brand">AFGCARS ENT</a>
|
||||
<ul class="sidebar-menu">
|
||||
<li><a href="admin_dashboard.php"><span>Dashboard</span></a></li>
|
||||
<li><a href="admin_cars.php"><span>Manage Cars</span></a></li>
|
||||
<li><a href="admin_purchases.php" class="active"><span>Purchase Requests</span></a></li>
|
||||
<li><a href="admin_purchases.php" class="active"><span>Transactions</span></a></li>
|
||||
<li><a href="admin_users.php"><span>Users</span></a></li>
|
||||
<li><a href="admin_messages.php"><span>Messages</span></a></li>
|
||||
</ul>
|
||||
@ -81,8 +92,9 @@ $purchases = $stmt->fetchAll();
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<header class="mb-3">
|
||||
<h1 class="fw-bold" style="font-size: 2.5rem;">Purchase Requests</h1>
|
||||
<p class="text-secondary">Verify bank IDs and personal information to approve or reject vehicle transactions.</p>
|
||||
<span class="badge badge-primary mb-1">ENTERPRISE MODE</span>
|
||||
<h1 class="fw-bold" style="font-size: 2.5rem;">Transaction Engine</h1>
|
||||
<p class="text-secondary">Manage Escrow, verify SHA256 tokens, and release payments for secure car marketplace sales.</p>
|
||||
</header>
|
||||
|
||||
<?php if ($message): ?>
|
||||
@ -96,67 +108,70 @@ $purchases = $stmt->fetchAll();
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Vehicle Details</th>
|
||||
<th>Buyer Verification</th>
|
||||
<th>Bank Reference</th>
|
||||
<th>Transaction Amount</th>
|
||||
<th>Current Status</th>
|
||||
<th>Transaction Ref</th>
|
||||
<th>Vehicle</th>
|
||||
<th>Escrow Status</th>
|
||||
<th>Cost Breakdown</th>
|
||||
<th>Verification</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($purchases as $p): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold"><?= htmlspecialchars($p['reference_number'] ?: 'LEGACY-'.$p['id']) ?></div>
|
||||
<div class="text-sm text-secondary" style="font-family: monospace; font-size: 0.7rem;"><?= htmlspecialchars($p['transaction_id'] ?: 'N/A') ?></div>
|
||||
<div class="text-sm text-secondary"><?= date('M d, H:i', strtotime($p['created_at'])) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="display: flex; align-items: center; gap: 1.2rem;">
|
||||
<img src="<?= htmlspecialchars($p['image_path'] ?: 'assets/images/placeholder-car.jpg') ?>" style="width: 90px; height: 60px; object-fit: cover; border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<img src="<?= htmlspecialchars($p['image_path'] ?: 'assets/images/placeholder-car.jpg') ?>" style="width: 70px; height: 50px; object-fit: cover; border-radius: 8px;">
|
||||
<div>
|
||||
<div class="fw-bold"><?= htmlspecialchars($p['brand'] . ' ' . $p['model']) ?></div>
|
||||
<div class="fw-bold text-sm"><?= htmlspecialchars($p['brand']) ?></div>
|
||||
<div class="text-sm text-secondary"><?= $p['year'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold text-sm"><?= htmlspecialchars($p['buyer_name']) ?></div>
|
||||
<div class="text-sm text-secondary"><?= htmlspecialchars($p['buyer_phone']) ?></div>
|
||||
<div class="text-sm text-secondary" style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"><?= htmlspecialchars($p['personal_info']) ?></div>
|
||||
<div class="badge badge-<?= $p['escrow_status'] === 'released' ? 'success' : ($p['escrow_status'] === 'cancelled' ? 'danger' : 'warning') ?>" style="display: block; text-align: center; margin-bottom: 0.3rem;">
|
||||
<?= strtoupper(str_replace('_', ' ', $p['escrow_status'] ?: 'awaiting_verification')) ?>
|
||||
</div>
|
||||
<span class="text-sm text-secondary">Status: <?= ucfirst($p['status']) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<code style="background: rgba(255, 255, 255, 0.05); padding: 0.4rem 0.8rem; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); color: var(--primary-color); font-weight: 700; font-family: monospace;"><?= htmlspecialchars($p['bank_id']) ?></code>
|
||||
<div class="text-sm">Base: <span class="text-white">$<?= number_format($p['base_price'], 0) ?></span></div>
|
||||
<div class="text-sm">Fee: <span class="text-gold">$<?= number_format($p['marketplace_fee'], 0) ?></span></div>
|
||||
<div class="text-gold fw-bold" style="font-size: 1.1rem; border-top: 1px solid rgba(255,255,255,0.1); margin-top: 0.3rem; padding-top: 0.3rem;">Total: $<?= number_format($p['total_amount'] ?: $p['base_price'], 0) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-gold fw-bold">$<?= number_format($p['price']) ?></div>
|
||||
<div class="text-sm fw-bold"><?= htmlspecialchars($p['buyer_name']) ?></div>
|
||||
<div class="text-sm text-secondary">Bank ID: <span class="text-white"><?= htmlspecialchars($p['bank_id'] ?: 'N/A') ?></span></div>
|
||||
<div class="text-sm text-secondary" title="<?= htmlspecialchars($p['verification_token']) ?>">Token: <span style="font-family: monospace; font-size: 0.6rem;"><?= substr($p['verification_token'], 0, 12) ?>...</span></div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-<?= $p['status'] === 'approved' ? 'success' : ($p['status'] === 'rejected' ? 'danger' : 'warning') ?>">
|
||||
<?= ucfirst($p['status']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($p['status'] === 'pending'): ?>
|
||||
<div style="display: flex; gap: 1rem; align-items: center;">
|
||||
<form method="POST" style="display: inline;">
|
||||
<?php if ($p['escrow_status'] === 'awaiting_verification' || !$p['escrow_status']): ?>
|
||||
<div style="display: flex; gap: 0.8rem; flex-direction: column;">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="purchase_id" value="<?= $p['id'] ?>">
|
||||
<button type="submit" name="action" value="approve" class="text-gold text-sm fw-bold btn-sm" style="background: none; border: none; cursor: pointer; padding: 0;">Approve</button>
|
||||
<button type="submit" name="action" value="approve" class="btn btn-primary btn-sm" style="width: 100%;">Verify & Hold</button>
|
||||
</form>
|
||||
<form method="POST" style="display: inline;">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="purchase_id" value="<?= $p['id'] ?>">
|
||||
<button type="submit" name="action" value="reject" class="text-sm fw-bold btn-sm" style="background: none; border: none; cursor: pointer; padding: 0; color: var(--danger);">Reject</button>
|
||||
<button type="submit" name="action" value="reject" class="text-sm fw-bold" style="background: none; border: none; cursor: pointer; color: var(--danger); width: 100%;">Reject</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php elseif ($p['escrow_status'] === 'held_in_escrow'): ?>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="purchase_id" value="<?= $p['id'] ?>">
|
||||
<button type="submit" name="action" value="release" class="btn btn-success btn-sm" style="width: 100%; background-color: #2ed573;">Release Funds</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="text-secondary text-sm fw-bold">Verified</span>
|
||||
<span class="text-secondary text-sm fw-bold">COMPLETED</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($purchases)): ?>
|
||||
<tr>
|
||||
<td colspan="6" style="padding: 4rem; text-align: center;">
|
||||
<p class="text-secondary">No purchase requests waiting for verification.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -164,4 +179,4 @@ $purchases = $stmt->fetchAll();
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
34
app/Controllers/PurchaseController.php
Normal file
34
app/Controllers/PurchaseController.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Services\PurchaseService;
|
||||
use App\Repositories\CarRepository;
|
||||
use Exception;
|
||||
|
||||
class PurchaseController {
|
||||
protected $purchaseService;
|
||||
protected $carRepo;
|
||||
|
||||
public function __construct() {
|
||||
$this->purchaseService = new PurchaseService();
|
||||
$this->carRepo = new CarRepository();
|
||||
}
|
||||
|
||||
public function reserve($carId, $userId) {
|
||||
try {
|
||||
$this->purchaseService->reserveCar($carId, $userId);
|
||||
return ['success' => true, 'message' => 'Car reserved successfully for 15 minutes.'];
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function checkout($carId, $userId, $buyerData) {
|
||||
try {
|
||||
$transactionId = $this->purchaseService->initiatePurchase($carId, $userId, $buyerData);
|
||||
return ['success' => true, 'transaction_id' => $transactionId];
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
}
|
||||
19
app/Helpers/Autoloader.php
Normal file
19
app/Helpers/Autoloader.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
spl_autoload_register(function ($class) {
|
||||
// Map namespace/class to directory structure
|
||||
// e.g., App\Controllers\PurchaseController -> app/Controllers/PurchaseController.php
|
||||
|
||||
// Remove 'App\' prefix if present
|
||||
if (strpos($class, 'App\\') === 0) {
|
||||
$class = substr($class, 4);
|
||||
}
|
||||
|
||||
$file = __DIR__ . '/../' . str_replace('\\', '/', $class) . '.php';
|
||||
|
||||
if (file_exists($file)) {
|
||||
require_once $file;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
22
app/Repositories/BaseRepository.php
Normal file
22
app/Repositories/BaseRepository.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
abstract class BaseRepository {
|
||||
protected $db;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = db();
|
||||
}
|
||||
|
||||
public function beginTransaction() {
|
||||
return $this->db->beginTransaction();
|
||||
}
|
||||
|
||||
public function commit() {
|
||||
return $this->db->commit();
|
||||
}
|
||||
|
||||
public function rollBack() {
|
||||
return $this->db->rollBack();
|
||||
}
|
||||
}
|
||||
44
app/Repositories/CarRepository.php
Normal file
44
app/Repositories/CarRepository.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
class CarRepository extends BaseRepository {
|
||||
|
||||
public function find($id, $lock = false) {
|
||||
$sql = "SELECT * FROM cars WHERE id = :id";
|
||||
if ($lock) {
|
||||
$sql .= " FOR UPDATE";
|
||||
}
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute(['id' => $id]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function reserve($carId, $userId, $minutes = 15) {
|
||||
$expiresAt = date('Y-m-d H:i:s', strtotime("+$minutes minutes"));
|
||||
$sql = "UPDATE cars SET
|
||||
reserved_by = :user_id,
|
||||
reserved_at = NOW(),
|
||||
reservation_expires_at = :expires_at
|
||||
WHERE id = :id";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
return $stmt->execute([
|
||||
'user_id' => $userId,
|
||||
'expires_at' => $expiresAt,
|
||||
'id' => $carId
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAsSold($id) {
|
||||
$stmt = $this->db->prepare("UPDATE cars SET status = 'sold' WHERE id = :id");
|
||||
return $stmt->execute(['id' => $id]);
|
||||
}
|
||||
|
||||
public function isAvailable($id) {
|
||||
$sql = "SELECT * FROM cars WHERE id = :id
|
||||
AND status = 'approved'
|
||||
AND (reserved_by IS NULL OR reservation_expires_at < NOW())";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute(['id' => $id]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
}
|
||||
54
app/Repositories/PurchaseRepository.php
Normal file
54
app/Repositories/PurchaseRepository.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
class PurchaseRepository extends BaseRepository {
|
||||
|
||||
public function create($data) {
|
||||
$sql = "INSERT INTO purchases (
|
||||
transaction_id, reference_number, verification_token,
|
||||
car_id, user_id, buyer_name, buyer_email, buyer_phone,
|
||||
base_price, marketplace_fee, tax, total_amount,
|
||||
status, escrow_status, payment_method, expires_at
|
||||
) VALUES (
|
||||
:transaction_id, :reference_number, :verification_token,
|
||||
:car_id, :user_id, :buyer_name, :buyer_email, :buyer_phone,
|
||||
:base_price, :marketplace_fee, :tax, :total_amount,
|
||||
:status, :escrow_status, :payment_method, :expires_at
|
||||
)";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($data);
|
||||
return $this->db->lastInsertId();
|
||||
}
|
||||
|
||||
public function findByTransactionId($transactionId) {
|
||||
$stmt = $this->db->prepare("SELECT * FROM purchases WHERE transaction_id = :id");
|
||||
$stmt->execute(['id' => $transactionId]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function updateStatus($id, $status, $escrowStatus = null) {
|
||||
$sql = "UPDATE purchases SET status = :status";
|
||||
$params = ['id' => $id, 'status' => $status];
|
||||
|
||||
if ($escrowStatus) {
|
||||
$sql .= ", escrow_status = :escrow_status";
|
||||
$params['escrow_status'] = $escrowStatus;
|
||||
}
|
||||
|
||||
$sql .= " WHERE id = :id";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
return $stmt->execute($params);
|
||||
}
|
||||
|
||||
public function generateUniqueReference() {
|
||||
$ref = 'REF-' . strtoupper(substr(md5(uniqid()), 0, 8));
|
||||
// Check if exists
|
||||
$stmt = $this->db->prepare("SELECT id FROM purchases WHERE reference_number = :ref");
|
||||
$stmt->execute(['ref' => $ref]);
|
||||
if ($stmt->fetch()) {
|
||||
return $this->generateUniqueReference();
|
||||
}
|
||||
return $ref;
|
||||
}
|
||||
}
|
||||
12
app/Repositories/SettingRepository.php
Normal file
12
app/Repositories/SettingRepository.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
class SettingRepository extends BaseRepository {
|
||||
public function get($key, $default = null) {
|
||||
$stmt = $this->db->prepare("SELECT value FROM settings WHERE `key` = :key");
|
||||
$stmt->execute(['key' => $key]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ? $row['value'] : $default;
|
||||
}
|
||||
}
|
||||
|
||||
29
app/Services/PaymentService.php
Normal file
29
app/Services/PaymentService.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
class PaymentService {
|
||||
|
||||
public function generateUUID() {
|
||||
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
|
||||
public function generateVerificationToken($transactionId, $amount) {
|
||||
return hash('sha256', $transactionId . $amount . 'SECRET_SALT_2026');
|
||||
}
|
||||
|
||||
public function processPayment($method, $amount) {
|
||||
// Offline Simulation
|
||||
// In a real scenario, this would call a gateway API
|
||||
return [
|
||||
'success' => true,
|
||||
'transaction_id' => $this->generateUUID(),
|
||||
'status' => 'paid'
|
||||
];
|
||||
}
|
||||
}
|
||||
98
app/Services/PurchaseService.php
Normal file
98
app/Services/PurchaseService.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use App\Repositories\CarRepository;
|
||||
use App\Repositories\PurchaseRepository;
|
||||
use App\Repositories\SettingRepository;
|
||||
use Exception;
|
||||
|
||||
class PurchaseService {
|
||||
protected $carRepo;
|
||||
protected $purchaseRepo;
|
||||
protected $settingRepo;
|
||||
protected $paymentService;
|
||||
|
||||
public function __construct() {
|
||||
$this->carRepo = new CarRepository();
|
||||
$this->purchaseRepo = new PurchaseRepository();
|
||||
$this->settingRepo = new SettingRepository();
|
||||
$this->paymentService = new PaymentService();
|
||||
}
|
||||
|
||||
public function reserveCar($carId, $userId) {
|
||||
$this->carRepo->beginTransaction();
|
||||
try {
|
||||
$car = $this->carRepo->find($carId, true); // Lock for update
|
||||
|
||||
if (!$car) throw new Exception("Car not found.");
|
||||
if ($car['status'] !== 'approved') throw new Exception("Car is not available for sale.");
|
||||
if ($car['user_id'] == $userId) throw new Exception("You cannot buy your own car.");
|
||||
|
||||
// Check if already reserved by someone else and not expired
|
||||
if ($car['reserved_by'] && $car['reserved_by'] != $userId && strtotime($car['reservation_expires_at']) > time()) {
|
||||
throw new Exception("This car is currently reserved by another buyer.");
|
||||
}
|
||||
|
||||
$this->carRepo->reserve($carId, $userId);
|
||||
$this->carRepo->commit();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->carRepo->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function calculateFees($price) {
|
||||
$feePercent = $this->settingRepo->get('marketplace_fee_percentage', 5);
|
||||
$taxPercent = $this->settingRepo->get('tax_percentage', 10);
|
||||
|
||||
$fee = ($price * $feePercent) / 100;
|
||||
$tax = ($price * $taxPercent) / 100;
|
||||
$total = $price + $fee + $tax;
|
||||
|
||||
return [
|
||||
'base_price' => $price,
|
||||
'fee' => $fee,
|
||||
'tax' => $tax,
|
||||
'total' => $total
|
||||
];
|
||||
}
|
||||
|
||||
public function initiatePurchase($carId, $userId, $buyerData) {
|
||||
$this->purchaseRepo->beginTransaction();
|
||||
try {
|
||||
$car = $this->carRepo->find($carId, true);
|
||||
if (!$car) throw new Exception("Car not found.");
|
||||
|
||||
$costs = $this->calculateFees($car['price']);
|
||||
$transactionId = $this->paymentService->generateUUID();
|
||||
$ref = $this->purchaseRepo->generateUniqueReference();
|
||||
$token = $this->paymentService->generateVerificationToken($transactionId, $costs['total']);
|
||||
|
||||
$purchaseId = $this->purchaseRepo->create([
|
||||
'transaction_id' => $transactionId,
|
||||
'reference_number' => $ref,
|
||||
'verification_token' => $token,
|
||||
'car_id' => $carId,
|
||||
'user_id' => $userId,
|
||||
'buyer_name' => $buyerData['name'],
|
||||
'buyer_email' => $buyerData['email'],
|
||||
'buyer_phone' => $buyerData['phone'],
|
||||
'base_price' => $costs['base_price'],
|
||||
'marketplace_fee' => $costs['fee'],
|
||||
'tax' => $costs['tax'],
|
||||
'total_amount' => $costs['total'],
|
||||
'status' => 'initiated',
|
||||
'escrow_status' => 'awaiting_verification',
|
||||
'payment_method' => $buyerData['payment_method'],
|
||||
'expires_at' => date('Y-m-d H:i:s', strtotime("+1 hour"))
|
||||
]);
|
||||
|
||||
$this->purchaseRepo->commit();
|
||||
return $transactionId;
|
||||
} catch (Exception $e) {
|
||||
$this->purchaseRepo->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,3 +15,6 @@ function db() {
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
// Global Autoloader for Enterprise Architecture
|
||||
require_once __DIR__ . '/../app/Helpers/Autoloader.php';
|
||||
38
db/migrations/20260223_enterprise_buy_sell.sql
Normal file
38
db/migrations/20260223_enterprise_buy_sell.sql
Normal file
@ -0,0 +1,38 @@
|
||||
-- Enterprise Buy/Sell/Payment Module Migration
|
||||
|
||||
-- Update cars table for reservations
|
||||
ALTER TABLE `cars`
|
||||
ADD COLUMN `reserved_by` INT NULL,
|
||||
ADD COLUMN `reserved_at` TIMESTAMP NULL,
|
||||
ADD COLUMN `reservation_expires_at` TIMESTAMP NULL,
|
||||
ADD CONSTRAINT `fk_cars_reserved_by` FOREIGN KEY (`reserved_by`) REFERENCES `users`(`id`) ON DELETE SET NULL;
|
||||
|
||||
-- Update purchases table for Enterprise Flow
|
||||
-- We need to check if existing purchases exist and handle them.
|
||||
-- Since this is an upgrade, we'll allow NULL for new columns temporarily if needed,
|
||||
-- but the requirement asks for NOT NULL UNIQUE, so we'll be careful.
|
||||
ALTER TABLE `purchases`
|
||||
ADD COLUMN `transaction_id` CHAR(36) NULL AFTER `id`,
|
||||
ADD COLUMN `reference_number` VARCHAR(50) NULL AFTER `transaction_id`,
|
||||
ADD COLUMN `verification_token` VARCHAR(64) NULL AFTER `reference_number`,
|
||||
ADD COLUMN `base_price` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
ADD COLUMN `marketplace_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
ADD COLUMN `tax` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
ADD COLUMN `total_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
ADD COLUMN `payment_method` ENUM('card', 'bank_transfer', 'wallet') NULL,
|
||||
ADD COLUMN `escrow_status` ENUM('awaiting_verification', 'held_in_escrow', 'released', 'cancelled') DEFAULT 'awaiting_verification',
|
||||
ADD COLUMN `expires_at` TIMESTAMP NULL;
|
||||
|
||||
ALTER TABLE `purchases`
|
||||
MODIFY COLUMN `status` ENUM('initiated', 'processing', 'paid', 'failed', 'refunded', 'chargeback', 'reserved', 'completed', 'cancelled', 'pending', 'approved', 'rejected') DEFAULT 'initiated';
|
||||
|
||||
-- Settings table for configurable marketplace values
|
||||
CREATE TABLE IF NOT EXISTS `settings` (
|
||||
`key` VARCHAR(50) PRIMARY KEY,
|
||||
`value` VARCHAR(255) NOT NULL,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
INSERT IGNORE INTO `settings` (`key`, `value`) VALUES
|
||||
('marketplace_fee_percentage', '5'),
|
||||
('tax_percentage', '10');
|
||||
127
purchase.php
127
purchase.php
@ -1,38 +1,61 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
use App\Controllers\PurchaseController;
|
||||
use App\Services\PurchaseService;
|
||||
use App\Repositories\CarRepository;
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$controller = new PurchaseController();
|
||||
$purchaseService = new PurchaseService();
|
||||
$carRepo = new CarRepository();
|
||||
|
||||
$id = $_GET['id'] ?? 0;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT c.*, ci.image_path FROM cars c LEFT JOIN car_images ci ON c.id = ci.car_id AND ci.is_main = 1 WHERE c.id = ? AND c.status = 'approved'");
|
||||
$stmt->execute([$id]);
|
||||
$car = $stmt->fetch();
|
||||
// Step 1: Try to reserve the car
|
||||
$reservation = $controller->reserve($id, $userId);
|
||||
|
||||
if (!$car) {
|
||||
header('Location: cars.php');
|
||||
if (!$reservation['success']) {
|
||||
$_SESSION['error'] = $reservation['message'];
|
||||
header('Location: car_detail.php?id=' . $id);
|
||||
exit;
|
||||
}
|
||||
|
||||
$car = $carRepo->find($id);
|
||||
// Fetch main image manually since find() doesn't JOIN images (standard repository pattern)
|
||||
$stmt = db()->prepare("SELECT image_path FROM car_images WHERE car_id = ? AND is_main = 1");
|
||||
$stmt->execute([$id]);
|
||||
$mainImage = $stmt->fetch();
|
||||
$car['image_path'] = $mainImage ? $mainImage['image_path'] : 'assets/images/placeholder-car.jpg';
|
||||
|
||||
$costs = $purchaseService->calculateFees($car['price']);
|
||||
|
||||
$success = false;
|
||||
$error = '';
|
||||
$transactionId = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = $_POST['buyer_name'] ?? '';
|
||||
$phone = $_POST['buyer_phone'] ?? '';
|
||||
$bank_id = $_POST['bank_id'] ?? '';
|
||||
$personal_info = $_POST['personal_info'] ?? '';
|
||||
$email = $_SESSION['user_email'] ?? '';
|
||||
$buyerData = [
|
||||
'name' => $_POST['buyer_name'] ?? '',
|
||||
'phone' => $_POST['buyer_phone'] ?? '',
|
||||
'email' => $_SESSION['user_email'] ?? '',
|
||||
'bank_id' => $_POST['bank_id'] ?? '',
|
||||
'personal_info' => $_POST['personal_info'] ?? '',
|
||||
'payment_method' => $_POST['payment_method'] ?? 'bank_transfer'
|
||||
];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO purchases (car_id, user_id, buyer_name, buyer_email, buyer_phone, bank_id, personal_info, status) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')");
|
||||
if ($stmt->execute([$id, $_SESSION['user_id'], $name, $email, $phone, $bank_id, $personal_info])) {
|
||||
$result = $controller->checkout($id, $userId, $buyerData);
|
||||
|
||||
if ($result['success']) {
|
||||
$success = true;
|
||||
$transactionId = $result['transaction_id'];
|
||||
} else {
|
||||
$error = "Failed to submit request. Please try again.";
|
||||
$error = $result['message'];
|
||||
}
|
||||
}
|
||||
?>
|
||||
@ -41,29 +64,53 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<?php if ($success): ?>
|
||||
<div class="box text-center" style="padding: 6rem;">
|
||||
<div style="font-size: 6rem; margin-bottom: 2.5rem; filter: drop-shadow(0 10px 20px rgba(0,0,0,0.3));">🚀</div>
|
||||
<h1 class="text-gold fw-black mb-1" style="font-size: 3.5rem;">Purchase Request Sent!</h1>
|
||||
<h1 class="text-gold fw-black mb-1" style="font-size: 3.5rem;">Purchase Initiated!</h1>
|
||||
<p class="text-secondary mb-3" style="font-size: 1.3rem; max-width: 750px; margin-left: auto; margin-right: auto; line-height: 1.8; font-weight: 600;">
|
||||
Your verification request for the <strong class="text-gold"><?= htmlspecialchars($car['brand'] . ' ' . $car['model']) ?></strong> with Bank Reference <strong class="text-gold"><?= htmlspecialchars($bank_id) ?></strong> has been successfully submitted to our verification team.
|
||||
Your secure transaction <strong class="text-gold"><?= htmlspecialchars($transactionId) ?></strong> has been created.
|
||||
The car is reserved for you. Please proceed with payment verification.
|
||||
</p>
|
||||
<div class="flex justify-center gap-1 mt-3">
|
||||
<a href="dashboard.php" class="btn btn-primary btn-lg">View Request Status</a>
|
||||
<a href="cars.php" class="btn btn-outline btn-lg">Back to Marketplace</a>
|
||||
<a href="receipt.php?tx=<?= $transactionId ?>" class="btn btn-primary btn-lg">View Invoice</a>
|
||||
<a href="dashboard.php" class="btn btn-outline btn-lg">Go to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="grid" style="grid-template-columns: 1fr 1.6fr; gap: 4rem; align-items: start;">
|
||||
<div class="glass" style="padding: 2.5rem; position: sticky; top: 120px; border-top: 5px solid var(--primary-color);">
|
||||
<h3 class="fw-black mb-2 text-gold" style="text-transform: uppercase; letter-spacing: 2px; font-size: 1rem;">Transaction Summary</h3>
|
||||
<div class="mb-2" style="width: 100%; height: 220px; background-image: url('<?= htmlspecialchars($car['image_path'] ?: 'assets/images/placeholder-car.jpg') ?>'); background-size: cover; background-position: center; border-radius: 20px; border: 1px solid var(--glass-border);"></div>
|
||||
<h3 class="fw-black mb-2 text-gold" style="text-transform: uppercase; letter-spacing: 2px; font-size: 1rem;">Enterprise Checkout</h3>
|
||||
<div class="mb-2" style="width: 100%; height: 220px; background-image: url('<?= htmlspecialchars($car['image_path']) ?>'); background-size: cover; background-position: center; border-radius: 20px; border: 1px solid var(--glass-border);"></div>
|
||||
<h2 class="fw-black mb-1" style="font-size: 1.8rem; color: #fff;"><?= htmlspecialchars($car['brand'] . ' ' . $car['model']) ?></h2>
|
||||
<p class="text-secondary mb-2 fw-bold" style="font-size: 1.1rem;"><?= $car['year'] ?> Model • <?= $car['city'] ?></p>
|
||||
<div class="flex justify-between align-center mt-2 pt-2" style="border-top: 1px solid var(--glass-border);">
|
||||
<span class="text-secondary fw-black" style="text-transform: uppercase; font-size: 0.85rem;">Total Amount Due</span>
|
||||
<span class="price-tag" style="font-size: 1.8rem;">$<?= number_format($car['price']) ?></span>
|
||||
|
||||
<div class="mt-2 pt-2" style="border-top: 1px solid var(--glass-border);">
|
||||
<div class="flex justify-between mb-1">
|
||||
<span class="text-secondary">Car Price</span>
|
||||
<span class="text-white fw-bold">$<?= number_format($costs['base_price'], 2) ?></span>
|
||||
</div>
|
||||
<div class="flex justify-between mb-1">
|
||||
<span class="text-secondary">Marketplace Fee (<?= $purchaseService->calculateFees($car['price'])['fee'] / $car['price'] * 100 ?>%)</span>
|
||||
<span class="text-white fw-bold">$<?= number_format($costs['fee'], 2) ?></span>
|
||||
</div>
|
||||
<div class="flex justify-between mb-1">
|
||||
<span class="text-secondary">Tax (<?= $purchaseService->calculateFees($car['price'])['tax'] / $car['price'] * 100 ?>%)</span>
|
||||
<span class="text-white fw-bold">$<?= number_format($costs['tax'], 2) ?></span>
|
||||
</div>
|
||||
<div class="flex justify-between align-center mt-1 pt-1" style="border-top: 2px dashed var(--glass-border);">
|
||||
<span class="text-gold fw-black" style="text-transform: uppercase; font-size: 0.85rem;">Total Payable</span>
|
||||
<span class="price-tag" style="font-size: 1.8rem;">$<?= number_format($costs['total'], 2) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 p-1 text-center" style="background: rgba(255,0,0,0.1); border-radius: 10px;">
|
||||
<p class="text-sm text-white m-0">Reservation expires in: <span id="timer">15:00</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass" style="padding: 4.5rem;">
|
||||
<div class="flex align-center gap-1 mb-2">
|
||||
<span class="badge badge-primary">ESCROW PROTECTION ACTIVE</span>
|
||||
<span class="text-secondary text-sm">Transaction ID: Securely Generated</span>
|
||||
</div>
|
||||
<h1 class="fw-black mb-1" style="font-size: 3rem; color: #fff;">Buyer Verification</h1>
|
||||
<p class="text-secondary mb-3" style="font-size: 1.15rem; font-weight: 500;">Provide your legal documentation and banking details to proceed with this secure purchase.</p>
|
||||
|
||||
@ -75,7 +122,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<div class="grid grid-2">
|
||||
<div class="form-group">
|
||||
<label>Full Legal Name (as on ID Card)</label>
|
||||
<input type="text" name="buyer_name" class="form-control" value="<?= htmlspecialchars($_SESSION['user_name']) ?>" required placeholder="Enter your full name">
|
||||
<input type="text" name="buyer_name" class="form-control" value="<?= htmlspecialchars($_SESSION['user_name'] ?? '') ?>" required placeholder="Enter your full name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Phone Number</label>
|
||||
@ -83,6 +130,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Payment Method</label>
|
||||
<select name="payment_method" class="form-control" required>
|
||||
<option value="bank_transfer">Bank Transfer (Escrow Mode)</option>
|
||||
<option value="card">Credit/Debit Card</option>
|
||||
<option value="wallet">Digital Wallet</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Bank Reference ID / Account Number</label>
|
||||
<input type="text" name="bank_id" class="form-control" required placeholder="Azizi Bank / Kabul Bank Transaction ID">
|
||||
@ -96,13 +152,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
<div class="mt-3 mb-3" style="padding: 2.5rem; background: rgba(212, 175, 55, 0.05); border-left: 5px solid var(--primary-color); border-radius: 20px;">
|
||||
<p class="text-secondary text-sm" style="line-height: 1.8; margin: 0; font-weight: 600;">
|
||||
<strong class="text-gold" style="font-size: 1.1rem; display: block; margin-bottom: 0.5rem;">IMPORTANT SECURITY NOTICE:</strong>
|
||||
Your personal data is encrypted. Submission of fraudulent bank IDs will result in account suspension and legal action under Afghanistan's automotive marketplace regulations.
|
||||
<strong class="text-gold" style="font-size: 1.1rem; display: block; margin-bottom: 0.5rem;">ENTERPRISE ESCROW SYSTEM:</strong>
|
||||
Your payment will be held securely in Escrow. Funds are only released to the seller once you confirm receipt of the vehicle and the admin verifies all documentation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex align-center gap-1 mt-3">
|
||||
<button type="submit" class="btn btn-primary btn-lg" style="flex: 2; font-weight: 900; letter-spacing: 1px;">SUBMIT SECURE PURCHASE REQUEST</button>
|
||||
<button type="submit" class="btn btn-primary btn-lg" style="flex: 2; font-weight: 900; letter-spacing: 1px;">SECURE CHECKOUT & PAY</button>
|
||||
<a href="car_detail.php?id=<?= $id ?>" class="btn btn-outline btn-lg" style="flex: 1; font-weight: 700;">CANCEL</a>
|
||||
</div>
|
||||
</form>
|
||||
@ -111,4 +167,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let timeLeft = 15 * 60;
|
||||
const timerElement = document.getElementById('timer');
|
||||
const countdown = setInterval(() => {
|
||||
if (timeLeft <= 0) {
|
||||
clearInterval(countdown);
|
||||
alert('Reservation expired. Please refresh the page to try again.');
|
||||
window.location.reload();
|
||||
} else {
|
||||
const minutes = Math.floor(timeLeft / 60);
|
||||
const seconds = timeLeft % 60;
|
||||
timerElement.innerText = `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
|
||||
timeLeft--;
|
||||
}
|
||||
}, 1000);
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
94
receipt.php
94
receipt.php
@ -1,47 +1,55 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
use App\Repositories\PurchaseRepository;
|
||||
use App\Services\PaymentService;
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$purchase_id = $_GET['id'] ?? 0;
|
||||
$txId = $_GET['tx'] ?? '';
|
||||
$purchaseRepo = new PurchaseRepository();
|
||||
$paymentService = new PaymentService();
|
||||
|
||||
// Fetch purchase details (must be approved and belong to the user or admin)
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT p.*, c.brand, c.model, c.year, c.price, c.city, u.name as seller_name, u.phone as seller_phone
|
||||
// Fetch purchase details
|
||||
$stmt = db()->prepare("
|
||||
SELECT p.*, c.brand, c.model, c.year, c.city, u.name as seller_name, u.phone as seller_phone
|
||||
FROM purchases p
|
||||
JOIN cars c ON p.car_id = c.id
|
||||
JOIN users u ON c.user_id = u.id
|
||||
WHERE p.id = ? AND p.status = 'approved' AND (p.user_id = ? OR ?)
|
||||
WHERE p.transaction_id = ? AND (p.user_id = ? OR ?)
|
||||
");
|
||||
$isAdmin = isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
|
||||
$stmt->execute([$purchase_id, $_SESSION['user_id'], $isAdmin]);
|
||||
$stmt->execute([$txId, $_SESSION['user_id'], $isAdmin]);
|
||||
$data = $stmt->fetch();
|
||||
|
||||
if (!$data) {
|
||||
echo "<div class='container' style='padding: 5rem; text-align: center;'><h1>Receipt not found or not approved.</h1><a href='dashboard.php' class='btn btn-primary'>Back to Dashboard</a></div>";
|
||||
echo "<div class='container' style='padding: 5rem; text-align: center;'><h1>Invoice not found.</h1><a href='dashboard.php' class='btn btn-primary'>Back to Dashboard</a></div>";
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Generate verification hash if missing (for legacy or just-in-time check)
|
||||
$verificationHash = $data['verification_token'] ?: $paymentService->generateVerificationToken($data['transaction_id'], $data['total_amount']);
|
||||
?>
|
||||
|
||||
<div class="container" style="max-width: 900px; padding: 4rem 0;">
|
||||
<div id="receipt" class="glass receipt-box" style="padding: 4rem; background: #fff; color: #1a1a1a; position: relative; overflow: hidden; border-radius: 0; border: 1px solid #ddd;">
|
||||
<div id="receipt" class="glass receipt-box" style="padding: 4rem; background: #fff; color: #1a1a1a; position: relative; overflow: hidden; border-radius: 0; border: 1px solid #ddd; box-shadow: 0 20px 50px rgba(0,0,0,0.1);">
|
||||
<!-- Watermark -->
|
||||
<div class="receipt-watermark" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) rotate(-45deg); font-size: 8rem; font-weight: 900; color: rgba(46, 213, 115, 0.08); pointer-events: none; z-index: 0; white-space: nowrap;">PAID & VERIFIED</div>
|
||||
<div class="receipt-watermark" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) rotate(-45deg); font-size: 8rem; font-weight: 900; color: rgba(46, 213, 115, 0.08); pointer-events: none; z-index: 0; white-space: nowrap;">ENTERPRISE VERIFIED</div>
|
||||
|
||||
<div class="flex justify-between align-center mb-3" style="position: relative; z-index: 1; border-bottom: 2px solid #f0f0f0; padding-bottom: 2rem;">
|
||||
<div>
|
||||
<h1 class="fw-black text-gold" style="font-size: 2.5rem; margin: 0; filter: brightness(0.8);">AfgCars</h1>
|
||||
<p style="color: #666; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; font-size: 0.8rem; margin-top: 0.5rem;">Premium Vehicle Marketplace</p>
|
||||
<h1 class="fw-black text-gold" style="font-size: 2.5rem; margin: 0; filter: brightness(0.8);">AfgCars Enterprise</h1>
|
||||
<p style="color: #666; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; font-size: 0.8rem; margin-top: 0.5rem;">Secure Vehicle Transaction Module</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<h2 class="fw-black" style="margin: 0; color: #333;">OFFICIAL RECEIPT</h2>
|
||||
<p style="color: #888; font-weight: 700; margin-top: 0.5rem;">Receipt #: <span style="color: #333;">RC-<?= str_pad($data['id'], 6, '0', STR_PAD_LEFT) ?></span></p>
|
||||
<h2 class="fw-black" style="margin: 0; color: #333;">OFFICIAL INVOICE</h2>
|
||||
<p style="color: #888; font-weight: 700; margin-top: 0.5rem;">Invoice #: <span style="color: #333;"><?= htmlspecialchars($data['reference_number'] ?: 'INV-'.$data['id']) ?></span></p>
|
||||
<p style="color: #888; font-weight: 700;">Date: <span style="color: #333;"><?= date('M d, Y', strtotime($data['created_at'])) ?></span></p>
|
||||
<p style="color: #888; font-weight: 700;">Status: <span class="badge badge-primary"><?= strtoupper($data['status']) ?></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -51,7 +59,7 @@ if (!$data) {
|
||||
<p class="fw-black" style="font-size: 1.2rem; margin-bottom: 0.5rem; color: #333;"><?= htmlspecialchars($data['buyer_name']) ?></p>
|
||||
<p style="margin-bottom: 0.3rem; color: #666; font-weight: 500;"><?= htmlspecialchars($data['buyer_email']) ?></p>
|
||||
<p style="margin-bottom: 0.3rem; color: #666; font-weight: 500;"><?= htmlspecialchars($data['buyer_phone']) ?></p>
|
||||
<p class="fw-black" style="color: var(--primary-color); margin-top: 1.5rem; filter: brightness(0.8);">Bank Verification ID: <?= htmlspecialchars($data['bank_id']) ?></p>
|
||||
<p class="fw-black" style="color: var(--primary-color); margin-top: 1.5rem; filter: brightness(0.8);">Transaction ID: <br><span style="font-size: 0.8rem; font-family: monospace;"><?= htmlspecialchars($data['transaction_id']) ?></span></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 style="border-bottom: 1px solid #eee; padding-bottom: 0.5rem; margin-bottom: 1.5rem; color: #aaa; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 2px; font-weight: 800;">Seller Information</h4>
|
||||
@ -66,35 +74,67 @@ if (!$data) {
|
||||
<table style="width: 100%; border-collapse: collapse; border: 1px solid #eee;">
|
||||
<thead>
|
||||
<tr style="background: #fcfcfc; text-align: left;">
|
||||
<th style="padding: 1.2rem; border-bottom: 2px solid #eee; color: #333;">Transaction Description</th>
|
||||
<th style="padding: 1.2rem; border-bottom: 2px solid #eee; text-align: right; color: #333;">Price (USD)</th>
|
||||
<th style="padding: 1.2rem; border-bottom: 2px solid #eee; color: #333;">Item Description</th>
|
||||
<th style="padding: 1.2rem; border-bottom: 2px solid #eee; text-align: right; color: #333;">Amount (USD)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 2rem 1.2rem; border-bottom: 1px solid #eee;">
|
||||
<td style="padding: 1.5rem 1.2rem; border-bottom: 1px solid #eee;">
|
||||
<div class="fw-black" style="font-size: 1.3rem; color: #333;"><?= htmlspecialchars($data['brand'] . ' ' . $data['model']) ?></div>
|
||||
<div style="color: #888; font-size: 0.9rem; margin-top: 0.5rem; font-weight: 500;"><?= $data['year'] ?> Model Vehicle - Secured Transaction</div>
|
||||
<div style="color: #888; font-size: 0.9rem; margin-top: 0.5rem; font-weight: 500;"><?= $data['year'] ?> Model Vehicle - Base Price</div>
|
||||
</td>
|
||||
<td style="padding: 2rem 1.2rem; border-bottom: 1px solid #eee; text-align: right; font-weight: 800; font-size: 1.3rem; color: #333;">$<?= number_format($data['price']) ?></td>
|
||||
<td style="padding: 1.5rem 1.2rem; border-bottom: 1px solid #eee; text-align: right; font-weight: 800; font-size: 1.3rem; color: #333;">$<?= number_format($data['base_price'], 2) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 1rem 1.2rem; border-bottom: 1px solid #eee;">
|
||||
<div style="color: #666; font-weight: 600;">Marketplace Service Fee</div>
|
||||
</td>
|
||||
<td style="padding: 1rem 1.2rem; border-bottom: 1px solid #eee; text-align: right; font-weight: 700; color: #333;">$<?= number_format($data['marketplace_fee'], 2) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 1rem 1.2rem; border-bottom: 1px solid #eee;">
|
||||
<div style="color: #666; font-weight: 600;">Automotive Sales Tax</div>
|
||||
</td>
|
||||
<td style="padding: 1rem 1.2rem; border-bottom: 1px solid #eee; text-align: right; font-weight: 700; color: #333;">$<?= number_format($data['tax'], 2) ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr style="background: #fafafa;">
|
||||
<td style="padding: 2.5rem 1.2rem; text-align: right; font-weight: 700; font-size: 1.2rem; color: #666;">Total Transaction Amount</td>
|
||||
<td style="padding: 2.5rem 1.2rem; text-align: right; font-weight: 900; font-size: 2.2rem; color: var(--primary-color); filter: brightness(0.8);">$<?= number_format($data['price']) ?></td>
|
||||
<td style="padding: 2.5rem 1.2rem; text-align: right; font-weight: 700; font-size: 1.2rem; color: #666;">Total Payable Amount</td>
|
||||
<td style="padding: 2.5rem 1.2rem; text-align: right; font-weight: 900; font-size: 2.2rem; color: var(--primary-color); filter: brightness(0.8);">$<?= number_format($data['total_amount'], 2) ?></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="grid" style="grid-template-columns: 1fr 3fr; gap: 2rem; margin-top: 3rem; position: relative; z-index: 1;">
|
||||
<div style="background: #f9f9f9; padding: 1.5rem; display: flex; align-items: center; justify-content: center; border: 1px solid #eee; border-radius: 10px;">
|
||||
<!-- Placeholder for QR Code -->
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 120px; height: 120px; background: #333; padding: 10px; border-radius: 5px; position: relative;">
|
||||
<div style="width: 100%; height: 100%; background: #fff; border: 5px solid #333; display: grid; grid-template-columns: repeat(4, 1fr);">
|
||||
<?php for($i=0;$i<16;$i++): ?>
|
||||
<div style="background: <?= rand(0,1) ? '#000' : '#fff' ?>;"></div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size: 0.6rem; margin-top: 0.5rem; color: #888; font-weight: 700; text-transform: uppercase;">Scan to Verify</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; justify-content: center;">
|
||||
<h5 style="margin: 0 0 0.5rem 0; color: #aaa; text-transform: uppercase; font-size: 0.7rem; letter-spacing: 1px; font-weight: 800;">SHA256 Verification Code</h5>
|
||||
<p style="font-family: monospace; font-size: 0.75rem; word-break: break-all; color: #666; margin: 0; padding: 1rem; background: #f0f0f0; border-radius: 5px;"><?= $verificationHash ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-center" style="border-top: 2px dashed #eee; padding-top: 2.5rem; color: #aaa; font-size: 0.85rem; font-weight: 600; position: relative; z-index: 1; letter-spacing: 0.5px;">
|
||||
<p style="margin-bottom: 0.5rem;">This official document is computer-generated and verified by AfgCars Security Systems.</p>
|
||||
<p style="color: #ccc;">NATIONAL AUTOMOTIVE REGISTRY OF AFGHANISTAN • SECURE MARKETPLACE VERIFICATION</p>
|
||||
<p style="margin-bottom: 0.5rem;">This official document is computer-generated and verified by AfgCars Enterprise Systems.</p>
|
||||
<p style="color: #ccc;">ESCROW STATUS: <?= strtoupper($data['escrow_status'] ?: 'held_in_escrow') ?> • PAYMENT METHOD: <?= strtoupper($data['payment_method'] ?: 'bank_transfer') ?></p>
|
||||
</div>
|
||||
|
||||
<!-- Paid Badge -->
|
||||
<div style="position: absolute; bottom: 80px; right: 60px; border: 6px double #2ed573; color: #2ed573; padding: 15px 30px; font-size: 2.5rem; font-weight: 900; transform: rotate(-15deg); border-radius: 12px; opacity: 0.6; pointer-events: none;">PAID</div>
|
||||
<!-- Status Badge -->
|
||||
<div style="position: absolute; bottom: 120px; right: 60px; border: 6px double #2ed573; color: #2ed573; padding: 15px 30px; font-size: 2.5rem; font-weight: 900; transform: rotate(-15deg); border-radius: 12px; opacity: 0.6; pointer-events: none; text-transform: uppercase;"><?= $data['status'] ?></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex justify-center gap-1">
|
||||
@ -108,7 +148,7 @@ if (!$data) {
|
||||
nav, footer, .btn, .nav-actions { display: none !important; }
|
||||
body { background: #fff !important; color: #000 !important; }
|
||||
.container { max-width: 100% !important; padding: 0 !important; margin: 0 !important; }
|
||||
.glass { border: none !important; box-shadow: none !important; background: #fff !important; }
|
||||
.glass { border: none !important; box-shadow: none !important; background: #fff !important; border-radius: 0 !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user