db->fetch('SELECT * FROM carts WHERE token = ? AND status = ?', [$token, 'active']); } if ($cart === null) { $token = bin2hex(random_bytes(24)); $this->db->execute( 'INSERT INTO carts (token, user_id, status) VALUES (?, ?, ?)', [$token, $userId, 'active'] ); setcookie('fatbottom_cart', $token, [ 'expires' => time() + 60 * 60 * 24 * 30, 'path' => '/', 'secure' => !empty($_SERVER['HTTPS']), 'httponly' => true, 'samesite' => 'Lax', ]); $cart = $this->db->fetch('SELECT * FROM carts WHERE token = ?', [$token]); } elseif ($userId !== null && empty($cart['user_id'])) { $this->db->execute('UPDATE carts SET user_id = ? WHERE id = ?', [$userId, (int) $cart['id']]); $cart['user_id'] = $userId; } $this->touch((int) $cart['id']); return $cart; } public function add(int $menuItemId, int $quantity, string $notes, ?int $userId = null): bool { $item = $this->db->fetch('SELECT * FROM menu_items WHERE id = ? AND active = 1', [$menuItemId]); if ($item === null) { return false; } $special = $this->db->fetch( "SELECT price_cents FROM specials WHERE menu_item_id = ? AND active = 1 AND price_cents IS NOT NULL AND (starts_at IS NULL OR starts_at <= CURRENT_TIMESTAMP) AND (ends_at IS NULL OR ends_at >= CURRENT_TIMESTAMP) ORDER BY price_cents ASC LIMIT 1", [$menuItemId] ); $unitPrice = $special !== null ? min((int) $item['price_cents'], (int) $special['price_cents']) : (int) $item['price_cents']; $cart = $this->current($userId); $notes = trim(substr($notes, 0, 240)); $existing = $this->db->fetch( 'SELECT * FROM cart_items WHERE cart_id = ? AND menu_item_id = ? AND notes = ?', [(int) $cart['id'], $menuItemId, $notes] ); if ($existing) { $this->db->execute( 'UPDATE cart_items SET quantity = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [min(20, (int) $existing['quantity'] + max(1, $quantity)), (int) $existing['id']] ); } else { $this->db->execute( 'INSERT INTO cart_items (cart_id, menu_item_id, quantity, unit_price_cents, notes) VALUES (?, ?, ?, ?, ?)', [(int) $cart['id'], $menuItemId, min(20, max(1, $quantity)), $unitPrice, $notes] ); } $this->recalculate((int) $cart['id']); return true; } public function update(array $quantities, ?int $userId = null): void { $cart = $this->current($userId); foreach ($quantities as $itemId => $quantity) { $itemId = (int) $itemId; $quantity = (int) $quantity; if ($quantity <= 0) { $this->db->execute('DELETE FROM cart_items WHERE id = ? AND cart_id = ?', [$itemId, (int) $cart['id']]); } else { $this->db->execute( 'UPDATE cart_items SET quantity = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND cart_id = ?', [min(20, $quantity), $itemId, (int) $cart['id']] ); } } $this->recalculate((int) $cart['id']); } public function remove(int $cartItemId, ?int $userId = null): void { $cart = $this->current($userId); $this->db->execute('DELETE FROM cart_items WHERE id = ? AND cart_id = ?', [$cartItemId, (int) $cart['id']]); $this->recalculate((int) $cart['id']); } public function details(?int $userId = null): array { $cart = $this->current($userId); $items = $this->db->fetchAll( 'SELECT ci.*, mi.name, mi.description, mi.active FROM cart_items ci JOIN menu_items mi ON mi.id = ci.menu_item_id WHERE ci.cart_id = ? ORDER BY ci.id', [(int) $cart['id']] ); $cart['items'] = $items; $cart['item_count'] = array_sum(array_map(static fn (array $item): int => (int) $item['quantity'], $items)); return $cart; } private function touch(int $cartId): void { $this->db->execute('UPDATE carts SET last_seen_at = CURRENT_TIMESTAMP WHERE id = ?', [$cartId]); } private function recalculate(int $cartId): void { $subtotal = (int) $this->db->fetch( 'SELECT COALESCE(SUM(quantity * unit_price_cents), 0) AS subtotal FROM cart_items WHERE cart_id = ?', [$cartId] )['subtotal']; $this->db->execute( 'UPDATE carts SET subtotal_cents = ?, last_seen_at = CURRENT_TIMESTAMP WHERE id = ?', [$subtotal, $cartId] ); } }