10-reservation-evenements

<?php
class EventBookingSystem {
    private array $bookings = [];
    
    public function bookTickets(Event $event, User $user, int $quantity, array $options = []): array {
        // Vérification disponibilité
        $available = $this->getAvailableSeats($event);
        if($quantity > $available) {
            throw new Exception("Not enough seats available");
        }
        
        // Calcul du prix
        $basePrice = $event->getPrice();
        $totalPrice = $basePrice * $quantity;
        
        // Remises Early Bird
        $daysUntilEvent = $this->getDaysUntil($event->getDate());
        if($daysUntilEvent > 60) {
            $totalPrice *= 0.80;
        } elseif($daysUntilEvent > 30) {
            $totalPrice *= 0.90;
        }
        
        // Remises groupe
        if($quantity >= 10) {
            $totalPrice *= 0.85;
        } elseif($quantity >= 5) {
            $totalPrice *= 0.92;
        }
        
        // Remises membre
        if($user->isMember()) {
            $totalPrice *= 0.90;
        }
        
        // Options supplémentaires
        if(isset($options['vip_access']) && $options['vip_access']) {
            $totalPrice += 50 * $quantity;
        }
        if(isset($options['meal_included']) && $options['meal_included']) {
            $totalPrice += 25 * $quantity;
        }
        if(isset($options['parking']) && $options['parking']) {
            $totalPrice += 10;
        }
        
        // Traitement du paiement
        $paymentMethod = $options['payment_method'] ?? 'card';
        if($paymentMethod === 'card') {
            $stripe = new StripeAPI();
            $stripe->setKey('sk_live_123456');
            $paymentResult = $stripe->charge($totalPrice, $user->getEmail());
            
            if(!$paymentResult['success']) {
                throw new Exception("Payment failed: " . $paymentResult['error']);
            }
        } elseif($paymentMethod === 'paypal') {
            $paypal = new PayPalAPI();
            $paypal->setCredentials('client_id', 'secret');
            $paymentResult = $paypal->processPayment($totalPrice, $user->getEmail());
            
            if(!$paymentResult['approved']) {
                throw new Exception("PayPal payment not approved");
            }
        } elseif($paymentMethod === 'bank_transfer') {
            // Génération IBAN de référence
            $reference = 'EVT-' . $event->getId() . '-' . time();
            // Pas de paiement immédiat, juste réservation temporaire
        }
        
        // Création de la réservation
        $db = new PDO('mysql:host=localhost;dbname=events','root','');
        $stmt = $db->prepare('INSERT INTO bookings (event_id,user_id,quantity,total_price,status) VALUES (?,?,?,?,?)');
        $status = $paymentMethod === 'bank_transfer' ? 'pending' : 'confirmed';
        $stmt->execute([$event->getId(), $user->getId(), $quantity, $totalPrice, $status]);
        $bookingId = $db->lastInsertId();
        
        // Mise à jour des places disponibles
        $stmt = $db->prepare('UPDATE events SET available_seats = available_seats - ? WHERE id = ?');
        $stmt->execute([$quantity, $event->getId()]);
        
        // Génération des billets
        $tickets = [];
        for($i = 0; $i < $quantity; $i++) {
            $ticketCode = $this->generateTicketCode();
            $stmt = $db->prepare('INSERT INTO tickets (booking_id,code,event_id,user_id) VALUES (?,?,?,?)');
            $stmt->execute([$bookingId, $ticketCode, $event->getId(), $user->getId()]);
            $tickets[] = $ticketCode;
            
            // Génération QR code
            $qrCode = $this->generateQRCode($ticketCode);
            file_put_contents("/var/tickets/qr_{$ticketCode}.png", $qrCode);
        }
        
        // Envoi email de confirmation
        $emailBody = $this->buildConfirmationEmail($event, $user, $tickets, $totalPrice);
        mail($user->getEmail(), "Booking Confirmation", $emailBody);
        
        // Notification SMS si demandé
        if(isset($options['sms_notification']) && $options['sms_notification']) {
            $twilio = new TwilioAPI();
            $twilio->setAuth('account_sid', 'token');
            $twilio->sendSMS($user->getPhone(), "Your booking is confirmed. Tickets: " . implode(', ', $tickets));
        }
        
        // Ajout au calendrier
        if(isset($options['add_to_calendar']) && $options['add_to_calendar']) {
            $icsContent = $this->generateICS($event, $user);
            file_put_contents("/var/calendar/event_{$bookingId}.ics", $icsContent);
            mail($user->getEmail(), "Add to Calendar", "Attached ICS file", [], "/var/calendar/event_{$bookingId}.ics");
        }
        
        // Logging et analytics
        $stmt = $db->prepare('INSERT INTO booking_analytics (event_id,user_id,quantity,revenue,booking_date) VALUES (?,?,?,?,?)');
        $stmt->execute([$event->getId(), $user->getId(), $quantity, $totalPrice, date('Y-m-d H:i:s')]);
        
        // Notification organisateur si grosse réservation
        if($quantity >= 10 || $totalPrice >= 1000) {
            mail($event->getOrganizerEmail(), "Large Booking Alert", "User {$user->getId()} booked {$quantity} tickets for {$totalPrice} EUR");
        }
        
        // Programme de fidélité
        $points = (int)($totalPrice / 10);
        $stmt = $db->prepare('UPDATE users SET loyalty_points = loyalty_points + ? WHERE id = ?');
        $stmt->execute([$points, $user->getId()]);
        
        return [
            'booking_id' => $bookingId,
            'tickets' => $tickets,
            'total_price' => $totalPrice,
            'status' => $status
        ];
    }
    
    private function getAvailableSeats(Event $event): int {
        $db = new PDO('mysql:host=localhost;dbname=events','root','');
        $stmt = $db->prepare('SELECT available_seats FROM events WHERE id = ?');
        $stmt->execute([$event->getId()]);
        return (int)$stmt->fetchColumn();
    }
    
    private function getDaysUntil(DateTime $date): int {
        $now = new DateTime();
        return (int)$now->diff($date)->days;
    }
    
    private function generateTicketCode(): string {
        return 'TKT-' . strtoupper(bin2hex(random_bytes(6)));
    }
    
    private function generateQRCode(string $data): string {
        // Simulation génération QR code
        return "QR_CODE_DATA_FOR_$data";
    }
    
    private function buildConfirmationEmail(Event $event, User $user, array $tickets, float $price): string {
        return "Dear {$user->getName()},\n\nYour booking for {$event->getName()} is confirmed.\nTickets: " . implode(', ', $tickets) . "\nTotal: {$price} EUR";
    }
    
    private function generateICS(Event $event, User $user): string {
        return "BEGIN:VCALENDAR\nVERSION:2.0\nEVENT:{$event->getName()}\nEND:VCALENDAR";
    }
}

class Event {
    public function getId(): int { return 1; }
    public function getName(): string { return "Concert"; }
    public function getPrice(): float { return 50.0; }
    public function getDate(): DateTime { return new DateTime('+45 days'); }
    public function getOrganizerEmail(): string { return 'organizer@example.com'; }
}

class User {
    public function getId(): int { return 1; }
    public function getName(): string { return "John Doe"; }
    public function getEmail(): string { return 'john@example.com'; }
    public function getPhone(): string { return '+33612345678'; }
    public function isMember(): bool { return true; }
}

class StripeAPI {
    public function setKey(string $key): void { }
    public function charge(float $amount, string $email): array { return ['success' => true]; }
}

class PayPalAPI {
    public function setCredentials(string $id, string $secret): void { }
    public function processPayment(float $amount, string $email): array { return ['approved' => true]; }
}

class TwilioAPI {
    public function setAuth(string $sid, string $token): void { }
    public function sendSMS(string $to, string $message): bool { return true; }
}

/*
=== USER STORIES ===

US1: En tant que développeur, je veux tester la logique de calcul de prix sans 
     dépendre de la BDD, des APIs de paiement, de l'envoi d'emails/SMS ni de la 
     génération de fichiers.

US2: En tant que product owner, je veux ajouter Stripe Checkout comme nouvelle 
     méthode de paiement sans modifier la classe principale.

US3: En tant que marketing manager, je veux créer des règles de pricing dynamiques 
     (ex: remise 15% les mardis, remise 25% pour les étudiants) sans toucher au code.

US4: En tant que event manager, je veux pouvoir envoyer les confirmations via 
     WhatsApp, Telegram ou email selon les préférences utilisateur.

US5: En tant que data analyst, je veux tracker les bookings dans Google Analytics 
     et Mixpanel en plus de la BDD.

US6: En tant que compliance officer, je veux appliquer des règles métier spécifiques 
     (ex: limite d'âge, limite de billets par personne) de manière extensible.

US7: En tant que product manager, je veux implémenter un système de liste d'attente 
     quand l'événement est complet, avec notifications automatiques lors d'annulations.

US8: En tant que business analyst, je veux simuler des scénarios de booking 
     (prévisions de revenus) sans créer de vraies réservations.

US9: En tant que technical lead, je veux migrer progressivement vers un système 
     de messaging asynchrone (RabbitMQ) pour les emails/SMS sans tout réécrire.

US10: En tant que product owner, je veux permettre la revente de billets entre 
     utilisateurs avec validation et commission.
*/