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.
*/