From adf8c9c9729387ec93e03603affc7daedfcd612e Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 16 Oct 2025 06:28:46 +0000 Subject: [PATCH] V14 --- api/get_order_status.php | 44 +- assets/css/main.css | 554 +++++++++++++++++- checkout.php | 282 +++++---- create_stripe_session.php | 36 +- db/config.php | 1 + index.php | 78 ++- menu.php | 160 +++-- migrations/00000000_drop_all_tables.sql | 15 + migrations/00000000_drop_users_table.sql | 1 + migrations/20251011_create_cart_table.sql | 7 + migrations/20251011_create_ratings_table.sql | 8 + ...ate_restaurants_and_menu_items_tables.sql} | 0 migrations/20251013_create_users_table.sql | 7 + migrations/20251014_create_orders_tables.sql | 29 +- .../20251015_add_order_id_to_ratings.sql | 1 - migrations/20251015_add_password_to_users.sql | 1 - migrations/20251015_add_status_to_orders.sql | 1 - migrations/20251015_add_token_to_orders.sql | 1 + migrations/20251015_allow_guest_orders.sql | 2 + ...251015_create_admin_user_with_password.sql | 2 +- migrations/20251015_create_users_table.sql | 7 - migrations/20251015_update_cart_table.sql | 2 - migrations/20251016_update_cart_table.sql | 2 + order_confirmation.php | 127 +++- order_status.php | 213 ++++--- payment-success.php | 51 +- paypal-capture.php | 43 +- 27 files changed, 1264 insertions(+), 411 deletions(-) create mode 100644 migrations/00000000_drop_all_tables.sql create mode 100644 migrations/00000000_drop_users_table.sql create mode 100644 migrations/20251011_create_cart_table.sql create mode 100644 migrations/20251011_create_ratings_table.sql rename migrations/{20251015_create_restaurants_and_menu_items_tables.sql => 20251012_create_restaurants_and_menu_items_tables.sql} (100%) create mode 100644 migrations/20251013_create_users_table.sql delete mode 100644 migrations/20251015_add_order_id_to_ratings.sql delete mode 100644 migrations/20251015_add_password_to_users.sql delete mode 100644 migrations/20251015_add_status_to_orders.sql create mode 100644 migrations/20251015_add_token_to_orders.sql create mode 100644 migrations/20251015_allow_guest_orders.sql delete mode 100644 migrations/20251015_create_users_table.sql delete mode 100644 migrations/20251015_update_cart_table.sql create mode 100644 migrations/20251016_update_cart_table.sql diff --git a/api/get_order_status.php b/api/get_order_status.php index 3f64edae..46d9d51f 100644 --- a/api/get_order_status.php +++ b/api/get_order_status.php @@ -3,35 +3,43 @@ header('Content-Type: application/json'); session_start(); require_once __DIR__ . '/../db/config.php'; -// Check if user is logged in -if (!isset($_SESSION['user_id'])) { - echo json_encode(['error' => 'User not authenticated']); - exit; -} +$order_id = $_GET['order_id'] ?? null; +$token = $_GET['token'] ?? null; +$user_id = $_SESSION['user_id'] ?? null; -// Check if order_id is provided -if (!isset($_GET['order_id'])) { +if (!$order_id) { echo json_encode(['error' => 'Order ID not specified']); exit; } -$order_id = $_GET['order_id']; -$user_id = $_SESSION['user_id']; +$status = null; try { $pdo = db(); + + if ($user_id) { + $stmt = $pdo->prepare("SELECT status FROM orders WHERE id = ? AND user_id = ?"); + $stmt->execute([$order_id, $user_id]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + if ($result) { + $status = $result['status']; + } + } elseif ($token) { + $stmt = $pdo->prepare("SELECT status FROM orders WHERE id = ? AND guest_token = ?"); + $stmt->execute([$order_id, $token]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + if ($result) { + $status = $result['status']; + } + } - // Fetch the order status, ensuring the order belongs to the logged-in user - $stmt = $pdo->prepare("SELECT status FROM orders WHERE id = ? AND user_id = ?"); - $stmt->execute([$order_id, $user_id]); - $order = $stmt->fetch(PDO::FETCH_ASSOC); - - if ($order) { - echo json_encode(['status' => ucwords($order['status'])]); + if ($status) { + echo json_encode(['status' => ucwords($status)]); } else { echo json_encode(['error' => 'Order not found or permission denied']); } + } catch (PDOException $e) { - echo json_encode(['error' => 'Database error']); + http_response_code(500); + echo json_encode(['error' => 'Database connection error']); } -?> \ No newline at end of file diff --git a/assets/css/main.css b/assets/css/main.css index 4d065668..41be8532 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -264,17 +264,17 @@ footer { /* Restaurant Menu Page */ .restaurant-hero-menu { position: relative; - height: 300px; + height: 350px; background-size: cover; background-position: center; display: flex; flex-direction: column; justify-content: flex-end; - padding: 2rem; + padding: 3rem; color: var(--white); - border-radius: var(--border-radius); + border-radius: 0 0 var(--border-radius) var(--border-radius); overflow: hidden; - margin-bottom: 2rem; + margin-bottom: 3rem; } .restaurant-hero-menu::after { @@ -284,7 +284,7 @@ footer { left: 0; width: 100%; height: 100%; - background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0) 100%); + background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.1) 100%); } .restaurant-hero-menu-content { @@ -293,23 +293,23 @@ footer { } .restaurant-hero-menu h1 { - font-size: 2.5rem; + font-size: 3rem; font-weight: 700; margin: 0 0 0.5rem; - text-shadow: 0 2px 4px rgba(0,0,0,0.6); + text-shadow: 0 2px 8px rgba(0,0,0,0.7); } .restaurant-hero-menu p { - font-size: 1rem; + font-size: 1.1rem; margin: 0.25rem 0; - text-shadow: 0 1px 3px rgba(0,0,0,0.5); + text-shadow: 0 1px 5px rgba(0,0,0,0.6); } /* Menu Grid */ .menu-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 2rem; + grid-template-columns: 1fr; + gap: 1.5rem; } .menu-item-card { @@ -319,7 +319,8 @@ footer { overflow: hidden; transition: all 0.3s ease-in-out; display: flex; - flex-direction: column; + justify-content: space-between; + padding: 1.5rem; } .menu-item-card:hover { @@ -327,21 +328,23 @@ footer { box-shadow: 0 8px 20px rgba(0,0,0,0.12); } -.menu-item-card img { - width: 100%; - height: 180px; +.menu-item-image { + width: 120px; + height: 120px; object-fit: cover; + border-radius: 12px; + margin-left: 1.5rem; } .menu-item-card-content { - padding: 1.5rem; + padding: 0; display: flex; flex-direction: column; flex-grow: 1; } .menu-item-card-content h3 { - font-size: 1.15rem; + font-size: 1.2rem; font-weight: 700; margin: 0 0 0.5rem; } @@ -357,9 +360,50 @@ footer { font-size: 1.1rem; font-weight: 700; color: var(--text-color); - align-self: flex-end; } +/* Reviews Section */ +.reviews-section { + background-color: var(--white); + padding: 2rem; + border-radius: var(--border-radius); + box-shadow: var(--shadow-soft); + position: sticky; + top: 120px; /* Adjust based on header height */ +} + +.review-card { + border-bottom: 1px solid var(--light-gray); + padding: 1.5rem 0; +} + +.review-card:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.review-card:first-child { + padding-top: 0; +} + +.review-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.review-header strong { + font-size: 1rem; +} + +.review-text { + font-style: italic; + color: var(--dark-gray); + margin-bottom: 0.5rem; +} + + /* Rating Section on Menu Page */ .rating-form-container { background-color: var(--white); @@ -1029,4 +1073,476 @@ footer { .col-md-9 { width: 100%; padding: 0; -} \ No newline at end of file +} + +/* Categories Section */ +.categories-section { + margin-bottom: 3rem; +} + +.category-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1.5rem; +} + +.category-card { + position: relative; + border-radius: var(--border-radius); + overflow: hidden; + height: 150px; + display: flex; + align-items: flex-end; + justify-content: center; + text-decoration: none; + color: var(--white); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.category-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0,0,0,0.2); +} + +.category-card img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + transition: transform 0.3s ease; +} + +.category-card:hover img { + transform: scale(1.05); +} + +.category-card-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 60%); + z-index: 2; +} + +.category-card h3 { + position: relative; + z-index: 3; + margin: 0; + padding: 1rem; + font-size: 1.2rem; + font-weight: 700; + text-align: center; + text-shadow: 0 2px 4px rgba(0,0,0,0.5); +} + +/* New Checkout Styles */ +.checkout-container { + display: flex; + min-height: 100vh; + background-color: var(--white); +} + +.checkout-main { + flex-grow: 1; + padding: 2rem 4rem; + display: flex; + flex-direction: column; +} + +.checkout-header { + margin-bottom: 3rem; +} + +.checkout-logo { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.5rem; + font-weight: 700; + color: var(--text-color); +} + +.step-title { + font-size: 1.75rem; + margin-bottom: 2rem; +} + +#delivery-form .form-group { + margin-bottom: 1.5rem; +} + +#delivery-form .form-control { + padding: 1rem; + font-size: 1rem; + border-radius: 8px; + border: 1px solid var(--medium-gray); +} + +.btn-primary { + width: 100%; + padding: 1rem; + font-size: 1.1rem; + font-weight: 600; + border-radius: 8px; + background-color: var(--coral); + color: white; + border: none; + cursor: pointer; + transition: background-color 0.3s; +} + +.btn-primary:hover { + background-color: #ff4f4f; +} + +.payment-methods { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; + margin-bottom: 2rem; +} + +.payment-method-card { + border: 1px solid var(--medium-gray); + border-radius: 12px; + cursor: pointer; + transition: border-color 0.3s, box-shadow 0.3s; +} + +.payment-method-card:has(input:checked) { + border-color: var(--coral); + box-shadow: 0 0 0 2px var(--coral); +} + +.payment-method-card label { + display: flex; + align-items: center; + gap: 1rem; + padding: 1.5rem; + font-size: 1.1rem; + font-weight: 600; +} + +.payment-method-card input[type="radio"] { + display: none; +} + +.payment-method-card svg { + width: 32px; + height: 32px; +} + +#paypal-button-container { + margin-top: 1rem; +} + +.btn-secondary { + width: 100%; + padding: 1rem; + font-size: 1.1rem; + font-weight: 600; + border-radius: 8px; + background-color: transparent; + color: var(--dark-gray); + border: 1px solid var(--medium-gray); + cursor: pointer; + margin-top: 1rem; + transition: background-color 0.3s, border-color 0.3s; +} + +.btn-secondary:hover { + background-color: var(--light-gray); + border-color: var(--dark-gray); +} + +.checkout-summary { + width: 450px; + background-color: var(--off-white); + padding: 2rem; + border-left: 1px solid var(--light-gray); + display: flex; + flex-direction: column; +} + +.checkout-summary h4 { + font-size: 1.5rem; + margin-bottom: 2rem; +} + +.summary-items { + flex-grow: 1; +} + +.summary-item { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + font-size: 1rem; +} + +.item-name { + color: var(--dark-gray); +} + +.item-price { + font-weight: 600; +} + +.summary-total { + border-top: 1px solid var(--medium-gray); + padding-top: 1.5rem; +} + +.summary-line { + display: flex; + justify-content: space-between; + margin-bottom: 0.75rem; + font-size: 1rem; +} + +.summary-line.discount { + color: #28a745; +} + +.summary-line.total { + font-size: 1.25rem; + font-weight: 700; + margin-top: 1rem; +} + +/* Order Confirmation Page Refined */ +.card.shadow-sm { + border: none; + border-radius: var(--border-radius); + box-shadow: var(--shadow-soft); +} + +.card-header.bg-success { + background-color: var(--coral) !important; + border-bottom: none; + border-radius: var(--border-radius) var(--border-radius) 0 0; +} + +.card-header h2 { + font-size: 1.75rem; + color: var(--white); +} + +.card-body .lead { + font-size: 1.2rem; + font-weight: 500; + color: var(--dark-gray); +} + +.card-body h5 { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 1rem; + color: var(--text-color); + border-bottom: 2px solid var(--light-gray); + padding-bottom: 0.5rem; +} + +.badge.bg-warning { + font-size: 0.9rem; + padding: 0.5em 0.75em; + background-color: var(--turquoise) !important; + color: var(--text-color) !important; +} + +.list-group-item { + border-color: var(--light-gray); +} + +.list-group-item span { + font-weight: 600; +} + +.list-group-item.fw-bold { + font-size: 1.1rem; +} + +.order-confirmation-actions .btn { + margin: 0 0.5rem; + padding: 0.8rem 1.5rem; + font-weight: 600; + text-transform: uppercase; + font-size: 0.9rem; + border-radius: 50px; + transition: all 0.3s ease; +} + +.order-confirmation-actions .btn-primary { + background-color: var(--coral); + border-color: var(--coral); +} + +.order-confirmation-actions .btn-primary:hover { + background-color: #ff4f4f; + border-color: #ff4f4f; + transform: translateY(-2px); +} + +.order-confirmation-actions .btn-secondary { + background-color: var(--off-white); + border-color: var(--medium-gray); + color: var(--dark-gray); +} + +.order-confirmation-actions .btn-secondary:hover { + background-color: var(--light-gray); + border-color: var(--dark-gray); + transform: translateY(-2px); +} + +/* Order Status Page */ +.order-status-container { + max-width: 800px; + margin: 2rem auto; + padding: 2rem; + background-color: var(--white); + border-radius: var(--border-radius); + box-shadow: var(--shadow-soft); +} + +.order-status-header { + text-align: center; + margin-bottom: 2.5rem; + border-bottom: 1px solid var(--light-gray); + padding-bottom: 1.5rem; +} + +.order-status-header h1 { + font-size: 2.2rem; + margin-bottom: 0.5rem; +} + +.order-status-header p { + font-size: 1.1rem; + color: var(--dark-gray); +} + +#order-status-timeline { + position: relative; + padding: 1rem 0; +} + +.timeline-item { + display: flex; + position: relative; + padding-left: 60px; /* Space for icon and line */ + padding-bottom: 3rem; +} + +.timeline-item:last-child { + padding-bottom: 0; +} + +.timeline-item::before { + content: ''; + position: absolute; + left: 24px; /* Center the line */ + top: 50px; + width: 2px; + height: calc(100% - 50px); + background-color: var(--light-gray); + transition: background-color 0.5s ease; +} + +.timeline-item:last-child::before { + display: none; +} + +.timeline-icon { + position: absolute; + left: 0; + top: 0; + width: 50px; + height: 50px; + border-radius: 50%; + background-color: var(--light-gray); + color: var(--medium-gray); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + z-index: 1; + border: 4px solid var(--off-white); + transition: all 0.5s ease; +} + +.timeline-content { + padding-top: 10px; +} + +.timeline-content h5 { + font-size: 1.2rem; + font-weight: 700; + margin-bottom: 0.25rem; + transition: color 0.5s ease; + border-bottom: none; + padding-bottom: 0; +} + +.timeline-content p { + font-size: 0.95rem; + color: var(--dark-gray); + margin: 0; +} + +/* --- Timeline States --- */ + +/* Default (Not yet active) */ +.timeline-item:not(.timeline-complete):not(.timeline-active) .timeline-icon { + background-color: var(--light-gray); + color: var(--medium-gray); +} + +/* Active State */ +.timeline-active .timeline-icon { + background-color: var(--turquoise); + color: var(--white); + transform: scale(1.1); + box-shadow: 0 0 15px rgba(64, 224, 208, 0.7); +} + +.timeline-active h5 { + color: var(--turquoise); +} + +/* Complete State */ +.timeline-complete::before { + background-color: var(--coral); +} + +.timeline-complete .timeline-icon { + background-color: var(--coral); + color: var(--white); +} + +/* Cancelled State */ +.timeline-cancelled .timeline-icon { + background-color: var(--dark-gray); + color: var(--white); +} + +.timeline-cancelled h5 { + color: var(--dark-gray); + text-decoration: line-through; +} + +.order-status-footer { + text-align: center; + margin-top: 2.5rem; + padding-top: 1.5rem; + border-top: 1px solid var(--light-gray); +} + +.order-status-footer p { + margin-bottom: 1rem; +} diff --git a/checkout.php b/checkout.php index ec376752..d975f095 100644 --- a/checkout.php +++ b/checkout.php @@ -3,16 +3,26 @@ session_start(); require_once 'db/config.php'; require_once 'includes/api_keys.php'; -if (!isset($_SESSION['user_id'])) { - header("Location: login.php"); - exit(); -} - -$userId = $_SESSION['user_id']; +$is_guest = !isset($_SESSION['user_id']); +$user_id = $_SESSION['user_id'] ?? null; +$session_id = session_id(); $pdo = db(); -$stmt = $pdo->prepare("SELECT c.id, mi.name, mi.price, c.quantity, r.name as restaurant_name, r.id as restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id JOIN restaurants r ON mi.restaurant_id = r.id WHERE c.user_id = :user_id"); -$stmt->bindParam(':user_id', $userId); +$user = []; +if (!$is_guest) { + $userStmt = $pdo->prepare("SELECT name, email, address, phone FROM users WHERE id = ?"); + $userStmt->execute([$user_id]); + $user = $userStmt->fetch(PDO::FETCH_ASSOC); +} + +// Fetch cart items +if (!$is_guest) { + $stmt = $pdo->prepare("SELECT c.id, mi.name, mi.price, c.quantity, r.name as restaurant_name, r.id as restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id JOIN restaurants r ON mi.restaurant_id = r.id WHERE c.user_id = :user_id"); + $stmt->bindParam(':user_id', $user_id); +} else { + $stmt = $pdo->prepare("SELECT c.id, mi.name, mi.price, c.quantity, r.name as restaurant_name, r.id as restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id JOIN restaurants r ON mi.restaurant_id = r.id WHERE c.session_id = :session_id"); + $stmt->bindParam(':session_id', $session_id); +} $stmt->execute(); $cartItems = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -21,144 +31,216 @@ if (empty($cartItems)) { exit(); } -$totalPrice = 0; +$subtotal = 0; foreach ($cartItems as $item) { - $totalPrice += $item['price'] * $item['quantity']; + $subtotal += $item['price'] * $item['quantity']; } -// Fetch settings from the database $settingsStmt = $pdo->query("SELECT name, value FROM settings WHERE name IN ('delivery_fee', 'service_fee_percentage')"); $settings = $settingsStmt->fetchAll(PDO::FETCH_KEY_PAIR); $delivery_fee = $settings['delivery_fee'] ?? 0; $service_fee_percentage = $settings['service_fee_percentage'] ?? 0; +$service_fee = ($subtotal * $service_fee_percentage) / 100; -$service_fee = ($totalPrice * $service_fee_percentage) / 100; -$totalPriceWithFees = $totalPrice + $delivery_fee + $service_fee; - +$discount_amount = $_SESSION['discount_amount'] ?? 0; +$totalPrice = $subtotal + $delivery_fee + $service_fee - $discount_amount; +$_SESSION['total_price'] = $totalPrice; include 'header.php'; ?> - + -
-

Checkout

-
-
-

Delivery Information

-
-
- - -
-
- - -
-
- - -
- -

Payment Method

-
- - -
-
- - -
- - -
- +
+
+ -
-

Order Summary

-
    - -
  • - (x) - $ -
  • - -
  • - Delivery Fee - $ -
  • -
  • - Service Fee (%) - $ -
  • -
  • - Total - $ -
  • -
+
+

1. Delivery Details

+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ +
+
+ + +
+ +
+

Order Summary

+
+ +
+ (x) + $ +
+ +
+
+
+ Subtotal + $ +
+
+ Delivery Fee + $ +
+
+ Service Fee + $ +
+ 0): ?> +
+ Discount + -$ +
+ +
+ Total + $ +
diff --git a/menu.php b/menu.php index 257f6bd7..89a7dcfc 100644 --- a/menu.php +++ b/menu.php @@ -69,111 +69,85 @@ try { } ?> -
- -
-
-
-

- -
- - -
- -
-

-
- - ( reviews) -
-
-
- - Image of <?php echo htmlspecialchars($restaurant['name']); ?> - -
-
- -
- -

Menu

-
- - -
-
- - <?php echo htmlspecialchars($item['name']); ?> - -
-
-

-

$

-
- - - -
- - -
-
-
-
-
- - -
-

This restaurant has no menu items yet.

-
+
+
+

+

+
+ + ( reviews) + +
+ + +
+
+
-
- -
-
-

Reviews & Ratings

- - 0): ?> -
- You have already reviewed this restaurant. -
- -
- Leave a review for a completed order. -
- -
- Log in to see reviews or leave your own. -
- - - - -
-
-
-
- +
+
+
+

Menu

+
+
+
+

Reviews

+ + +
+
+ + +
+

""

+ +
+ + +

This restaurant has no reviews yet.

+ - -

Restaurant not found.

- + + Leave a Review + +

Log in to leave a review.

+ +
+
+
\ No newline at end of file diff --git a/migrations/00000000_drop_all_tables.sql b/migrations/00000000_drop_all_tables.sql new file mode 100644 index 00000000..f0e751ef --- /dev/null +++ b/migrations/00000000_drop_all_tables.sql @@ -0,0 +1,15 @@ +DROP TABLE IF EXISTS cart CASCADE; +DROP TABLE IF EXISTS ratings CASCADE; +DROP TABLE IF EXISTS favorite_restaurants CASCADE; +DROP TABLE IF EXISTS driver_assignments CASCADE; +DROP TABLE IF EXISTS order_items CASCADE; +DROP TABLE IF EXISTS orders CASCADE; +DROP TABLE IF EXISTS menu_items CASCADE; +DROP TABLE IF EXISTS special_promotions CASCADE; +DROP TABLE IF EXISTS coupons CASCADE; +DROP TABLE IF EXISTS restaurants CASCADE; +DROP TABLE IF EXISTS cuisines CASCADE; +DROP TABLE IF EXISTS drivers CASCADE; +DROP TABLE IF EXISTS password_resets CASCADE; +DROP TABLE IF EXISTS settings CASCADE; +DROP TABLE IF EXISTS users CASCADE; \ No newline at end of file diff --git a/migrations/00000000_drop_users_table.sql b/migrations/00000000_drop_users_table.sql new file mode 100644 index 00000000..365a2107 --- /dev/null +++ b/migrations/00000000_drop_users_table.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS users; \ No newline at end of file diff --git a/migrations/20251011_create_cart_table.sql b/migrations/20251011_create_cart_table.sql new file mode 100644 index 00000000..88b54606 --- /dev/null +++ b/migrations/20251011_create_cart_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS cart ( + id SERIAL PRIMARY KEY, + user_id INT, + menu_item_id INT NOT NULL, + quantity INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/migrations/20251011_create_ratings_table.sql b/migrations/20251011_create_ratings_table.sql new file mode 100644 index 00000000..33a46112 --- /dev/null +++ b/migrations/20251011_create_ratings_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS ratings ( + id SERIAL PRIMARY KEY, + user_id INT NOT NULL, + order_id INT NOT NULL, + rating INT NOT NULL, + comment TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/migrations/20251015_create_restaurants_and_menu_items_tables.sql b/migrations/20251012_create_restaurants_and_menu_items_tables.sql similarity index 100% rename from migrations/20251015_create_restaurants_and_menu_items_tables.sql rename to migrations/20251012_create_restaurants_and_menu_items_tables.sql diff --git a/migrations/20251013_create_users_table.sql b/migrations/20251013_create_users_table.sql new file mode 100644 index 00000000..f9fdfd42 --- /dev/null +++ b/migrations/20251013_create_users_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/migrations/20251014_create_orders_tables.sql b/migrations/20251014_create_orders_tables.sql index 969b6da0..91cf3e97 100644 --- a/migrations/20251014_create_orders_tables.sql +++ b/migrations/20251014_create_orders_tables.sql @@ -1,18 +1,17 @@ -CREATE TABLE IF NOT EXISTS `orders` ( - `id` INT AUTO_INCREMENT PRIMARY KEY, - `user_id` INT NOT NULL, - `restaurant_id` INT NOT NULL, - `total_price` DECIMAL(10, 2) NOT NULL, - `status` VARCHAR(50) NOT NULL DEFAULT 'pending', - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) +CREATE TABLE IF NOT EXISTS orders ( + id SERIAL PRIMARY KEY, + user_id INT NOT NULL, + restaurant_id INT NOT NULL, + total_price DECIMAL(10, 2) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE IF NOT EXISTS `order_items` ( - `id` INT AUTO_INCREMENT PRIMARY KEY, - `order_id` INT NOT NULL, - `menu_item_id` INT NOT NULL, - `quantity` INT NOT NULL, - `price` DECIMAL(10, 2) NOT NULL, - FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`) +CREATE TABLE IF NOT EXISTS order_items ( + id SERIAL PRIMARY KEY, + order_id INT NOT NULL, + menu_item_id INT NOT NULL, + quantity INT NOT NULL, + price DECIMAL(10, 2) NOT NULL, + FOREIGN KEY (order_id) REFERENCES orders(id) ); diff --git a/migrations/20251015_add_order_id_to_ratings.sql b/migrations/20251015_add_order_id_to_ratings.sql deleted file mode 100644 index f1b29640..00000000 --- a/migrations/20251015_add_order_id_to_ratings.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE ratings ADD COLUMN order_id INT; diff --git a/migrations/20251015_add_password_to_users.sql b/migrations/20251015_add_password_to_users.sql deleted file mode 100644 index 1b24a1a7..00000000 --- a/migrations/20251015_add_password_to_users.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE users ADD COLUMN password VARCHAR(255); diff --git a/migrations/20251015_add_status_to_orders.sql b/migrations/20251015_add_status_to_orders.sql deleted file mode 100644 index bb88dc5a..00000000 --- a/migrations/20251015_add_status_to_orders.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE orders ADD COLUMN status VARCHAR(50) NOT NULL DEFAULT 'Pending'; \ No newline at end of file diff --git a/migrations/20251015_add_token_to_orders.sql b/migrations/20251015_add_token_to_orders.sql new file mode 100644 index 00000000..0bd8c334 --- /dev/null +++ b/migrations/20251015_add_token_to_orders.sql @@ -0,0 +1 @@ +ALTER TABLE orders ADD COLUMN token VARCHAR(255) UNIQUE; \ No newline at end of file diff --git a/migrations/20251015_allow_guest_orders.sql b/migrations/20251015_allow_guest_orders.sql new file mode 100644 index 00000000..ea4c7857 --- /dev/null +++ b/migrations/20251015_allow_guest_orders.sql @@ -0,0 +1,2 @@ +ALTER TABLE "orders" ALTER COLUMN "user_id" DROP NOT NULL; +ALTER TABLE "orders" ADD COLUMN "guest_name" VARCHAR(255) NULL, ADD COLUMN "guest_email" VARCHAR(255) NULL; \ No newline at end of file diff --git a/migrations/20251015_create_admin_user_with_password.sql b/migrations/20251015_create_admin_user_with_password.sql index f6ab28fb..93e69b08 100644 --- a/migrations/20251015_create_admin_user_with_password.sql +++ b/migrations/20251015_create_admin_user_with_password.sql @@ -1 +1 @@ -INSERT INTO users (email, role, password, is_admin) VALUES ('admin@example.com', 'owner', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', TRUE) ON CONFLICT (email) DO UPDATE SET role = 'owner', password = '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', is_admin = TRUE; +INSERT INTO users (name, email, role, password, is_admin) VALUES ('Admin User', 'admin@example.com', 'owner', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', TRUE) ON CONFLICT (email) DO UPDATE SET name = 'Admin User', role = 'owner', password = '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', is_admin = TRUE; diff --git a/migrations/20251015_create_users_table.sql b/migrations/20251015_create_users_table.sql deleted file mode 100644 index fcb4fd90..00000000 --- a/migrations/20251015_create_users_table.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS `users` ( - `id` INT AUTO_INCREMENT PRIMARY KEY, - `name` VARCHAR(255) NOT NULL, - `email` VARCHAR(255) NOT NULL UNIQUE, - `password` VARCHAR(255) NOT NULL, - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); diff --git a/migrations/20251015_update_cart_table.sql b/migrations/20251015_update_cart_table.sql deleted file mode 100644 index 93e9f825..00000000 --- a/migrations/20251015_update_cart_table.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE `cart` ADD `session_id` VARCHAR(255) NULL AFTER `user_id`; -ALTER TABLE `cart` MODIFY `user_id` INT NULL; diff --git a/migrations/20251016_update_cart_table.sql b/migrations/20251016_update_cart_table.sql new file mode 100644 index 00000000..ab09bd01 --- /dev/null +++ b/migrations/20251016_update_cart_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE cart ADD COLUMN session_id VARCHAR(255) NULL; +ALTER TABLE cart ALTER COLUMN user_id DROP NOT NULL; diff --git a/order_confirmation.php b/order_confirmation.php index 172ec498..de7e5bdd 100644 --- a/order_confirmation.php +++ b/order_confirmation.php @@ -2,31 +2,128 @@ session_start(); require_once 'db/config.php'; -if (!isset($_SESSION['user_id'])) { - header("Location: login.php"); - exit(); -} +$order_id = $_SESSION['order_id'] ?? $_GET['order_id'] ?? null; +$user_id = $_SESSION['user_id'] ?? null; +$guest_token = $_SESSION['token'] ?? $_GET['token'] ?? null; -if (!isset($_GET['id'])) { +if (!$order_id) { header("Location: index.php"); exit(); } -$orderId = $_GET['id']; +$pdo = db(); +$order = null; + +if ($user_id) { + $stmt = $pdo->prepare("SELECT * FROM orders WHERE id = ? AND user_id = ?"); + $stmt->execute([$order_id, $user_id]); + $order = $stmt->fetch(PDO::FETCH_ASSOC); +} elseif ($guest_token) { + $stmt = $pdo->prepare("SELECT * FROM orders WHERE id = ? AND token = ?"); + $stmt->execute([$order_id, $guest_token]); + $order = $stmt->fetch(PDO::FETCH_ASSOC); +} + +if (!$order) { + // If the order is not found or doesn't belong to the user/guest, redirect. + header("Location: index.php"); + exit(); +} + +// Fetch order items +$itemsStmt = $pdo->prepare(" + SELECT oi.quantity, mi.name, mi.price + FROM order_items oi + JOIN menu_items mi ON oi.menu_item_id = mi.id + WHERE oi.order_id = :order_id +"); +$itemsStmt->bindParam(':order_id', $order_id); +$itemsStmt->execute(); +$orderItems = $itemsStmt->fetchAll(PDO::FETCH_ASSOC); + +// Determine the correct tracking URL +$tracking_url = "order_status.php?order_id=" . $order_id; +if (!$user_id && $guest_token) { + $tracking_url .= "&token=" . $guest_token; +} include 'header.php'; ?> -
-
-
-

Thank You for Your Order!

-

Your order has been placed successfully.

-

Your Order ID is:

-

We have received your order and will begin processing it shortly.

- Continue Shopping +
+
+
+
+
+

Thank You for Your Order!

+
+
+
+

Your order has been placed successfully.

+

Your Order ID is:

+
+ +
+
+
Delivery Details
+

+ Name:
+ Address:
+ Phone: +

+
+
+
Order Summary
+

+ Date:
+ Status: +

+
+
+ +
Items Ordered
+
    + +
  • + (x) + $ +
  • + +
+ +
    +
  • + Subtotal + $ +
  • + 0): ?> +
  • + Discount + -$ +
  • + +
  • + Total + $ +
  • +
+ +
+

We've received your order and will begin processing it shortly. You can track the progress of your order using the button below.

+ Continue Shopping + Track Order +
+
+
- + diff --git a/order_status.php b/order_status.php index 87b595b4..1a1fd2d4 100644 --- a/order_status.php +++ b/order_status.php @@ -1,124 +1,175 @@

No order specified.

"; - include 'footer.php'; - exit(); -} - -$order_id = $_GET['order_id']; -$user_id = $_SESSION['user_id']; $pdo = db(); +$order = null; -// Fetch order details to ensure the user owns this order -$p_order = $pdo->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.user_id = ?"); -$p_order->execute([$order_id, $user_id]); -$order = $p_order->fetch(PDO::FETCH_ASSOC); +if ($user_id) { + // User is logged in, verify order belongs to them + $stmt = $pdo->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.user_id = ?"); + $stmt->execute([$order_id, $user_id]); + $order = $stmt->fetch(PDO::FETCH_ASSOC); +} elseif ($token) { + // Guest access, verify token + $stmt = $pdo->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.guest_token = ?"); + $stmt->execute([$order_id, $token]); + $order = $stmt->fetch(PDO::FETCH_ASSOC); +} if (!$order) { - echo "

Order not found or you do not have permission to view it.

"; + include 'header.php'; + echo "
Order not found or you do not have permission to view it.
"; include 'footer.php'; exit(); } // Fetch order items -$p_items = $pdo->prepare("SELECT oi.*, mi.name as item_name FROM order_items oi JOIN menu_items mi ON oi.menu_item_id = mi.id WHERE oi.order_id = ?"); -$p_items->execute([$order_id]); -$items = $p_items->fetchAll(PDO::FETCH_ASSOC); +$stmt = $pdo->prepare("SELECT oi.*, mi.name as item_name FROM order_items oi JOIN menu_items mi ON oi.menu_item_id = mi.id WHERE oi.order_id = ?"); +$stmt->execute([$order_id]); +$items = $stmt->fetchAll(PDO::FETCH_ASSOC); +include 'header.php'; ?>
-

Order Status for #

-
-
-
-
Live Status
-

Restaurant:

-

Order Date:

-
-

Status:

+
+
+
+
+

Thank You For Your Order!

+

Order #

+

Restaurant:

+
-
-
-
-
-
-
-
-
Order Summary
-
    - -
  • - (x) - $ -
  • - -
  • - Total - $ -
  • -
+
+
+

Order Status

+
+ +
+
+
+ +
+
+
Order Summary
+
    + +
  • + (x) + $ +
  • + +
  • + Total + $ +
  • +
+
+
+
- Back to Order History
- + \ No newline at end of file diff --git a/payment-success.php b/payment-success.php index cebfb2e1..ab8e3ef4 100644 --- a/payment-success.php +++ b/payment-success.php @@ -11,25 +11,44 @@ if (!isset($_GET['session_id'])) { $stripe_session_id = $_GET['session_id']; $pdo = db(); +$session_id = session_id(); \Stripe\Stripe::setApiKey($stripeSecretKey); try { $checkout_session = \Stripe\Checkout\Session::retrieve($stripe_session_id); $metadata = $checkout_session->metadata; - $user_id = $metadata->user_id; - $coupon_id = $metadata->coupon_id; + $user_id = $metadata->user_id ?? null; + $is_guest = !$user_id; if ($checkout_session->payment_status == 'paid') { - // Fetch user's address - $stmt = $pdo->prepare("SELECT address FROM users WHERE id = ?"); - $stmt->execute([$user_id]); - $user = $stmt->fetch(); - $delivery_address = $user ? $user['address'] : 'N/A'; + $delivery_address = null; + $phone_number = null; + $guest_name = null; + $guest_email = null; + $guest_token = null; + + if ($is_guest) { + $guest_name = $metadata->guest_name ?? ''; + $guest_email = $metadata->guest_email ?? ''; + $delivery_address = $metadata->guest_address ?? 'N/A'; + $phone_number = $metadata->guest_phone ?? 'N/A'; + $cart_identifier = $session_id; + $cart_column = 'session_id'; + $guest_token = $metadata->token ?? null; // Use token from metadata + } else { + $stmt = $pdo->prepare("SELECT address, phone FROM users WHERE id = ?"); + $stmt->execute([$user_id]); + $user = $stmt->fetch(); + $delivery_address = $user ? $user['address'] : 'N/A'; + $phone_number = $user ? $user['phone'] : 'N/A'; + $cart_identifier = $user_id; + $cart_column = 'user_id'; + } // Fetch cart items - $stmt = $pdo->prepare("SELECT c.*, mi.price, mi.restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id WHERE c.user_id = ?"); - $stmt->execute([$user_id]); + $stmt = $pdo->prepare("SELECT c.*, mi.price, mi.restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id WHERE c.$cart_column = ?"); + $stmt->execute([$cart_identifier]); $cart_items = $stmt->fetchAll(); if (empty($cart_items)) { @@ -39,11 +58,12 @@ try { $total_price = $_SESSION['total_price'] ?? 0; $discount_amount = $_SESSION['discount_amount'] ?? 0; + $coupon_id = $metadata->coupon_id ?? null; $restaurant_id = $cart_items[0]['restaurant_id']; // Assuming order from one restaurant // Create order - $stmt = $pdo->prepare("INSERT INTO orders (user_id, restaurant_id, total_price, status, stripe_session_id, delivery_address, coupon_id, discount_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$user_id, $restaurant_id, $total_price, 'paid', $stripe_session_id, $delivery_address, $coupon_id, $discount_amount]); + $stmt = $pdo->prepare("INSERT INTO orders (user_id, restaurant_id, total_price, status, stripe_session_id, delivery_address, phone_number, coupon_id, discount_amount, guest_name, guest_email, token) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$user_id, $restaurant_id, $total_price, 'paid', $stripe_session_id, $delivery_address, $phone_number, $coupon_id, $discount_amount, $guest_name, $guest_email, $guest_token]); $order_id = $pdo->lastInsertId(); // Insert order items @@ -53,8 +73,8 @@ try { } // Clear cart - $stmt = $pdo->prepare("DELETE FROM cart WHERE user_id = ?"); - $stmt->execute([$user_id]); + $stmt = $pdo->prepare("DELETE FROM cart WHERE $cart_column = ?"); + $stmt->execute([$cart_identifier]); // Clear coupon session variables unset($_SESSION['coupon_id']); @@ -64,8 +84,11 @@ try { unset($_SESSION['discount_amount']); unset($_SESSION['subtotal']); - $_SESSION['order_id'] = $order_id; + if ($is_guest) { + $_SESSION['token'] = $guest_token; + } + header("Location: order_confirmation.php"); exit(); diff --git a/paypal-capture.php b/paypal-capture.php index d0bac939..8c7d6839 100644 --- a/paypal-capture.php +++ b/paypal-capture.php @@ -5,13 +5,15 @@ require_once 'includes/api_keys.php'; header('Content-Type: application/json'); -if (!isset($_SESSION['user_id']) || !isset($_POST['orderID'])) { +if (!isset($_POST['orderID'])) { echo json_encode(['error' => 'Invalid request.']); exit(); } $orderID = $_POST['orderID']; -$user_id = $_SESSION['user_id']; +$user_id = $_SESSION['user_id'] ?? null; +$is_guest = !$user_id; +$session_id = session_id(); // Helper function to get PayPal access token function get_paypal_access_token($clientId, $secret, $apiBase) { @@ -56,15 +58,26 @@ $details = json_decode($result); if (isset($details->status) && $details->status == 'COMPLETED') { $pdo = db(); - // Fetch user's address - $stmt = $pdo->prepare("SELECT address FROM users WHERE id = ?"); - $stmt->execute([$user_id]); - $user = $stmt->fetch(); - $delivery_address = $user ? $user['address'] : 'N/A'; + $delivery_address = $_POST['address'] ?? 'N/A'; + $phone_number = $_POST['phone'] ?? 'N/A'; + $guest_name = null; + $guest_email = null; + $guest_token = null; + + if ($is_guest) { + $guest_name = $_POST['name'] ?? ''; + $guest_email = $_POST['email'] ?? ''; + $cart_identifier = $session_id; + $cart_column = 'session_id'; + $guest_token = bin2hex(random_bytes(16)); // Generate a unique token for guest orders + } else { + $cart_identifier = $user_id; + $cart_column = 'user_id'; + } // Fetch cart items - $stmt = $pdo->prepare("SELECT c.*, mi.price, mi.restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id WHERE c.user_id = ?"); - $stmt->execute([$user_id]); + $stmt = $pdo->prepare("SELECT c.*, mi.price, mi.restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id WHERE c.$cart_column = ?"); + $stmt->execute([$cart_identifier]); $cart_items = $stmt->fetchAll(); if (empty($cart_items)) { @@ -78,8 +91,8 @@ if (isset($details->status) && $details->status == 'COMPLETED') { $restaurant_id = $cart_items[0]['restaurant_id']; // Assuming order from one restaurant // Create order - $stmt = $pdo->prepare("INSERT INTO orders (user_id, restaurant_id, total_price, status, stripe_session_id, delivery_address, coupon_id, discount_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$user_id, $restaurant_id, $total_price, 'paid', $orderID, $delivery_address, $coupon_id, $discount_amount]); + $stmt = $pdo->prepare("INSERT INTO orders (user_id, restaurant_id, total_price, status, stripe_session_id, delivery_address, phone_number, coupon_id, discount_amount, guest_name, guest_email, token) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$user_id, $restaurant_id, $total_price, 'paid', $orderID, $delivery_address, $phone_number, $coupon_id, $discount_amount, $guest_name, $guest_email, $guest_token]); $order_id = $pdo->lastInsertId(); // Insert order items @@ -89,8 +102,8 @@ if (isset($details->status) && $details->status == 'COMPLETED') { } // Clear cart - $stmt = $pdo->prepare("DELETE FROM cart WHERE user_id = ?"); - $stmt->execute([$user_id]); + $stmt = $pdo->prepare("DELETE FROM cart WHERE $cart_column = ?"); + $stmt->execute([$cart_identifier]); // Clear coupon session variables unset($_SESSION['coupon_id']); @@ -101,6 +114,10 @@ if (isset($details->status) && $details->status == 'COMPLETED') { unset($_SESSION['subtotal']); $_SESSION['order_id'] = $order_id; + if ($is_guest) { + $_SESSION['token'] = $guest_token; + } + echo json_encode(['success' => true, 'order_id' => $order_id]); } else {