src/Controller/AccueilController.php line 74

Open in your IDE?
  1. <?php
  2. // src/Controller/AccueilController
  3. /*
  4.  * 
  5.  * Date de modification:16/05/2025
  6.  * Description:Controller LayoutAccueil 5sur5 Séjour
  7.  *
  8.  */
  9. namespace App\Controller;
  10. use App\Entity\Fonctions;
  11. use App\Entity\User;
  12. use App\Entity\Ref;
  13. use App\Entity\Commande;
  14. use App\Entity\Typeproduit;
  15. use App\Service\AttachementService;
  16. use App\Service\BlogService;
  17. use App\Service\EmailsCmdService;
  18. use App\Service\SejourService;
  19. use App\Service\SiteService;
  20. use App\Service\TypeProduiteService;
  21. use App\Service\UserService;
  22. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  23. use Symfony\Component\Routing\Annotation\Route;
  24. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  25. use Symfony\Component\HttpFoundation\Response;
  26. use Symfony\Component\HttpFoundation\Request;
  27. use Symfony\Component\HttpFoundation\JsonResponse;
  28. use Doctrine\ORM\EntityManagerInterface;
  29. use Symfony\Component\Mailer\MailerInterface;
  30. use Symfony\Bridge\Twig\Mime\TemplatedEmail;
  31. use Symfony\Component\Mime\Address;
  32. use Psr\Log\LoggerInterface;
  33. class AccueilController extends AbstractController
  34. {
  35.     private $typeProduiteService;
  36.     private $siteService;
  37.     private $blogService;
  38.     private $UserService;
  39.     private $sejourService;
  40.     private $attachementService;
  41.     private $mailer;
  42.     private $logger;
  43.     private $emailsCmdService;
  44.     public function __construct(TypeProduiteService $typeProduiteServiceSiteService $siteServiceBlogService $blogServiceUserService $UserServiceSejourService $sejourServiceAttachementService $attachementServiceMailerInterface $mailerLoggerInterface $loggerEmailsCmdService $emailsCmdService)
  45.     {
  46.         $this->typeProduiteService $typeProduiteService;
  47.         $this->siteService $siteService;
  48.         $this->blogService $blogService;
  49.         $this->UserService $UserService;
  50.         $this->sejourService $sejourService;
  51.         $this->attachementService $attachementService;
  52.         $this->mailer $mailer;
  53.         $this->logger $logger;
  54.         $this->emailsCmdService $emailsCmdService;
  55.     }
  56.     /**
  57.      * @Route("/Accueil5sur5", name="layoutAccueil")
  58.      * */
  59.     public function layoutAccueil()
  60.     {
  61.         return $this->render('Accueil/layoutAccueil.html.twig');
  62.     }
  63.     /**
  64.      * @Route("/Accueil5sur5/header_layoutAccueil", name="header_layoutAccueil")
  65.      */
  66.     public function header(Request $request): Response
  67.     {
  68.         $produit $this->typeProduiteService;
  69.         $liste $produit->produitlistType();
  70.         // dd($liste);
  71.         $session $request->getSession();
  72.         $Products $session->get("Panier");
  73.         if ($Products == Null) {
  74.             $Products = [];
  75.         }
  76.         return $this->render('Accueil/header.html.twig', [
  77.             'produit' => $liste,
  78.             'Products' => $Products,
  79.         ]);
  80.     }
  81.     /**
  82.      * @Route("/Accueil5sur5/header_Parent", name="header_Parent")
  83.      */
  84.     public function header_Parent(Request $request): Response
  85.     {
  86.         $produit $this->typeProduiteService;
  87.         $liste $produit->produitlistType();
  88.         // dd($liste);
  89.         $session $request->getSession();
  90.         $Products $session->get("Panier");
  91.         if ($Products == Null) {
  92.             $Products = [];
  93.         }
  94.         return $this->render('Accueil/headerParents.html.twig', [
  95.             'produit' => $liste,
  96.             'Products' => $Products,
  97.         ]);
  98.     }
  99.     /**
  100.      * @Route("/Accueil5sur5/headerLogin", name="headerLogin")
  101.      */
  102.     public function headerLogin(Request $request): Response
  103.     {
  104.         $produit $this->typeProduiteService;
  105.         $liste $produit->produitlistType();
  106.         // dd($liste);
  107.         $session $request->getSession();
  108.         $Products $session->get("Panier");
  109.         if ($Products == Null) {
  110.             $Products = [];
  111.         }
  112.         return $this->render('Accueil/headerLogin.html.twig', [
  113.             'produit' => $liste,
  114.             'Products' => $Products,
  115.         ]);
  116.     }
  117.     /**
  118.      * @Route("/", name="page_Accueil")
  119.      */
  120.     public function PageAccueil()
  121.     {
  122.         $produit $this->typeProduiteService;
  123.         $liste $produit->produitlistType();
  124.         $siteservice $this->siteService;
  125.         $site $siteservice->getActiveSite();
  126.         $list $this->blogService->allblog();
  127.         return $this->render('Accueil/PageAccueil.html.twig', [
  128.             "site" => $site
  129.             'list' => $list
  130.             'produit' => $liste,
  131.             'recaptcha_site_key' => $this->getParameter('recaptcha.site_key')
  132.         ]);
  133.     }
  134.     /**
  135.      * @Route("/Parent/", name="Parent")
  136.      */
  137.     public function Acceuil(Request $request)
  138.     {
  139.         $produit $this->typeProduiteService;
  140.         $liste $produit->produitlistType();
  141.         $siteservice $this->siteService;
  142.         $site $siteservice->getActiveSite();
  143.         $list $this->blogService->allblog();
  144.         $session $request->getSession();
  145.         $Products $session->get("Panier");
  146.         if ($Products == Null) {
  147.             $Products = [];
  148.         }
  149.         return $this->render('Accueil/PageAccueil.html.twig', [
  150.             "site" => $site
  151.             'list' => $list
  152.             'produit' => $liste
  153.             'Products' => $Products,
  154.             'recaptcha_site_key' => $this->getParameter('recaptcha.site_key')
  155.         ]);
  156.     }
  157.     /**
  158.      * Compatibilité ancienne URL parent.
  159.      * @Route("/Parent/AccueilParent", name="AccueilParent")
  160.      */
  161.     public function AccueilParentLegacy(Request $request): Response
  162.     {
  163.         $codeSejour trim((string) $request->query->get('codeSejour'''));
  164.         if ($codeSejour !== '') {
  165.             return $this->redirectToRoute('app_back_Parent', ['codeSejour' => strtoupper($codeSejour)]);
  166.         }
  167.         return $this->redirectToRoute('Parent');
  168.     }
  169.     /**
  170.      * @Route("/Accueil5sur5/footer_layoutAccueil", name="footer_layoutAccueil")
  171.      */
  172.     public function footer()
  173.     {
  174.         return $this->render('Accueil/footer.html.twig');
  175.     }
  176.     /**
  177.      * @Route("/Accompagnateur/register", name="accomp_register",methods={"POST","GET"})
  178.      */
  179.     public function register(Request $request\App\Service\RecaptchaService $recaptchaService)
  180.     {
  181.         try {
  182.             error_log("🔍 DEBUG accomp_register - Début du traitement pour: " $request->get('email'));
  183.             
  184.             // ===== VALIDATION RECAPTCHA V3 =====
  185.             $recaptchaToken $request->get('recaptcha_token''');
  186.             $recaptchaResult $recaptchaService->verify($recaptchaToken'creation_sejour'$request->getClientIp());
  187.             
  188.             if (!$recaptchaResult['success']) {
  189.                 $this->logger->warning('🤖 Échec validation reCAPTCHA', [
  190.                 'ip' => $request->getClientIp(),
  191.                     'error' => $recaptchaResult['error'],
  192.                     'score' => $recaptchaResult['score'] ?? null,
  193.                 'email' => $request->get('email')
  194.             ]);
  195.                 return new JsonResponse([
  196.                     'success' => false,
  197.                     'error' => 'Validation de sécurité échouée. Veuillez réessayer.'
  198.                 ], 429);
  199.             }
  200.             error_log("✅ reCAPTCHA validé avec score: " . ($recaptchaResult['score'] ?? 'N/A'));
  201.         // Forcer type_user=EF si la requête vient de la page écoles publiques
  202.         $referer $request->headers->get('referer');
  203.         if ($referer && strpos($referer'/ecoles-publiques/creer') !== false) {
  204.             $request->request->set('type_user''EF');
  205.             error_log("🏫 Création depuis page écoles publiques → Force type_user=EF");
  206.         }
  207.         ini_set("max_execution_time", -1);
  208.         ini_set('memory_limit''-1');
  209.         $em $this->getDoctrine()->getManager();
  210.         $UserService $this->UserService;
  211.         $SejourService $this->sejourService;
  212.         $AttachementService $this->attachementService;
  213.         $nom $request->get("nom_acc");
  214.         $prenom $request->get('prenom_acc');
  215.         $etablisment $request->get("etablisment");
  216.         $fonction $request->get("fonction");
  217.         $EntityFonction $em->getRepository(Fonctions::class)->find($fonction);
  218.         if ($EntityFonction) {
  219.             $nameFonction $EntityFonction->getName();
  220.         } else {
  221.             $nameFonction "";
  222.         }
  223.         $adressetablisment $request->get("adressetablisment");
  224.         // prix et reverse
  225.         $prixcnxparent 2.90;
  226.         $prixcnxpartenaire 0;
  227.         $reversecnxpart 0;
  228.         $reverseventepart 0;
  229.         $phoneacc $request->get("phone_acc");
  230.         $mail $request->get("email");
  231.         $password $request->get("password");
  232.         $role "ROLE_ACC";
  233.         $themSejour $request->get('theme_sejour');
  234.         $adressSejour $request->get("adress_sejour");
  235.         $codePostal $request->get("codePostal");
  236.         $dateDebut $request->get("date_debut_sejour");
  237.         $FinSejour $request->get("date_fin_sejour");
  238.         $AgeDugroupe $request->get("age_dugroupe_sejour");
  239.         $NbEnfant $request->get("NbEnfant");
  240.         $pays $request->get("pays");
  241.         $ville $request->get("ville");
  242.         $CODEpOSTALetablisment $request->get("CODEpOSTALetablisment");
  243.         $villeetablisment $request->get("villeetablisment");
  244.         // Récupérer le type de séjour depuis le formulaire (EP, EF, PF)
  245.         $type_user $request->get("type_user");
  246.         if (!$type_user) {
  247.             $type_user "PP"// Par défaut: École Publique (parents paient)
  248.         }
  249.         error_log("🔍 DEBUG register - type_user received: " $type_user);
  250.         error_log("🔍 DEBUG register - payment_method_structure: " . ($request->get('payment_method_structure') ?? 'NULL'));
  251.         // Mapper le type_user vers le type de séjour et connpay
  252.         // Logique du code séjour : [Type][Pay][Timestamp]
  253.         // Type: E=École, P=Partenaire, C=CSE
  254.         // Pay: P=Payant (parents), F=Free (gratuit/structure)
  255.         switch ($type_user) {
  256.             case 'EF':
  257.                 // École Française (page dédiée) = École + Gratuit
  258.                 $type "ECOLES/AUTRES";
  259.                 $connpay 0// Gratuit (0 = Free)
  260.                 break;
  261.             case 'PF':
  262.                 // Partenaire Financé (structure prend en charge) = Partenaire + Structure finance
  263.                 $type "PARTENAIRES/VOYAGISTES";
  264.                 $connpay 0// Structure finance (0 = Free)
  265.                 break;
  266.             case 'PP':
  267.                 // Partenaire Payant (parents paient) = Partenaire + Parents paient
  268.                 $type "PARTENAIRES/VOYAGISTES";
  269.                 $connpay 1// Parents paient (1 = Payant)
  270.                 break;
  271.             default:
  272.                 $type "PARTENAIRES/VOYAGISTES";
  273.                 $connpay 1;
  274.                 break;
  275.         }
  276.         error_log("🔍 Type reçu du formulaire: type_user=$type_user → Type séjour=$type, connpay=$connpay");
  277.         $bytes random_bytes(5);
  278.         $passAcommpa bin2hex($bytes);
  279.         if ($NbEnfant) {
  280.             $NbEnfant $NbEnfant;
  281.         } else {
  282.             $NbEnfant 0;
  283.         }
  284.         $dateSJoue = new \Datetime();
  285.         $Milliseconde $dateSJoue->format('u');
  286.         $annes $dateSJoue->format('y');
  287.         $joours $dateSJoue->format('d');
  288.         $joours $dateSJoue->format('t');
  289.         $emailaccf "Acompa" $annes $joours $Milliseconde "@fictif.com";
  290.         //CREATION ACCOMPAGNATEUR
  291.         $accomp $UserService->creationNewAcommpa($nom$prenom$etablisment$nameFonction$adressetablisment$phoneacc$emailaccf$role$passAcommpa$mail);
  292.         $accomp->setIdFonction($EntityFonction);
  293.         $em->persist($accomp);
  294.         $em->flush();
  295.         //CREATION PARTENAIRE
  296.         $part $em->getRepository(User::class)->VerifierAddresseEmailPartenaire($mail'ROLE_PARTENAIRE');
  297.         if (is_null($part)) {
  298.             $part $UserService->creationNewUser($nom$prenom$etablisment$nameFonction$adressetablisment$phoneacc$mail$passAcommpa"ROLE_PARTENAIRE");
  299.             $part->setIdFonction($EntityFonction);
  300.             $em->persist($part);
  301.             $em->flush();
  302.         }
  303.         //CREATION ETABLISSEMENT + PARTENAIRE
  304.         $Etablisment $UserService->creationNewEtablisment($part$nom$prenom$etablisment$fonction$adressetablisment$phoneacc$mail"ROLE_PARTENAIRE"$password$prixcnxparent$prixcnxpartenaire$reversecnxpart$reverseventepart);
  305.         if ($CODEpOSTALetablisment == "") {
  306.             $CODEpOSTALetablisment null;
  307.         }
  308.         $Etablisment->setCodepostaleatb($CODEpOSTALetablisment);
  309.         $Etablisment->setVille($villeetablisment);
  310.         $em->persist($Etablisment);
  311.         $em->flush();
  312.         //CREATION SEJOUR ACCOM + PARTENAIRE + ETABILSSEMENT
  313.         // Ne pas surcharger $connpay via la fonction (ex: VIP)
  314.         // Le code séjour doit refléter exclusivement le choix utilisateur (type_user)
  315.         $sejour $SejourService->CreationNouveauSejourParAccompagnateur($themSejour$adressSejour$dateDebut$FinSejour$AgeDugroupe$type$NbEnfant$connpay$pays$ville$prixcnxparent$prixcnxpartenaire$reversecnxpart$reverseventepart);
  316.         $sejour->setCodePostal(intval($codePostal));
  317.         $SejourService->affecterAccompaniateur($sejour$accomp);
  318.         $SejourService->affecterPartenaire($sejour$part);
  319.         $SejourService->affecteretablisment($sejour$Etablisment);
  320.         $em->persist($sejour);
  321.         $em->flush();
  322.         // Gestion du paiement par la structure
  323.         $paymentMethodStructure $request->get('payment_method_structure');
  324.         $billingEmail $request->get('billing_email');
  325.         $billingContact $request->get('billing_contact');
  326.         $payerVoice $request->get('payer_voice');
  327.         $voicePack $request->get('voice_pack');
  328.         // Déterminer si la structure doit payer
  329.         $structurePays = ($type_user === 'PF' && $paymentMethodStructure);
  330.         $structurePaysVoice = ($payerVoice === 'structure' && $voicePack && $paymentMethodStructure);
  331.         if ($structurePays || $structurePaysVoice) {
  332.             $montantHT 0;
  333.             $periodeLabel '';
  334.             if ($structurePays) {
  335.                 $montantHT += 39;
  336.                 $periodeLabel 'sejour_simple';
  337.             }
  338.             if ($structurePaysVoice && $voicePack) {
  339.                 $voiceTier intval($voicePack);
  340.                 $voiceHT = ($voiceTier === 35) ? 39 70;
  341.                 $montantHT += $voiceHT;
  342.                 if ($periodeLabel) {
  343.                     $periodeLabel .= '+boite_vocale_' $voiceTier;
  344.                 } else {
  345.                     $periodeLabel 'boite_vocale_' $voiceTier;
  346.                 }
  347.             }
  348.             $montantTTC round($montantHT 1.202);
  349.             // Récupérer le statut "en attente de paiement"
  350.             $statutAttentePaiement $em->getRepository(\App\Entity\Ref::class)->findOneBy(['libiller' => 'attente_paiement']);
  351.             if (!$statutAttentePaiement) {
  352.                 // Créer le statut s'il n'existe pas
  353.                 $statutAttentePaiement = new \App\Entity\Ref();
  354.                 $statutAttentePaiement->setLibiller('attente_paiement');
  355.                 // Pas de typeref nécessaire ici, on reste cohérent avec les autres statuts "attente_paiement"
  356.                 $em->persist($statutAttentePaiement);
  357.                 $em->flush();
  358.             }
  359.             // Mettre le séjour en attente de paiement
  360.             $sejour->setStatut($statutAttentePaiement);
  361.             $em->persist($sejour);
  362.             $em->flush();
  363.             // Créer une commande pour le séjour
  364.             $commande = new Commande();
  365.             $commande->setIdUser($part);
  366.             $commande->setIdSejour($sejour);
  367.             // Récupérer le statut "en attente" pour la commande
  368.             $statutCommandeEnAttente $em->getRepository(\App\Entity\Ref::class)->find(1); // 1 = en attente
  369.             if ($statutCommandeEnAttente) {
  370.                 $commande->setStatut($statutCommandeEnAttente);
  371.             }
  372.             $commande->setMontantht($montantHT);
  373.             $commande->setMontantrth($montantTTC);
  374.             $commande->setMontanenv($montantTTC);
  375.             $commande->setTva(20);
  376.             $commande->setDateCreateCommande(new \DateTime());
  377.             $commande->setNumComande(time());
  378.             $commande->setPeriode($periodeLabel);
  379.             // Normaliser le mode de paiement
  380.             $paymentTypeNormalized = ($paymentMethodStructure === 'carte') ? 'up2pay' 'rib';
  381.             $commande->setPaymentType($paymentTypeNormalized);
  382.             $em->persist($commande);
  383.             $em->flush();
  384.             error_log("💰 Commande créée pour séjour #{$sejour->getId()}, mode de paiement: {$paymentTypeNormalized}");
  385.             // Stocker les infos de facturation en session pour l'envoi RIB si nécessaire
  386.             $session $request->getSession();
  387.             $session->set('sejour_payment_info', [
  388.                 'sejour_id' => $sejour->getId(),
  389.                 'commande_id' => $commande->getId(),
  390.                 'payment_method' => $paymentTypeNormalized,
  391.                 'billing_email' => $billingEmail,
  392.                 'billing_contact' => $billingContact
  393.             ]);
  394.             // Ne pas envoyer les codes immédiatement si virement (sera envoyé avec le RIB)
  395.             // Si carte : les codes seront envoyés après paiement
  396.             if ($paymentTypeNormalized === 'rib') {
  397.                 // Les codes seront envoyés via la route send-rib-codes
  398.                 error_log("📧 Mode virement : codes séjour seront envoyés avec le RIB");
  399.                 $UserService->EnvoyerEmailAcommpatActivationNewMail($sejour$accomp);
  400.             } else {
  401.                 // Pour carte (Up2Pay) : les codes seront envoyés après paiement réussi
  402.                 error_log("💳 Mode carte Up2Pay : codes séjour seront envoyés après paiement");
  403.                 $UserService->EnvoyerEmailAcommpatActivationNewMail($sejour$accomp);
  404.             }
  405.             //AFFECTAION LOGO TO USER PARTENAIRE ADN ACCOMPAGNATEUR
  406.             if ($request->request->has('path')) {
  407.                 $ty "logo séjour";
  408.                 $AttachementService->creationLogoSejour($accomp$request->get('path'), $ty);
  409.                 $AttachementService->creationLogoSejour($part$request->get('path'), $ty);
  410.             }
  411.             $TypeSejour 8;
  412.             $session $request->getSession();
  413.             $session->set('Sejour'$sejour->getId());
  414.             // Return Up2Pay payment response for carte
  415.             if ($paymentMethodStructure === 'carte') {
  416.                 return new JsonResponse([
  417.                     'success' => true,
  418.                     'paymentType' => 'up2pay',
  419.                     'message' => 'Redirection vers le paiement sécurisé Up2Pay...',
  420.                     'idSejour' => $sejour->getId(),
  421.                     'commandeId' => $commande->getId()
  422.                 ]);
  423.             }
  424.             // For RIB payment, return success (no payment gateway redirect needed)
  425.             return new JsonResponse(["idSejour" => $sejour->getId()]);
  426.         } else {
  427.             // Si parents paient ou pas de mode de paiement : envoyer les codes normalement
  428.             $UserService->EnvoyerEmailAcommpatActivationNewMail($sejour$accomp);
  429.         }
  430.         //AFFECTAION LOGO TO USER PARTENAIRE ADN ACCOMPAGNATEUR
  431.         if ($request->request->has('path')) {
  432.             $ty "logo séjour";
  433.             $AttachementService->creationLogoSejour($accomp$request->get('path'), $ty);
  434.             $AttachementService->creationLogoSejour($part$request->get('path'), $ty);
  435.         }
  436.         $TypeSejour 8;
  437.         $session $request->getSession();
  438.         $session->set('Sejour'$sejour->getId());
  439.         //$this->redirectToRoute('app_back_Acommpa');
  440.         return new JsonResponse(["idSejour" => $sejour->getId()]);
  441.         
  442.         } catch (\Exception $e) {
  443.             error_log("❌ Erreur dans accomp_register: " $e->getMessage());
  444.             error_log("Stack trace: " $e->getTraceAsString());
  445.             
  446.             return new JsonResponse([
  447.                 'success' => false,
  448.                 'error' => 'Une erreur est survenue lors de la création du séjour: ' $e->getMessage()
  449.             ], 500);
  450.         }
  451.     }
  452.     //inscription du parent 
  453.     /**
  454.      * @Route("/Parent/register", name="parent_register",methods={"POST","GET"})
  455.      */
  456.     public function registerparent(Request $request)
  457.     {
  458.         $UserService $this->UserService;
  459.         //
  460.         $nom $request->get("nomparent");
  461.         $prenom $request->get('prenomparent');
  462.         $mailparent $request->get("mailparent");
  463.         $numtel $request->get("numtel");
  464.         $passwordparent $request->get("passwordparent");
  465.         $confirmpassword $request->get("confirmpassword");
  466.         $notifsms $request->get("sms");
  467.         $notifmail $request->get('mailnotif');
  468.         if ($confirmpassword != $passwordparent) {
  469.             return new JsonResponse('erorpasswordconfirm');
  470.         }
  471.         $role "ROLE_PARENT";
  472.         // Bloquer seulement s’il existe déjà un compte Parent avec cet email
  473.         if ($UserService->findParentByEmail($mailparent) !== null) {
  474.             return new JsonResponse('eror');
  475.         }
  476.         $existingUser $UserService->verifmailold($mailparent);
  477.         if ($existingUser === null) {
  478.             $UserService->creationNewParent($nom$prenom$mailparent$numtel$role$passwordparent$notifsms$notifmail);
  479.             return new JsonResponse('done');
  480.         }
  481.         // Un compte existe (Partenaire, Admin, etc.) sans ROLE_PARENT : on ajoute le rôle Parent et on met à jour
  482.         $UserService->addParentRoleAndUpdate($existingUser$nom$prenom$numtel$passwordparent$notifsms$notifmail);
  483.         return new JsonResponse('done');
  484.     }
  485.     /**
  486.      * @Route("/Accueil5sur5/5sur5", name="sur5")
  487.      */
  488.     public function sur5()
  489.     {
  490.         $produit $this->typeProduiteService;
  491.         $liste $produit->produitlistType();
  492.         //dd($liste);
  493.         return $this->render('Accueil/5sur5.html.twig', [
  494.             'produit' => $liste,
  495.         ]);
  496.     }
  497.     /**
  498.      * @Route("/Parent/Accueil5sur5/5sur5", name="5sur5Parent")
  499.      */
  500.     public function sur5Parent()
  501.     {
  502.         $produit $this->typeProduiteService;
  503.         $liste $produit->produitlistType();
  504.         //dd($liste);
  505.         return $this->render('Accueil/5sur5.html.twig', [
  506.             'produit' => $liste,
  507.         ]);
  508.     }
  509.     /**
  510.      * @Route("/Accueil5sur5/album/{id}", name="album")
  511.      */
  512.     public function produitlist($id)
  513.     {
  514.         $produit $this->typeProduiteService;
  515.         $liste $produit->produitlistType();
  516.         $produit $id;
  517.         //dd($liste);
  518.         return $this->render('Accueil/album.html.twig', [
  519.             'produit' => $liste,
  520.             'showArt' => $produit,
  521.         ]);
  522.     }
  523.     /**
  524.      * @Route("/Parent/Accueil5sur5/album/{id}", name="albumParent")
  525.      */
  526.     public function produitlistParent($id)
  527.     {
  528.         $produit $this->typeProduiteService;
  529.         $liste $produit->produitlistType();
  530.         // dd($liste);
  531.         $produit $id;
  532.         return $this->render('Accueil/album.html.twig', [
  533.             'produit' => $liste,
  534.             'showArt' => $produit,
  535.         ]);
  536.     }
  537.     /**
  538.      * @Route("/Accueil5sur5/Boutique_Souvenirs", name="boutique5sur5_Souvenir")
  539.      */
  540.     public function boutique5sur5()
  541.     {
  542.         $produit $this->typeProduiteService;
  543.         $liste $produit->produitlistTypeConditionnement();
  544.         return $this->render('Accueil/boutique_5sur5.html.twig', [
  545.             'produit' => $liste,
  546.         ]);
  547.     }
  548.     /**
  549.      * @Route("/Accueil5sur5/AlaUne", name="AlaUne")
  550.      */
  551.     public function AlaUne()
  552.     {
  553.         $blogsbolt $this->blogService;
  554.         $list $blogsbolt->allblog();
  555.         //dd($list);
  556.         return $this->render('Accueil/AlaUne.html.twig', ['list' => $list]);
  557.     }
  558.     /**
  559.      * @Route("/Parent/Accueil5sur5/AlaUne", name="AlaUneParent")
  560.      */
  561.     public function AlaUneParent()
  562.     {
  563.         $blogsbolt $this->blogService;
  564.         $list $blogsbolt->allblog();
  565.         //dd($list);
  566.         return $this->render('Accueil/AlaUneParent.html.twig', ['list' => $list]);
  567.     }
  568.     /**
  569.      * @Route("/Accueil5sur5/DetailsAlaUne/{id}", name="DetailsAlaUne")
  570.      */
  571.     public function DetailsAlaUne($id)
  572.     {
  573.         $blo $this->blogService;
  574.         $blog $blo->myblog($id);
  575.         return $this->render('Accueil/DetailsAlaUne.html.twig', ['blog' => $blog]);
  576.     }
  577.     /**
  578.      * @Route("/ServiceClient_5sur5", name="ServiceClient_5sur5")
  579.      * */
  580.     public function ServiceClient_5sur5()
  581.     {
  582.         $produit $this->typeProduiteService;
  583.         $liste $produit->produitlistType();
  584.         // dd($liste);
  585.         return $this->render('Accueil/ServiceClient_5sur5.html.twig', [
  586.             'produit' => $liste,
  587.         ]);
  588.     }
  589.     /**
  590.      * @Route("/Besoindaide_5sur5_accueil", name="Besoindaide_5sur5_accueil")
  591.      */
  592.     public function Besoindaide_5sur5_accueil(): Response
  593.     {
  594.         $produit $this->typeProduiteService;
  595.         $liste $produit->produitlistType();
  596.         // dd($liste);
  597.         return $this->render('Accueil/besoindaide_5sur5_accueil.html.twig', [
  598.             'produit' => $liste,
  599.         ]);
  600.     }
  601.     /**
  602.      * @Route("/help/microphone", name="help_microphone")
  603.      */
  604.     public function helpMicrophone(): Response
  605.     {
  606.         return $this->render('Accueil/help_microphone.html.twig', []);
  607.     }
  608.     /**
  609.      * @Route("/Mentionlegale_5sur5", name="Mentionlegale_5sur5")
  610.      */
  611.     public function Mentionlegale()
  612.     {
  613.         return $this->render('Accueil/mentionlegal.html.twig', []);
  614.     }
  615.     /**
  616.      * @Route("/Conditons_generales_5sur5", name="Conditons_generales_5sur5")
  617.      */
  618.     public function Conditons_generales()
  619.     {
  620.         return $this->render('Accueil/ConditionsGenerales.html.twig', []);
  621.     }
  622.     /**
  623.      * @Route("/Politique_Confidentialite_5sur5", name="Politique_Confidentialite_5sur5")
  624.      */
  625.     public function PolitiqueConfidentialite()
  626.     {
  627.         return $this->render('Accueil/PolitiqueConfidentialite.html.twig', []);
  628.     }
  629.     /**
  630.      * @Route("/unsubscribe-user", name="unsubscribe_user")
  631.      */
  632.     public function unsubscribeUser(Request $requestEntityManagerInterface $em): Response
  633.     {
  634.         $email $request->query->get('email');
  635.         if (!$email) {
  636.             return $this->render('unsubscribe/error.html.twig', [
  637.                 'message' => 'Email manquant'
  638.             ]);
  639.         }
  640.         $user $em->getRepository(User::class)->findOneBy(['email' => $email]);
  641.         if (!$user) {
  642.             return $this->render('unsubscribe/error.html.twig', [
  643.                 'message' => 'Utilisateur non trouvé'
  644.             ]);
  645.         }
  646.         $user->setShowpubprod(\App\Entity\User::SHOWPUBPROD_DESABONNE_EMAIL);
  647.         $em->flush();
  648.         return $this->render('unsubscribe/success.html.twig', [
  649.             'email' => $email
  650.         ]);
  651.     }
  652.     /**
  653.      * Désabonnement SMS : marque le parent comme ne souhaitant plus recevoir de SMS (smsnotif = 0).
  654.      * @Route("/unsubscribe-sms", name="unsubscribe_sms")
  655.      */
  656.     public function unsubscribeSms(Request $requestEntityManagerInterface $em): Response
  657.     {
  658.         $tel $request->query->get('tel');
  659.         if (!$tel || trim($tel) === '') {
  660.             return $this->render('unsubscribe/error.html.twig', [
  661.                 'message' => 'Numéro de téléphone manquant'
  662.             ]);
  663.         }
  664.         $digits preg_replace('/\D/'''$tel);
  665.         if (strlen($digits) === 10 && $digits[0] === '0') {
  666.             $digits '33' substr($digits1);
  667.         } elseif (strlen($digits) === && $digits[0] !== '3') {
  668.             $digits '33' $digits;
  669.         }
  670.         if (strlen($digits) < 10) {
  671.             return $this->render('unsubscribe/error.html.twig', [
  672.                 'message' => 'Numéro invalide'
  673.             ]);
  674.         }
  675.         $formatted33 $digits;
  676.         $formatted0 '0' substr($digits2);
  677.         $user $em->getRepository(User::class)->findOneBy(['nummobile' => $formatted0]);
  678.         if (!$user) {
  679.             $user $em->getRepository(User::class)->findOneBy(['nummobile' => $formatted33]);
  680.         }
  681.         if (!$user) {
  682.             $user $em->getRepository(User::class)->findOneBy(['nummobile' => $tel]);
  683.         }
  684.         if (!$user) {
  685.             return $this->render('unsubscribe/error.html.twig', [
  686.                 'message' => 'Aucun compte associé à ce numéro'
  687.             ]);
  688.         }
  689.         $user->setSmsnotif(0);
  690.         $em->getRepository(\App\Entity\ParentSejour::class)->createQueryBuilder('ps')
  691.             ->update()
  692.             ->set('ps.smsnotif'':zero')
  693.             ->where('ps.idParent = :user')
  694.             ->setParameter('zero'0)
  695.             ->setParameter('user'$user)
  696.             ->getQuery()
  697.             ->execute();
  698.         $em->flush();
  699.         return $this->render('unsubscribe/success_sms.html.twig', [
  700.             'tel' => $formatted0
  701.         ]);
  702.     }
  703.     /**
  704.      * @Route("/contact", name="app_contact", methods={"POST"})
  705.      */
  706.     public function processContactForm(Request $request): Response
  707.     {
  708.         try {
  709.             // Récupérer les données du formulaire (support des anciens et nouveaux noms)
  710.             $name $request->request->get('name') ?: $request->request->get('fullname');
  711.             $organization $request->request->get('organization') ?: $request->request->get('org');
  712.             $email $request->request->get('email');
  713.             $telephone $request->request->get('telephone') ?: $request->request->get('phone');
  714.             $sejoursCount $request->request->get('sejours_count') ?: $request->request->get('sejours');
  715.             $message $request->request->get('message''');
  716.             $topic $request->request->get('topic''contact');
  717.             $objet $request->request->get('objet''contact');
  718.             $pack $request->request->get('pack''');
  719.             $consent $request->request->get('consent');
  720.             // Support ancien format avec 'subject'
  721.             $subject $request->request->get('subject');
  722.             // Validation des données
  723.             if (empty($name) || empty($email) || empty($organization) || empty($consent)) {
  724.                 return new JsonResponse([
  725.                     'success' => false,
  726.                     'message' => 'Tous les champs obligatoires doivent être remplis (nom, email, établissement, consentement).'
  727.                 ], 400);
  728.             }
  729.             if (!filter_var($emailFILTER_VALIDATE_EMAIL)) {
  730.                 return new JsonResponse([
  731.                     'success' => false,
  732.                     'message' => 'L\'adresse email n\'est pas valide.'
  733.                 ], 400);
  734.             }
  735.             // Préparer le sujet selon le type de demande
  736.             if (empty($subject)) {
  737.                 $currentDate = new \DateTime();
  738.                 if ($objet === 'expert') {
  739.                     $subject 'Demande expert_' $currentDate->format('d/m/Y');
  740.                 } elseif ($objet === 'demo' || $topic === 'demo') {
  741.                     $subject 'Demande demo_' $currentDate->format('d/m/Y');
  742.                 } else {
  743.                     $subject 'Nouveau message de contact - 5sur5séjour';
  744.                 }
  745.             }
  746.             // Créer l'email
  747.             $emailContent = (new TemplatedEmail())
  748.                 ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  749.                 ->to(new Address('ramzi.benlarbi@gmail.com''Ramzi Benlarbi'))
  750.                 ->addCc(new Address('yousra.tlich@gmail.com''Yousra Tlich'))
  751.                 ->addCc(new Address('partenariat@5sur5sejour.com''Partenariat 5sur5séjour'))
  752.                 ->subject($subject)
  753.                 ->htmlTemplate('emails/contact_form.html.twig')
  754.                 ->context([
  755.                     'name' => $name,
  756.                     'organization' => $organization,
  757.                     'email' => $email,
  758.                     'telephone' => $telephone,
  759.                     'sejours_count' => $sejoursCount,
  760.                     'subject' => $subject,
  761.                     'message' => $message,
  762.                     'topic' => $topic,
  763.                     'objet' => $objet,
  764.                     'pack' => $pack,
  765.                     'date' => new \DateTime()
  766.                 ]);
  767.             // Envoyer l'email
  768.             $this->mailer->send($emailContent);
  769.             // Logger le succès
  770.             $this->logger->info('Email de contact envoyé avec succès', [
  771.                 'from' => $email,
  772.                 'name' => $name,
  773.                 'subject' => $subject
  774.             ]);
  775.             return new JsonResponse([
  776.                 'success' => true,
  777.                 'message' => 'Votre message a été envoyé avec succès ! Nous vous répondrons dans les plus brefs délais.'
  778.             ]);
  779.         } catch (\Exception $e) {
  780.             // Logger l'erreur
  781.             $this->logger->error('Erreur lors de l\'envoi de l\'email de contact', [
  782.                 'error' => $e->getMessage(),
  783.                 'trace' => $e->getTraceAsString()
  784.             ]);
  785.             return new JsonResponse([
  786.                 'success' => false,
  787.                 'message' => 'Une erreur est survenue lors de l\'envoi du message. Veuillez réessayer plus tard.'
  788.             ], 500);
  789.         }
  790.     }
  791.     /**
  792.      * @Route("/contact/grand-volume", name="app_contact_grand_volume", methods={"POST"})
  793.      */
  794.     public function processGrandVolumeForm(Request $request\App\Service\RecaptchaService $recaptchaService): JsonResponse
  795.     {
  796.         try {
  797.             // Récupérer les données du formulaire
  798.             $nomStructure $request->request->get('nom_structure');
  799.             $typeStructure $request->request->get('type_structure');
  800.             $nombreSejours $request->request->get('nombre_sejours');
  801.             $contactEmail $request->request->get('contact_email');
  802.             $contactTelephone $request->request->get('contact_telephone');
  803.             $message $request->request->get('message''');
  804.             $acceptRgpd $request->request->get('accept_rgpd');
  805.             // ===== VALIDATION RECAPTCHA V3 =====
  806.             $recaptchaToken $request->request->get('recaptcha_token''');
  807.             $recaptchaResult $recaptchaService->verify($recaptchaToken'grand_volume'$request->getClientIp());
  808.             
  809.             if (!$recaptchaResult['success']) {
  810.                 $this->logger->warning('🤖 Échec validation reCAPTCHA sur grand volume', [
  811.                     'ip' => $request->getClientIp(),
  812.                     'error' => $recaptchaResult['error'],
  813.                     'email' => $contactEmail
  814.                 ]);
  815.                 return new JsonResponse([
  816.                     'success' => false,
  817.                     'message' => 'Validation de sécurité échouée. Veuillez réessayer.'
  818.                 ], 429);
  819.             }
  820.             // Validation des données
  821.             if (
  822.                 empty($nomStructure) || empty($typeStructure) || empty($nombreSejours) ||
  823.                 empty($contactEmail) || empty($contactTelephone) || empty($acceptRgpd)
  824.             ) {
  825.                 return new JsonResponse([
  826.                     'success' => false,
  827.                     'message' => 'Tous les champs obligatoires doivent être remplis.'
  828.                 ], 400);
  829.             }
  830.             if (!filter_var($contactEmailFILTER_VALIDATE_EMAIL)) {
  831.                 return new JsonResponse([
  832.                     'success' => false,
  833.                     'message' => 'L\'adresse email n\'est pas valide.'
  834.                 ], 400);
  835.             }
  836.             if (intval($nombreSejours) < 26) {
  837.                 return new JsonResponse([
  838.                     'success' => false,
  839.                     'message' => 'Le nombre de séjours doit être d\'au moins 26 par an.'
  840.                 ], 400);
  841.             }
  842.             // Préparer le sujet
  843.             $subject 'Demande de devis Grand Volume - ' $nomStructure ' (' $nombreSejours ' séjours/an)';
  844.             // Créer l'email pour l'équipe
  845.             try {
  846.                 $emailContent = (new TemplatedEmail())
  847.                     ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  848.                     ->to('partenariat@5sur5sejour.com')
  849.                     ->addTo('yousra.tlich@gmail.com')
  850.                     ->addTo('ramzi.benlarbi@gmail.com')
  851.                     ->subject($subject)
  852.                     ->htmlTemplate('emails/grand_volume_request.html.twig')
  853.                     ->context([
  854.                         'nomStructure' => $nomStructure,
  855.                         'typeStructure' => $typeStructure,
  856.                         'nombreSejours' => $nombreSejours,
  857.                         'contactEmail' => $contactEmail,
  858.                         'contactTelephone' => $contactTelephone,
  859.                         'message' => $message
  860.                     ]);
  861.                 $this->mailer->send($emailContent);
  862.             } catch (\Exception $emailException) {
  863.                 $this->logger->error('Erreur lors de l\'envoi de l\'email à l\'équipe', [
  864.                     'error' => $emailException->getMessage()
  865.                 ]);
  866.                 throw $emailException;
  867.             }
  868.             // Email de confirmation au client
  869.             try {
  870.                 $clientEmail = (new TemplatedEmail())
  871.                     ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  872.                     ->to($contactEmail)
  873.                     ->subject('Votre demande de devis Grand Volume a été reçue')
  874.                     ->htmlTemplate('emails/grand_volume_confirmation.html.twig')
  875.                     ->context([
  876.                         'nomStructure' => $nomStructure
  877.                     ]);
  878.                 $this->mailer->send($clientEmail);
  879.             } catch (\Exception $emailException) {
  880.                 // On log l'erreur mais on continue car l'email principal a été envoyé
  881.                 $this->logger->warning('Erreur lors de l\'envoi de l\'email de confirmation au client', [
  882.                     'error' => $emailException->getMessage()
  883.                 ]);
  884.             }
  885.             return new JsonResponse([
  886.                 'success' => true,
  887.                 'message' => 'Votre demande de devis a été envoyée avec succès ! Notre équipe vous contactera dans les plus brefs délais pour vous proposer une offre personnalisée.'
  888.             ]);
  889.         } catch (\Exception $e) {
  890.             $this->logger->error('Erreur lors de l\'envoi du formulaire grand volume', [
  891.                 'error' => $e->getMessage(),
  892.                 'file' => $e->getFile(),
  893.                 'line' => $e->getLine(),
  894.                 'trace' => $e->getTraceAsString()
  895.             ]);
  896.             // En mode dev, retourner le message d'erreur détaillé
  897.             if ($this->getParameter('kernel.environment') === 'dev') {
  898.                 return new JsonResponse([
  899.                     'success' => false,
  900.                     'message' => 'Erreur : ' $e->getMessage() . ' (ligne ' $e->getLine() . ')'
  901.                 ], 500);
  902.             }
  903.             return new JsonResponse([
  904.                 'success' => false,
  905.                 'message' => 'Une erreur est survenue lors de l\'envoi de votre demande. Veuillez réessayer plus tard.'
  906.             ], 500);
  907.         }
  908.     }
  909.     /**
  910.      * @Route("/contact/boite-vocale-devis", name="app_contact_boite_vocale_devis", methods={"POST"})
  911.      */
  912.     public function processBoiteVocaleDevisForm(Request $request): JsonResponse
  913.     {
  914.         try {
  915.             // Récupérer les données du formulaire
  916.             $nomStructure $request->request->get('nom_structure');
  917.             $typeStructure $request->request->get('type_structure');
  918.             $nombreParticipants $request->request->get('nombre_participants');
  919.             $dateDebut $request->request->get('date_debut');
  920.             $dateFin $request->request->get('date_fin');
  921.             $themeSejour $request->request->get('theme_sejour');
  922.             $contactEmail $request->request->get('contact_email');
  923.             $contactTelephone $request->request->get('contact_telephone');
  924.             $message $request->request->get('message''');
  925.             $acceptRgpd $request->request->get('accept_rgpd');
  926.             // Validation des données
  927.             if (
  928.                 empty($nomStructure) || empty($typeStructure) || empty($nombreParticipants) ||
  929.                 empty($dateDebut) || empty($dateFin) || empty($themeSejour) ||
  930.                 empty($contactEmail) || empty($contactTelephone) || empty($acceptRgpd)
  931.             ) {
  932.                 return new JsonResponse([
  933.                     'success' => false,
  934.                     'message' => 'Tous les champs obligatoires doivent être remplis.'
  935.                 ], 400);
  936.             }
  937.             if (!filter_var($contactEmailFILTER_VALIDATE_EMAIL)) {
  938.                 return new JsonResponse([
  939.                     'success' => false,
  940.                     'message' => 'L\'adresse email n\'est pas valide.'
  941.                 ], 400);
  942.             }
  943.             if (intval($nombreParticipants) < 71) {
  944.                 return new JsonResponse([
  945.                     'success' => false,
  946.                     'message' => 'Le nombre de participants doit être d\'au moins 71 pour un grand séjour.'
  947.                 ], 400);
  948.             }
  949.             // Validation des dates
  950.             $dateDebutObj = new \DateTime($dateDebut);
  951.             $dateFinObj = new \DateTime($dateFin);
  952.             if ($dateFinObj $dateDebutObj) {
  953.                 return new JsonResponse([
  954.                     'success' => false,
  955.                     'message' => 'La date de fin doit être postérieure à la date de début.'
  956.                 ], 400);
  957.             }
  958.             // Préparer le sujet
  959.             $subject 'Demande de devis Boîte Vocale - ' $nomStructure ' (' $nombreParticipants ' participants)';
  960.             // Créer l'email pour l'équipe
  961.             try {
  962.                 $emailContent = (new TemplatedEmail())
  963.                     ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  964.                     ->to('partenariat@5sur5sejour.com')
  965.                     ->addTo('yousra.tlich@gmail.com')
  966.                     ->addTo('ramzi.benlarbi@gmail.com')
  967.                     ->subject($subject)
  968.                     ->htmlTemplate('emails/boite_vocale/devis_request.html.twig')
  969.                     ->context([
  970.                         'nomStructure' => $nomStructure,
  971.                         'typeStructure' => $typeStructure,
  972.                         'nombreParticipants' => $nombreParticipants,
  973.                         'dateDebut' => $dateDebut,
  974.                         'dateFin' => $dateFin,
  975.                         'themeSejour' => $themeSejour,
  976.                         'contactEmail' => $contactEmail,
  977.                         'contactTelephone' => $contactTelephone,
  978.                         'message' => $message
  979.                     ]);
  980.                 $this->mailer->send($emailContent);
  981.             } catch (\Exception $emailException) {
  982.                 $this->logger->error('Erreur lors de l\'envoi de l\'email à l\'équipe (boîte vocale)', [
  983.                     'error' => $emailException->getMessage()
  984.                 ]);
  985.                 throw $emailException;
  986.             }
  987.             // Email de confirmation au client
  988.             try {
  989.                 $clientEmail = (new TemplatedEmail())
  990.                     ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  991.                     ->to($contactEmail)
  992.                     ->subject('Votre demande de devis Boîte Vocale a été reçue')
  993.                     ->htmlTemplate('emails/boite_vocale/devis_confirmation.html.twig')
  994.                     ->context([
  995.                         'nomStructure' => $nomStructure
  996.                     ]);
  997.                 $this->mailer->send($clientEmail);
  998.             } catch (\Exception $emailException) {
  999.                 // On log l'erreur mais on continue car l'email principal a été envoyé
  1000.                 $this->logger->warning('Erreur lors de l\'envoi de l\'email de confirmation au client (boîte vocale)', [
  1001.                     'error' => $emailException->getMessage()
  1002.                 ]);
  1003.             }
  1004.             return new JsonResponse([
  1005.                 'success' => true,
  1006.                 'message' => 'Votre demande de devis a été envoyée avec succès ! Notre équipe vous contactera dans les plus brefs délais pour vous proposer une offre personnalisée.'
  1007.             ]);
  1008.         } catch (\Exception $e) {
  1009.             $this->logger->error('Erreur lors de l\'envoi du formulaire devis boîte vocale', [
  1010.                 'error' => $e->getMessage(),
  1011.                 'file' => $e->getFile(),
  1012.                 'line' => $e->getLine(),
  1013.                 'trace' => $e->getTraceAsString()
  1014.             ]);
  1015.             // En mode dev, retourner le message d'erreur détaillé
  1016.             if ($this->getParameter('kernel.environment') === 'dev') {
  1017.                 return new JsonResponse([
  1018.                     'success' => false,
  1019.                     'message' => 'Erreur : ' $e->getMessage() . ' (ligne ' $e->getLine() . ')'
  1020.                 ], 500);
  1021.             }
  1022.             return new JsonResponse([
  1023.                 'success' => false,
  1024.                 'message' => 'Une erreur est survenue lors de l\'envoi de votre demande. Veuillez réessayer plus tard.'
  1025.             ], 500);
  1026.         }
  1027.     }
  1028.     /**
  1029.      * @Route("/contact/demo", name="app_contact_demo", methods={"POST"})
  1030.      */
  1031.     public function processDemoForm(Request $request): Response
  1032.     {
  1033.         try {
  1034.             // Récupérer les données du formulaire de démo
  1035.             $fullname $request->request->get('fullname');
  1036.             $org $request->request->get('org');
  1037.             $email $request->request->get('email');
  1038.             $phone $request->request->get('phone');
  1039.             $message $request->request->get('message');
  1040.             $topic $request->request->get('topic');
  1041.             $pack $request->request->get('pack');
  1042.             $consent $request->request->get('consent');
  1043.             // Validation des données
  1044.             if (empty($fullname) || empty($org) || empty($email) || empty($consent)) {
  1045.                 return new JsonResponse([
  1046.                     'success' => false,
  1047.                     'message' => 'Tous les champs obligatoires doivent être remplis et le consentement accepté.'
  1048.                 ], 400);
  1049.             }
  1050.             if (!filter_var($emailFILTER_VALIDATE_EMAIL)) {
  1051.                 return new JsonResponse([
  1052.                     'success' => false,
  1053.                     'message' => 'L\'adresse email n\'est pas valide.'
  1054.                 ], 400);
  1055.             }
  1056.             // Préparer le sujet avec la date
  1057.             $currentDate = new \DateTime();
  1058.             $subject 'Demande demo_' $currentDate->format('d/m/Y');
  1059.             // Créer l'email pour la demande de démo
  1060.             $emailContent = (new TemplatedEmail())
  1061.                 ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  1062.                 ->to(new Address('ramzi.benlarbi@gmail.com''Ramzi Benlarbi'))
  1063.                 ->addCc(new Address('yousra.tlich@gmail.com''Yousra Tlich'))
  1064.                 ->addCc(new Address('partenariat@5sur5sejour.com''Partenariat 5sur5séjour'))
  1065.                 ->subject($subject)
  1066.                 ->htmlTemplate('emails/demo_request.html.twig')
  1067.                 ->context([
  1068.                     'fullname' => $fullname,
  1069.                     'org' => $org,
  1070.                     'email' => $email,
  1071.                     'phone' => $phone,
  1072.                     'message' => $message,
  1073.                     'topic' => $topic,
  1074.                     'pack' => $pack,
  1075.                     'date' => $currentDate
  1076.                 ]);
  1077.             // Envoyer l'email
  1078.             $this->mailer->send($emailContent);
  1079.             // Logger le succès
  1080.             $this->logger->info('Email de demande de démo envoyé avec succès', [
  1081.                 'from' => $email,
  1082.                 'fullname' => $fullname,
  1083.                 'org' => $org,
  1084.                 'topic' => $topic
  1085.             ]);
  1086.             return new JsonResponse([
  1087.                 'success' => true,
  1088.                 'message' => 'Votre demande de démo a été envoyée avec succès ! Nous vous recontacterons dans les 24 heures.'
  1089.             ]);
  1090.         } catch (\Exception $e) {
  1091.             // Logger l'erreur
  1092.             $this->logger->error('Erreur lors de l\'envoi de l\'email de demande de démo', [
  1093.                 'error' => $e->getMessage(),
  1094.                 'trace' => $e->getTraceAsString()
  1095.             ]);
  1096.             return new JsonResponse([
  1097.                 'success' => false,
  1098.                 'message' => 'Une erreur est survenue lors de l\'envoi de la demande. Veuillez réessayer plus tard.'
  1099.             ], 500);
  1100.         }
  1101.     }
  1102.     /**
  1103.      * @Route("/Accueil5sur5/Pack_Access", name="pack_access")
  1104.      * @Route("/Accueil5sur5/Pack_Decouverte", name="pack_decouverte")
  1105.      */
  1106.     public function packAccess()
  1107.     {
  1108.         return $this->render('Accueil/pack_access.html.twig');
  1109.     }
  1110.     /**
  1111.      * @Route("/Accueil5sur5/Pack_Annuel_Partenaires", name="pack_annuel_partenaires")
  1112.      * 
  1113.      */
  1114.     public function packAnnuelPartenaires()
  1115.     {
  1116.         return $this->render('Accueil/pack_annuel_partenaires.html.twig');
  1117.     }
  1118.     /**
  1119.      * @Route("/Accueil5sur5/Boite_Vocale", name="boite_vocale")
  1120.      */
  1121.     public function boiteVocale()
  1122.     {
  1123.         return $this->render('Accueil/boite_vocale.html.twig');
  1124.     }
  1125.     /**
  1126.      * @Route("/Accueil5sur5/Commande_Groupee", name="commande_groupee_accueil")
  1127.      */
  1128.     public function commandeGroupeeAccueil()
  1129.     {
  1130.         return $this->render('Accueil/CommandeGroupee.html.twig');
  1131.     }
  1132.     /**
  1133.      * @Route("/checkout/boite-vocale/{pack}", name="checkout_boite_vocale", defaults={"pack"="35"})
  1134.      */
  1135.     public function checkoutBoiteVocale(string $pack)
  1136.     {
  1137.         // Configuration des packs
  1138.         $packConfigs = [
  1139.             '35' => ['name' => 'Pack 35 enfants''price' => 40'max' => 35],
  1140.             '70' => ['name' => 'Pack 70 enfants''price' => 70'max' => 70],
  1141.             'plus' => ['name' => 'Grand séjour''price' => 0'max' => null]
  1142.         ];
  1143.         $packConfig $packConfigs[$pack] ?? $packConfigs['35'];
  1144.         return $this->render('Accueil/checkout_boite_vocale.html.twig', [
  1145.             'pack_type' => $pack,
  1146.             'pack_config' => $packConfig
  1147.         ]);
  1148.     }
  1149.     /**
  1150.      * @Route("/checkout/process-boite-vocale", name="checkout_process_boite_vocale", methods={"POST"})
  1151.      */
  1152.     public function processCheckoutBoiteVocale(Request $requestEntityManagerInterface $em\App\Service\RecaptchaService $recaptchaService)
  1153.     {
  1154.         try {
  1155.             // Récupérer les données du formulaire
  1156.             $data = [
  1157.                 'company_name' => $request->request->get('company_name'),
  1158.                 'company_type' => $request->request->get('company_type'),
  1159.                 'phone' => $request->request->get('phone'),
  1160.                 'address' => $request->request->get('address'),
  1161.                 'postal_code' => $request->request->get('postal_code'),
  1162.                 'city' => $request->request->get('city'),
  1163.                 'first_name' => $request->request->get('first_name'),
  1164.                 'last_name' => $request->request->get('last_name'),
  1165.                 'email' => $request->request->get('email'),
  1166.                 'role' => $request->request->get('role'),
  1167.                 'date_debut' => $request->request->get('date_debut'),
  1168.                 'date_fin' => $request->request->get('date_fin'),
  1169.                 'nombre_participants' => $request->request->get('nombre_participants'),
  1170.                 'theme_sejour' => $request->request->get('theme_sejour'),
  1171.                 'adresse_sejour' => $request->request->get('adresse_sejour'),
  1172.                 'pack_type' => $request->request->get('pack_type''35'),
  1173.                 'payment_method' => $request->request->get('payment_method''online')
  1174.             ];
  1175.             // ===== VALIDATION RECAPTCHA V3 =====
  1176.             $recaptchaToken $request->request->get('recaptcha_token''');
  1177.             $recaptchaResult $recaptchaService->verify($recaptchaToken'checkout_boite_vocale'$request->getClientIp());
  1178.             
  1179.             if (!$recaptchaResult['success']) {
  1180.                 $this->logger->warning('🤖 Échec validation reCAPTCHA sur checkout boite vocale', [
  1181.                     'ip' => $request->getClientIp(),
  1182.                     'error' => $recaptchaResult['error'],
  1183.                     'email' => $data['email']
  1184.                 ]);
  1185.                 return new JsonResponse([
  1186.                     'success' => false,
  1187.                     'error' => 'Validation de sécurité échouée. Veuillez réessayer.'
  1188.                 ], 429);
  1189.             }
  1190.             // Validation des champs obligatoires
  1191.             $requiredFields = ['company_name''phone''address''postal_code''city''first_name''last_name''email''date_debut''date_fin''nombre_participants''theme_sejour''adresse_sejour'];
  1192.             foreach ($requiredFields as $field) {
  1193.                 if (empty($data[$field])) {
  1194.                     return new JsonResponse([
  1195.                         'success' => false,
  1196.                         'error' => "Le champ {$field} est obligatoire"
  1197.                     ], 400);
  1198.                 }
  1199.             }
  1200.             // Déterminer les prix selon le pack
  1201.             $packPrices = [
  1202.                 '35' => ['ht' => 39'ttc' => 46.80],
  1203.                 '70' => ['ht' => 70'ttc' => 84],
  1204.                 'plus' => ['ht' => 0'ttc' => 0// Sur devis
  1205.             ];
  1206.             $prices $packPrices[$data['pack_type']] ?? $packPrices['35'];
  1207.             // Créer ou récupérer l'utilisateur
  1208.             $userRepository $em->getRepository(User::class);
  1209.             $user $userRepository->findOneBy(['email' => $data['email']]);
  1210.             if (!$user) {
  1211.                 $user = new User();
  1212.                 $user->setEmail($data['email']);
  1213.                 $user->setNom($data['last_name']);
  1214.                 $user->setPrenom($data['first_name']);
  1215.                 $user->setNummobile($data['phone']);
  1216.                 $user->setNometablisment($data['company_name']);
  1217.                 $mdpTemp substr(md5(uniqid()), 08);
  1218.                 $user->setPassword(password_hash($mdpTempPASSWORD_BCRYPT));
  1219.                 $user->setRoles(json_encode(['ROLE_PARTENAIRE']));
  1220.                 // Fetch Ref entity for status
  1221.                 $statutActif $em->getRepository(Ref::class)->find(1);
  1222.                 if (!$statutActif) {
  1223.                     $statutActif = new Ref();
  1224.                     $statutActif->setLibiller('Actif');
  1225.                     $em->persist($statutActif);
  1226.                     $em->flush();
  1227.                 }
  1228.                 $user->setStatut($statutActif);
  1229.                 $em->persist($user);
  1230.                 $em->flush();
  1231.             }
  1232.             // Récupérer le statut "en attente"
  1233.             $statutEnAttente $em->getRepository(Ref::class)->find(1);
  1234.             if (!$statutEnAttente) {
  1235.                 $statutEnAttente = new Ref();
  1236.                 $statutEnAttente->setLibiller('attente_paiement');
  1237.                 $em->persist($statutEnAttente);
  1238.                 $em->flush();
  1239.             }
  1240.             // Créer la commande
  1241.             $commande = new Commande();
  1242.             $commande->setIdUser($user);
  1243.             $commande->setStatut($statutEnAttente);
  1244.             $commande->setMontantht($prices['ht']);
  1245.             $commande->setMontantrth($prices['ttc']);
  1246.             $commande->setMontanenv($prices['ttc']);
  1247.             $commande->setTva(20);
  1248.             $commande->setDateCreateCommande(new \DateTime());
  1249.             $commande->setPeriode('boite_vocale'); // Identifier comme commande boîte vocale
  1250.             $commande->setNumComande(time());
  1251.             // Normaliser la méthode de paiement
  1252.             $methodNormalized strtolower($data['payment_method']);
  1253.             $aliases = [
  1254.                 'online' => 'stripe',
  1255.                 'cb' => 'stripe',
  1256.                 'payplug' => 'stripe',
  1257.                 'card' => 'stripe',
  1258.                 'bank' => 'rib',
  1259.                 'transfer' => 'rib',
  1260.                 'virement' => 'rib',
  1261.                 'bank_transfer' => 'rib'
  1262.             ];
  1263.             if (isset($aliases[$methodNormalized])) {
  1264.                 $methodNormalized $aliases[$methodNormalized];
  1265.             }
  1266.             $commande->setPaymentType($methodNormalized);
  1267.             // Stocker les infos du séjour dans un champ JSON ou commentaire (à adapter selon votre structure)
  1268.             // Pour l'instant, on stocke dans le numéro de commande comme référence
  1269.             $commande->setNumComande((int)time());
  1270.             $em->persist($commande);
  1271.             $em->flush();
  1272.             // Stocker les données en session pour la page de confirmation
  1273.             $session $request->getSession();
  1274.             $session->set('checkout_data', [
  1275.                 'commande_id' => $commande->getId(),
  1276.                 'type' => 'boite_vocale',
  1277.                 'pack_type' => $data['pack_type'],
  1278.                 'montant_ht' => $prices['ht'],
  1279.                 'montant_ttc' => $prices['ttc'],
  1280.                 'payment_method' => $methodNormalized,
  1281.                 'user' => $user,
  1282.                 'data' => $data
  1283.             ]);
  1284.             // Traiter selon la méthode de paiement
  1285.             if ($methodNormalized === 'stripe') {
  1286.                 // Return command ID for Up2Pay form submission
  1287.                 return new JsonResponse([
  1288.                     'success' => true,
  1289.                     'paymentType' => 'up2pay',
  1290.                     'message' => 'Redirection vers le paiement sécurisé Up2Pay...',
  1291.                     'commandeId' => $commande->getId()
  1292.                 ]);
  1293.             } else {
  1294.                 // RIB - Envoyer l'email avec les coordonnées bancaires
  1295.                 try {
  1296.                     $email = (new TemplatedEmail())
  1297.                         ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  1298.                         ->to($user->getEmail())
  1299.                         ->cc('yousra.tlich@gmail.com''ramzi.benlarbi@gmail.com')
  1300.                         ->subject('Virement bancaire - Boîte Vocale - Commande N°' $commande->getId())
  1301.                         ->htmlTemplate('emails/boite_vocale/rib_commande.html.twig')
  1302.                         ->context([
  1303.                             'user' => $user,
  1304.                             'commande' => $commande,
  1305.                             'data' => $data,
  1306.                             'pack_type' => $data['pack_type'],
  1307.                             'pack_label' => $data['pack_type'] === '35' 'Pack 35 enfants' : ($data['pack_type'] === '70' 'Pack 70 enfants' 'Grand séjour'),
  1308.                             'montant_ht' => $prices['ht'],
  1309.                             'montant_ttc' => $prices['ttc']
  1310.                         ]);
  1311.                     $this->mailer->send($email);
  1312.                 } catch (\Exception $e) {
  1313.                     error_log("⚠️ Erreur envoi email RIB: " $e->getMessage());
  1314.                 }
  1315.                 // Retourner succès avec message pour popup
  1316.                 return new JsonResponse([
  1317.                     'success' => true,
  1318.                     'paymentType' => 'rib',
  1319.                     'message' => 'Votre demande a bien été envoyée, nous reviendrons vers vous dans les plus brefs délais.',
  1320.                     'redirectUrl' => $this->generateUrl('checkout_success', ['method' => 'rib'])
  1321.                 ]);
  1322.             }
  1323.         } catch (\Exception $e) {
  1324.             error_log("❌ Erreur checkout boîte vocale: " $e->getMessage());
  1325.             error_log($e->getTraceAsString());
  1326.             return new JsonResponse([
  1327.                 'success' => false,
  1328.                 'error' => 'Une erreur est survenue lors du traitement de votre commande. Veuillez réessayer.'
  1329.             ], 500);
  1330.         }
  1331.     }
  1332.     /**
  1333.      * @Route("/checkout/pack/{pack}", name="checkout_pack", defaults={"pack"="serenite"})
  1334.      */
  1335.     public function checkoutPack(string $pack)
  1336.     {
  1337.         // Configuration des packs
  1338.         $packsConfig = [
  1339.             'decouverte' => [
  1340.                 'id' => 'decouverte',
  1341.                 'name' => 'Pack Découverte',
  1342.                 'subtitle' => 'Abonnement annuel',
  1343.                 'price' => 290,
  1344.                 'price_ht' => '290 € HT',
  1345.                 'onboarding' => 'Inclus',
  1346.                 'vocal_price' => '+5 € / séjour',
  1347.                 'features' => [
  1348.                     'Séjours illimités toute l\'année',
  1349.                     'Multi-sites pour une équipe',
  1350.                     'Reporting mensuel & facturation',
  1351.                     'Support prioritaire individuel'
  1352.                 ],
  1353.                 'info' => 'Plusieurs séjours par an ? Tarif dégressif automatique !'
  1354.             ],
  1355.             'serenite' => [
  1356.                 'id' => 'serenite',
  1357.                 'name' => 'Pack Annuel – 25 séjours',
  1358.                 'subtitle' => 'Le plus utilisé',
  1359.                 'description' => 'Le pack idéal pour les structures qui organisent plusieurs séjours par an. Jusqu\'à 25 séjours inclus dans votre abonnement annuel.',
  1360.                 'price' => 490,
  1361.                 'price_ht' => '490 € HT',
  1362.                 'price_suffix' => '/ an / structure',
  1363.                 'onboarding' => 'Déploiement par l\'équipe 5sur5',
  1364.                 'vocal_price' => ''// Retiré - plus de voix téléphonique
  1365.                 'benefit' => '+90% de connexions parents',
  1366.                 'benefit_subtitle' => 'Pas d\'action de votre part, on s\'occupe de tout mettre en place',
  1367.                 'features' => [
  1368.                     'Support prioritaire',
  1369.                     'Jusqu\'à 25 séjours inclus',
  1370.                     'Déploiement par l\'équipe 5sur5',
  1371.                     'Accompagnateurs illimités',
  1372.                     'Support premium'
  1373.                 ],
  1374.                 'info' => 'Budget annuel fixe, 0 dépassement. Un prix unique qui couvre tous vos séjours et tous les parents connectés. Aucun dépassement, données sécurisées RGPD en France.',
  1375.                 'additional_info' => 'Que vous organisiez 2 ou 20 séjours par an, tout est couvert sans frais supplémentaires. Notre plateforme gère automatiquement les familles, les albums souvenirs et la communication en temps réel.'
  1376.             ],
  1377.             'pro_illimite' => [
  1378.                 'id' => 'pro_illimite',
  1379.                 'name' => 'Pack Pro Illimité',
  1380.                 'subtitle' => 'Abonnement annuel',
  1381.                 'price' => 790,
  1382.                 'price_ht' => '790 € HT',
  1383.                 'onboarding' => 'Inclus + Formation avancée',
  1384.                 'vocal_price' => 'Inclus',
  1385.                 'features' => [
  1386.                     'Séjours illimités toute l\'année',
  1387.                     'Multi-sites pour une équipe',
  1388.                     'Reporting mensuel & facturation',
  1389.                     'Support prioritaire individuel',
  1390.                     'Voix téléphonique illimitée',
  1391.                     'Formation avancée de l\'équipe'
  1392.                 ],
  1393.                 'info' => 'Le meilleur rapport qualité/prix pour les structures avec plusieurs séjours !'
  1394.             ]
  1395.         ];
  1396.         // Vérifier que le pack existe
  1397.         if (!isset($packsConfig[$pack])) {
  1398.             throw $this->createNotFoundException('Pack non trouvé');
  1399.         }
  1400.         return $this->render('Accueil/checkout_pack.html.twig', [
  1401.             'pack' => $pack,
  1402.             'packConfig' => $packsConfig[$pack]
  1403.         ]);
  1404.     }
  1405.     /**
  1406.      * @Route("/checkout/process", name="checkout_process_pack", methods={"POST"})
  1407.      */
  1408.     public function checkoutProcessPack(Request $requestMailerInterface $mailer\App\Service\RecaptchaService $recaptchaService)
  1409.     {
  1410.         try {
  1411.             error_log('🔍 CHECKOUT START - Processing form submission');
  1412.             // Récupération des données du formulaire
  1413.             $data = [
  1414.                 'pack_id' => $request->request->get('pack_id'),
  1415.                 'payment_method' => $request->request->get('payment_method'),
  1416.                 'company_name' => $request->request->get('company_name'),
  1417.                 'company_type' => $request->request->get('company_type'),
  1418.                 'siren' => $request->request->get('siren'),
  1419.                 'phone' => $request->request->get('phone'),
  1420.                 'address' => $request->request->get('address'),
  1421.                 'postal_code' => $request->request->get('postal_code'),
  1422.                 'city' => $request->request->get('city'),
  1423.                 'first_name' => $request->request->get('first_name'),
  1424.                 'last_name' => $request->request->get('last_name'),
  1425.                 'email' => $request->request->get('email'),
  1426.                 'role' => $request->request->get('role'),
  1427.                 'need_onboarding' => $request->request->get('need_onboarding') ? true false,
  1428.             ];
  1429.             error_log('📋 Data received: ' json_encode($data));
  1430.             // ===== VALIDATION RECAPTCHA V3 =====
  1431.             $recaptchaToken $request->request->get('recaptcha_token''');
  1432.             $recaptchaResult $recaptchaService->verify($recaptchaToken'checkout_pack'$request->getClientIp());
  1433.             
  1434.             if (!$recaptchaResult['success']) {
  1435.                 $this->logger->warning('🤖 Échec validation reCAPTCHA sur checkout pack', [
  1436.                     'ip' => $request->getClientIp(),
  1437.                     'error' => $recaptchaResult['error'],
  1438.                     'email' => $data['email']
  1439.                 ]);
  1440.                 $this->addFlash('error''Validation de sécurité échouée. Veuillez réessayer.');
  1441.                 return $this->redirectToRoute('checkout_pack', ['pack' => $data['pack_id'] ?? 'serenite']);
  1442.             }
  1443.             // Validation
  1444.             $requiredFields = [
  1445.                 'pack_id',
  1446.                 'payment_method',
  1447.                 'company_name',
  1448.                 'company_type',
  1449.                 'phone',
  1450.                 'address',
  1451.                 'postal_code',
  1452.                 'city',
  1453.                 'first_name',
  1454.                 'last_name',
  1455.                 'email',
  1456.                 'role'
  1457.             ];
  1458.             foreach ($requiredFields as $field) {
  1459.                 if (empty($data[$field])) {
  1460.                     error_log('❌ Missing required field: ' $field);
  1461.                     $this->addFlash('error''Tous les champs obligatoires doivent être remplis.');
  1462.                     return $this->redirectToRoute('checkout_pack', ['pack' => $data['pack_id'] ?? 'serenite']);
  1463.                 }
  1464.             }
  1465.             error_log('✅ Validation passed');
  1466.             // ==================== CRÉER LA COMMANDE EN BASE DE DONNÉES ====================
  1467.             $em $this->getDoctrine()->getManager();
  1468.             $commande null;
  1469.             // Créer la commande pour TOUS les modes de paiement (online, transfer, quote)
  1470.             try {
  1471.                 error_log('📦 Création commande pack pour: ' $data['pack_id'] . ' (method: ' $data['payment_method'] . ')');
  1472.                 // Créer ou récupérer l'utilisateur
  1473.                 $userRepository $em->getRepository(User::class);
  1474.                 $user $userRepository->findOneBy(['email' => $data['email']]);
  1475.                 if (!$user) {
  1476.                     $user = new User();
  1477.                     $user->setEmail($data['email']);
  1478.                     $user->setNom($data['last_name']);
  1479.                     $user->setPrenom($data['first_name']);
  1480.                     $user->setNummobile($data['phone'] ?? '');
  1481.                     $user->setNometablisment($data['company_name'] ?? '');
  1482.                     // Combine address fields into single address field
  1483.                     $fullAddress trim(
  1484.                         ($data['address'] ?? '') . ' ' .
  1485.                         ($data['postal_code'] ?? '') . ' ' .
  1486.                         ($data['city'] ?? '')
  1487.                     );
  1488.                     $user->setAdresse($fullAddress);
  1489.                     $mdpTemp substr(md5(uniqid()), 08);
  1490.                     $user->setPassword(password_hash($mdpTempPASSWORD_BCRYPT));
  1491.                     $user->setRoles(json_encode(['ROLE_PARTENAIRE']));
  1492.                     // Fetch Ref entity for status
  1493.                     $statutActif $em->getRepository(Ref::class)->find(1);
  1494.                     if (!$statutActif) {
  1495.                         $statutActif = new Ref();
  1496.                         $statutActif->setLibiller('Actif');
  1497.                         $em->persist($statutActif);
  1498.                         $em->flush();
  1499.                     }
  1500.                     $user->setStatut($statutActif);
  1501.                     $em->persist($user);
  1502.                     $em->flush();
  1503.                     error_log('✅ Utilisateur créé: ' $user->getEmail());
  1504.                 } else {
  1505.                     error_log('✅ Utilisateur existant: ' $user->getEmail());
  1506.                 }
  1507.                 // Déterminer les montants
  1508.                 $packPrices = [
  1509.                     'serenite' => ['ht' => 490'ttc' => 588],
  1510.                     'decouverte' => ['ht' => 290'ttc' => 348],
  1511.                 ];
  1512.                 $prices $packPrices[$data['pack_id']] ?? $packPrices['serenite'];
  1513.                 // Récupérer le statut "en attente"
  1514.                 $statutEnAttente $em->getRepository(Ref::class)->find(1);
  1515.                 if (!$statutEnAttente) {
  1516.                     $statutEnAttente = new Ref();
  1517.                     $statutEnAttente->setLibiller('En attente');
  1518.                     $em->persist($statutEnAttente);
  1519.                     $em->flush();
  1520.                 }
  1521.                 // Créer la commande
  1522.                 $commande = new Commande();
  1523.                 $commande->setIdUser($user);
  1524.                 $commande->setStatut($statutEnAttente);
  1525.                 $commande->setMontantht($prices['ht']);
  1526.                 $commande->setMontantrth($prices['ttc']);
  1527.                 $commande->setMontanenv($prices['ttc']);
  1528.                 $commande->setTva(20);
  1529.                 $commande->setDateCreateCommande(new \DateTime());
  1530.                 $commande->setDateExpidition((new \DateTime())->modify('+12 months'));
  1531.                 $commande->setPeriode('pack_' $data['pack_id']);
  1532.                 $commande->setPaymentType($data['payment_method']);
  1533.                 $commande->setNumComande(time() . rand(100999));
  1534.                 $em->persist($commande);
  1535.                 $em->flush();
  1536.                 error_log('✅ Commande créée: ID=' $commande->getId() . ' N°' $commande->getNumComande());
  1537.             } catch (\Exception $e) {
  1538.                 error_log('❌ Erreur création commande: ' $e->getMessage());
  1539.                 return new JsonResponse([
  1540.                     'success' => false,
  1541.                     'error' => 'Erreur lors de la création de la commande: ' $e->getMessage()
  1542.                 ], 500);
  1543.             }
  1544.             // Stocker en session AVANT d'envoyer les emails
  1545.             $data['commande_id'] = $commande $commande->getId() : null;
  1546.             $data['commande_numero'] = $commande $commande->getNumComande() : null;
  1547.             $request->getSession()->set('checkout_data'$data);
  1548.             error_log('💾 Data stored in session');
  1549.             // Envoyer les emails de manière asynchrone (ne pas bloquer la requête)
  1550.             try {
  1551.                 error_log('📧 Attempting to send email to team...');
  1552.                 // Envoyer email à l'équipe 5sur5
  1553.                 $emailToTeam = (new TemplatedEmail())
  1554.                     ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  1555.                     ->to('contact@5sur5sejour.com')
  1556.                     ->cc('yousra.tlich@gmail.com''ramzi.benlarbi@gmail.com')
  1557.                     ->subject('Nouvelle souscription Pack ' strtoupper($data['pack_id']) . ' - ' $data['company_name'])
  1558.                     ->htmlTemplate('emails/checkout_notification_team.html.twig')
  1559.                     ->context([
  1560.                         'data' => $data,
  1561.                         'date' => new \DateTime()
  1562.                     ]);
  1563.                 $mailer->send($emailToTeam);
  1564.                 error_log('✅ Email to team sent');
  1565.                 // Envoyer email de confirmation au client
  1566.                 $emailToClient = (new TemplatedEmail())
  1567.                     ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  1568.                     ->to($data['email'])
  1569.                     ->subject('Confirmation de votre souscription - 5sur5 Séjour')
  1570.                     ->htmlTemplate('emails/checkout_confirmation_client.html.twig')
  1571.                     ->context([
  1572.                         'data' => $data,
  1573.                         'date' => new \DateTime()
  1574.                     ]);
  1575.                 $mailer->send($emailToClient);
  1576.                 error_log('✅ Email to client sent');
  1577.             } catch (\Exception $emailError) {
  1578.                 // Log l'erreur mais ne bloque pas le processus
  1579.                 error_log('⚠️ Email error (non-blocking): ' $emailError->getMessage());
  1580.             }
  1581.             // Stocker la commande en session pour PayPlug
  1582.             $request->getSession()->set('commandeActual'$commande->getId());
  1583.             error_log('💾 Commande stockée en session: ' $commande->getId());
  1584.             // Redirection selon le mode de paiement
  1585.             error_log('🔄 Redirecting based on method: ' $data['payment_method']);
  1586.             switch ($data['payment_method']) {
  1587.                 case 'online':
  1588.                     // Return command ID for Up2Pay form submission
  1589.                     error_log('💳 Préparation paiement Up2Pay pour commande #' $commande->getId());
  1590.                     // Return JSON response for AJAX call
  1591.                     return new JsonResponse([
  1592.                         'success' => true,
  1593.                         'paymentType' => 'up2pay',
  1594.                         'message' => 'Redirection vers le paiement sécurisé Up2Pay...',
  1595.                         'commandeId' => $commande->getId()
  1596.                     ]);
  1597.                 case 'transfer':
  1598.                     $this->addFlash('info''Demande de virement enregistrée. Vous recevrez les coordonnées bancaires par email.');
  1599.                     return $this->redirectToRoute('checkout_success', ['method' => 'transfer']);
  1600.                 case 'quote':
  1601.                     $this->addFlash('info''Demande de devis enregistrée. Vous recevrez le devis par email.');
  1602.                     return $this->redirectToRoute('checkout_success', ['method' => 'quote']);
  1603.                 default:
  1604.                     error_log('❌ Invalid payment method: ' $data['payment_method']);
  1605.                     throw new \Exception('Mode de paiement invalide');
  1606.             }
  1607.         } catch (\Exception $e) {
  1608.             error_log('❌ CHECKOUT ERROR: ' $e->getMessage());
  1609.             error_log('Stack trace: ' $e->getTraceAsString());
  1610.             $this->addFlash('error''Une erreur est survenue. Veuillez réessayer. Détail: ' $e->getMessage());
  1611.             return $this->redirectToRoute('checkout_pack', ['pack' => $request->request->get('pack_id''serenite')]);
  1612.         }
  1613.     }
  1614.     /**
  1615.      * @Route("/checkout/success/{method}", name="checkout_success")
  1616.      */
  1617.     public function checkoutSuccess(Request $requeststring $method)
  1618.     {
  1619.         $data $request->getSession()->get('checkout_data');
  1620.         if (!$data) {
  1621.             return $this->redirectToRoute('page_Accueil');
  1622.         }
  1623.         return $this->render('Accueil/checkout_success.html.twig', [
  1624.             'data' => $data,
  1625.             'method' => $method
  1626.         ]);
  1627.     }
  1628.     /**
  1629.      * @Route("/Accueil5sur5/Pack_Pro_Illimite", name="pack_pro_illimite")
  1630.      */
  1631.     public function packProIllimite()
  1632.     {
  1633.         return $this->render('Accueil/pack_pro_illimite.html.twig');
  1634.     }
  1635.     /**
  1636.      * @Route("/bank-transfer-request", name="bank_transfer_request", methods={"POST"})
  1637.      */
  1638.     public function bankTransferRequest(Request $requestEntityManagerInterface $em\App\Service\RecaptchaService $recaptchaService): JsonResponse
  1639.     {
  1640.         try {
  1641.             $this->logger->info('🔍 Début de la demande de RIB');
  1642.             
  1643.             // ===== VALIDATION RECAPTCHA V3 =====
  1644.             $recaptchaToken $request->request->get('recaptcha_token''');
  1645.             $recaptchaResult $recaptchaService->verify($recaptchaToken'rib_request'$request->getClientIp());
  1646.             
  1647.             if (!$recaptchaResult['success']) {
  1648.                 $this->logger->warning('🤖 Échec validation reCAPTCHA sur demande RIB', [
  1649.                     'ip' => $request->getClientIp(),
  1650.                     'error' => $recaptchaResult['error'],
  1651.                     'score' => $recaptchaResult['score'] ?? null
  1652.                 ]);
  1653.                 
  1654.                 return new JsonResponse([
  1655.                     'success' => false,
  1656.                     'message' => 'Validation de sécurité échouée. Veuillez réessayer.'
  1657.                 ], 429);
  1658.             }
  1659.             // Accepter les deux formats de paramètres
  1660.             $fullname $request->request->get('full_name') ?: $request->request->get('rib_fullname');
  1661.             $emailUser $request->request->get('billing_email') ?: $request->request->get('rib_email');
  1662.             $phone $request->request->get('phone') ?: $request->request->get('rib_phone');
  1663.             $org $request->request->get('company') ?: $request->request->get('rib_org');
  1664.             $message $request->request->get('message') ?: $request->request->get('rib_message');
  1665.             // Paramètres spécifiques au pack
  1666.             $packId $request->request->get('pack_id');
  1667.             $participants $request->request->get('participants');
  1668.             $totalAmount $request->request->get('total_amount');
  1669.             $startDate $request->request->get('start_date');
  1670.             $endDate $request->request->get('end_date');
  1671.             $theme $request->request->get('theme');
  1672.             $accompagnateur $request->request->get('accompagnateur');
  1673.             $this->logger->info('📝 Données reçues', [
  1674.                 'fullname' => $fullname,
  1675.                 'email' => $emailUser,
  1676.                 'pack_id' => $packId,
  1677.                 'participants' => $participants,
  1678.                 'total_amount' => $totalAmount,
  1679.                 'theme' => $theme,
  1680.                 'start_date' => $startDate,
  1681.                 'end_date' => $endDate
  1682.             ]);
  1683.             // Validation basique
  1684.             if (empty($fullname) || empty($emailUser)) {
  1685.                 return new JsonResponse([
  1686.                     'success' => false,
  1687.                     'message' => 'Le nom et l\'email sont obligatoires.'
  1688.                 ], 400);
  1689.             }
  1690.             // Générer un numéro de demande unique
  1691.             $requestNumber 'RIB-' date('Ymd') . '-' strtoupper(substr(md5(uniqid()), 06));
  1692.             $this->logger->info('🔢 Numéro de demande généré: ' $requestNumber);
  1693.             // Créer ou récupérer l'utilisateur accompagnateur
  1694.             try {
  1695.                 $this->logger->info('👤 Recherche utilisateur: ' $emailUser);
  1696.                 $userRepository $em->getRepository(User::class);
  1697.                 $user $userRepository->findOneBy(['email' => $emailUser]);
  1698.                 if (!$user) {
  1699.                     $this->logger->info('➕ Création nouvel utilisateur');
  1700.                     $user = new User();
  1701.                     $user->setEmail($emailUser);
  1702.                     $user->setNom($fullname);
  1703.                     $user->setPrenom('');
  1704.                     $user->setNummobile($phone ?? '');
  1705.                     $user->setNometablisment($org ?? '');
  1706.                     // Générer un mot de passe temporaire
  1707.                     $mdpTemp substr(md5(uniqid()), 08);
  1708.                     $user->setPassword(password_hash($mdpTempPASSWORD_BCRYPT));
  1709.                     $user->setRoles(json_encode(['ROLE_ACCOMPAGNATEUR']));
  1710.                     $user->setStatut(1);
  1711.                     $em->persist($user);
  1712.                     $em->flush();
  1713.                     $this->logger->info('✅ Utilisateur créé: ID=' $user->getId());
  1714.                 } else {
  1715.                     $this->logger->info('✅ Utilisateur existant trouvé: ID=' $user->getId());
  1716.                 }
  1717.             } catch (\Exception $userError) {
  1718.                 $this->logger->error('❌ Erreur création utilisateur', [
  1719.                     'error' => $userError->getMessage(),
  1720.                     'trace' => $userError->getTraceAsString()
  1721.                 ]);
  1722.                 throw $userError;
  1723.             }
  1724.             // Déterminer si c'est un pack annuel (serenite/decouverte) ou un séjour simple
  1725.             $isPack in_array($packId, ['serenite''decouverte''Pack Sérénité''Pack Découverte']);
  1726.             $this->logger->info('🔍 Détection type de demande', [
  1727.                 'pack_id' => $packId,
  1728.                 'isPack' => $isPack 'OUI' 'NON',
  1729.                 'test_array' => ['serenite''decouverte''Pack Sérénité''Pack Découverte']
  1730.             ]);
  1731.             $sejour null;
  1732.             $commande null;
  1733.             if ($isPack) {
  1734.                 // ==================== CRÉATION COMMANDE PACK ====================
  1735.                 try {
  1736.                     $this->logger->info('📦 Création commande pack: ' $packId);
  1737.                     // Mapping pack → labeletype
  1738.                     $packNames = [
  1739.                         'decouverte' => 'Pack Découverte',
  1740.                         'serenite' => 'Pack Sérénité',
  1741.                         'Pack Découverte' => 'Pack Découverte',
  1742.                         'Pack Sérénité' => 'Pack Sérénité'
  1743.                     ];
  1744.                     $packLabel $packNames[$packId] ?? 'Pack Sérénité';
  1745.                     // Récupérer le produit en BDD
  1746.                     $typeProduit $em->getRepository(Typeproduit::class)->findOneBy([
  1747.                         'labeletype' => $packLabel
  1748.                     ]);
  1749.                     // Récupérer le statut "en attente"
  1750.                     $statutEnAttente $em->getRepository(Ref::class)->find(1);
  1751.                     if (!$statutEnAttente) {
  1752.                         $statutEnAttente = new Ref();
  1753.                         $statutEnAttente->setLibiller('En attente');
  1754.                         $em->persist($statutEnAttente);
  1755.                         $em->flush();
  1756.                     }
  1757.                     // Déterminer les montants du pack
  1758.                     $defaultsByPack = [
  1759.                         'Pack Sérénité' => ['ht' => 490'ttc' => 588],
  1760.                         'Pack Découverte' => ['ht' => 290'ttc' => 348],
  1761.                     ];
  1762.                     $montantHt $typeProduit $typeProduit->getMontantHt() : null;
  1763.                     $montantTtc $typeProduit $typeProduit->getMontantTTC() : null;
  1764.                     if (empty($montantHt) || empty($montantTtc)) {
  1765.                         $fallback $defaultsByPack[$packLabel] ?? $defaultsByPack['Pack Sérénité'];
  1766.                         $montantHt $montantHt ?: $fallback['ht'];
  1767.                         $montantTtc $montantTtc ?: $fallback['ttc'];
  1768.                     }
  1769.                     // Créer la commande avec statut "en attente"
  1770.                     $commande = new Commande();
  1771.                     $commande->setIdUser($user);
  1772.                     $commande->setStatut($statutEnAttente);
  1773.                     $commande->setMontantht($montantHt);
  1774.                     $commande->setMontantrth($montantTtc);
  1775.                     $commande->setMontanenv($montantTtc);
  1776.                     $commande->setTva(20);
  1777.                     // Dates d'abonnement: début maintenant, fin dans 12 mois
  1778.                     $startDateTime = new \DateTime();
  1779.                     $endDateTime = (clone $startDateTime)->modify('+12 months');
  1780.                     $commande->setDateCreateCommande($startDateTime);
  1781.                     $commande->setDateExpidition($endDateTime);
  1782.                     $commande->setPeriode('annuel');
  1783.                     $commande->setPaymentType('rib');
  1784.                     $commande->setNumComande(time());
  1785.                     $this->logger->info('💾 Avant persist/flush commande');
  1786.                     $em->persist($commande);
  1787.                     $this->logger->info('💾 Après persist, avant flush');
  1788.                     $em->flush();
  1789.                     $this->logger->info('💾 Après flush');
  1790.                     $commandeId $commande->getId();
  1791.                     $this->logger->info('✅ Commande pack créée: ID=' $commandeId ' Montant=' $montantTtc '€');
  1792.                     // Vérifier que la commande existe vraiment en BDD
  1793.                     $commandeVerif $em->getRepository(Commande::class)->find($commandeId);
  1794.                     if ($commandeVerif) {
  1795.                         $this->logger->info('✅ VÉRIFICATION: Commande #' $commandeId ' existe bien en BDD');
  1796.                     } else {
  1797.                         $this->logger->error('❌ VÉRIFICATION: Commande #' $commandeId ' N\'EXISTE PAS en BDD !');
  1798.                     }
  1799.                 } catch (\Exception $commandeError) {
  1800.                     $this->logger->error('❌ Erreur création commande pack', [
  1801.                         'error' => $commandeError->getMessage(),
  1802.                         'trace' => $commandeError->getTraceAsString()
  1803.                     ]);
  1804.                     // NE PAS continuer si la création échoue
  1805.                     return new JsonResponse([
  1806.                         'success' => false,
  1807.                         'message' => 'Erreur lors de la création de la commande: ' $commandeError->getMessage()
  1808.                     ], 500);
  1809.                 }
  1810.             } else {
  1811.                 // ==================== CRÉATION SÉJOUR SIMPLE PF ====================
  1812.                 try {
  1813.                     $this->logger->info('🏕️ Création du séjour simple PF');
  1814.                     $dateDebut $startDate ?: date('Y-m-d');
  1815.                     $dateFin $endDate ?: date('Y-m-d'strtotime($dateDebut ' +7 days'));
  1816.                     $themeTexte $theme ?: 'Séjour';
  1817.                     $nbParticipants $participants ?: 10;
  1818.                     $sejour $this->sejourService->CreationNouveauSejour(
  1819.                         $themeTexte,              // themSejour
  1820.                         '',                       // adressSejour
  1821.                         '',                       // codePostal
  1822.                         $dateDebut,              // dateDebut
  1823.                         $dateFin,                // FinSejour
  1824.                         '6-10 ans',              // AgeDugroupe
  1825.                         'PF',                    // type (Partenaire Financé)
  1826.                         $user->getId(),          // userid
  1827.                         $nbParticipants,         // NbEnfant
  1828.                         0,                       // connpay (0 = non payé)
  1829.                         'France',                // pays
  1830.                         '',                      // ville
  1831.                         null,                    // prixcnxparent
  1832.                         null,                    // prixcnxpartenaire
  1833.                         null,                    // reversecnxpart
  1834.                         null                     // reverseventepart
  1835.                     );
  1836.                     // Récupérer ou créer le statut "attente_paiement"
  1837.                     $statutAttentePaiement $em->getRepository(Ref::class)->findOneBy(['libiller' => 'attente_paiement']);
  1838.                     if (!$statutAttentePaiement) {
  1839.                         $this->logger->warning('⚠️ Statut "attente_paiement" non trouvé');
  1840.                     } else {
  1841.                         $sejour->setStatut($statutAttentePaiement);
  1842.                         $em->flush();
  1843.                         $this->logger->info('✅ Statut défini: ' $statutAttentePaiement->getLibiller());
  1844.                     }
  1845.                     $this->logger->info('✅ Séjour créé: Code=' $sejour->getCodeSejour() . ' ID=' $sejour->getId());
  1846.                 } catch (\Exception $sejourError) {
  1847.                     $this->logger->error('❌ Erreur création séjour', [
  1848.                         'error' => $sejourError->getMessage(),
  1849.                         'trace' => $sejourError->getTraceAsString()
  1850.                     ]);
  1851.                     throw $sejourError;
  1852.                 }
  1853.             }
  1854.             // Envoyer email à l'équipe avec les informations complètes
  1855.             $emailContext = [
  1856.                 'fullname' => $fullname,
  1857.                 'user_email' => $emailUser,
  1858.                 'phone' => $phone,
  1859.                 'org' => $org,
  1860.                 'message' => $message,
  1861.                 'pack_id' => $packId,
  1862.                 'participants' => $participants,
  1863.                 'total_amount' => $totalAmount,
  1864.                 'start_date' => $startDate,
  1865.                 'end_date' => $endDate,
  1866.                 'theme' => $theme,
  1867.                 'accompagnateur' => $accompagnateur,
  1868.                 'request_number' => $requestNumber,
  1869.             ];
  1870.             // Ajouter les infos spécifiques selon le type
  1871.             if ($isPack && $commande) {
  1872.                 $emailContext['commande_id'] = $commande->getId();
  1873.                 $emailContext['commande_numero'] = $commande->getNumComande();
  1874.                 $emailContext['montant_ht'] = $commande->getMontantht();
  1875.                 $emailContext['montant_ttc'] = $commande->getMontantrth();
  1876.                 $emailContext['is_pack'] = true;
  1877.             } else if ($sejour) {
  1878.                 $emailContext['code_sejour'] = $sejour->getCodeSejour();
  1879.                 $emailContext['sejour_id'] = $sejour->getId();
  1880.                 $emailContext['is_pack'] = false;
  1881.             }
  1882.             $subjectPrefix $isPack '📦 Demande RIB Pack Annuel' '🏦 Demande RIB Paiement Séjour Simple';
  1883.             // Ajouter le numéro de commande au sujet si c'est un pack
  1884.             $subjectSuffix $requestNumber;
  1885.             if ($isPack && $commande) {
  1886.                 $subjectSuffix .= ' - Commande #' $commande->getNumComande();
  1887.             }
  1888.             $teamEmail = (new TemplatedEmail())
  1889.                 ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  1890.                 ->to('partenariat@5sur5sejour.com')
  1891.                 ->cc('ramzi.benlarbi@gmail.com''yousra.tlich@gmail.com')
  1892.                 ->subject($subjectPrefix ' - ' $org ' - ' $subjectSuffix)
  1893.                 ->htmlTemplate('emails/bank_transfer_request_team.html.twig')
  1894.                 ->context($emailContext);
  1895.             $this->mailer->send($teamEmail);
  1896.             // Email de confirmation au client avec RIB
  1897.             $ribDetails = [
  1898.                 'iban' => 'FR76 1010 7001 5300 0123 4567 890',
  1899.                 'bic' => 'BREDFRPPXXX',
  1900.                 'beneficiaire' => '5SUR5 SEJOUR',
  1901.                 'reference' => $requestNumber
  1902.             ];
  1903.             // Préparer le contexte email client
  1904.             $clientEmailContext = [
  1905.                 'fullname' => $fullname,
  1906.                 'request_number' => $requestNumber,
  1907.                 'rib' => $ribDetails,
  1908.                 'pack_id' => $packId,
  1909.                 'participants' => $participants,
  1910.                 'total_amount' => $totalAmount,
  1911.                 'start_date' => $startDate,
  1912.                 'end_date' => $endDate,
  1913.                 'theme' => $theme,
  1914.                 'accompagnateur' => $accompagnateur,
  1915.                 'org' => $org,
  1916.             ];
  1917.             // Ajouter les infos spécifiques
  1918.             if ($isPack && $commande) {
  1919.                 $clientEmailContext['commande_id'] = $commande->getId();
  1920.                 $clientEmailContext['commande_numero'] = $commande->getNumComande();
  1921.                 $clientEmailContext['montant_ht'] = $commande->getMontantht();
  1922.                 $clientEmailContext['montant_ttc'] = $commande->getMontantrth();
  1923.                 $clientEmailContext['is_pack'] = true;
  1924.             } else if ($sejour) {
  1925.                 $clientEmailContext['code_sejour'] = $sejour->getCodeSejour();
  1926.                 $clientEmailContext['sejour_id'] = $sejour->getId();
  1927.                 $clientEmailContext['is_pack'] = false;
  1928.             }
  1929.             $clientEmail = (new TemplatedEmail())
  1930.                 ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  1931.                 ->to($emailUser)
  1932.                 ->subject('RIB et Facture Proforma - ' $requestNumber ' - 5sur5 Séjour')
  1933.                 ->htmlTemplate('emails/bank_transfer_request_client.html.twig')
  1934.                 ->context($clientEmailContext);
  1935.             $this->mailer->send($clientEmail);
  1936.             return new JsonResponse([
  1937.                 'success' => true,
  1938.                 'message' => 'Votre demande a été envoyée avec succès ! Vous allez recevoir le RIB et la facture proforma par email.',
  1939.                 'requestNumber' => $requestNumber
  1940.             ]);
  1941.         } catch (\Exception $e) {
  1942.             $this->logger->error('Erreur lors de l\'envoi de la demande de RIB', [
  1943.                 'error' => $e->getMessage(),
  1944.                 'trace' => $e->getTraceAsString()
  1945.             ]);
  1946.             return new JsonResponse([
  1947.                 'success' => false,
  1948.                 'message' => 'Erreur: ' $e->getMessage(),
  1949.                 'error' => $e->getMessage(),
  1950.                 'trace' => $e->getTraceAsString()
  1951.             ], 500);
  1952.         }
  1953.     }
  1954.     /**
  1955.      * @Route("/pack-request-submit", name="pack_request_submit", methods={"POST"})
  1956.      */
  1957.     public function packRequestSubmit(Request $request): JsonResponse
  1958.     {
  1959.         try {
  1960.             $fullname $request->request->get('pack_fullname');
  1961.             $email $request->request->get('pack_email');
  1962.             $phone $request->request->get('pack_phone');
  1963.             $org $request->request->get('pack_org');
  1964.             $packType $request->request->get('pack_type');
  1965.             $message $request->request->get('pack_message');
  1966.             // Validation basique
  1967.             if (empty($fullname) || empty($email) || empty($packType)) {
  1968.                 return new JsonResponse([
  1969.                     'success' => false,
  1970.                     'message' => 'Le nom, l\'email et le type de pack sont obligatoires.'
  1971.                 ], 400);
  1972.             }
  1973.             // Envoyer email à l'équipe
  1974.             $email = (new TemplatedEmail())
  1975.                 ->from(new Address('noreply@5sur5sejour.fr''5sur5 Séjour'))
  1976.                 ->to('contact@5sur5sejour.fr')
  1977.                 ->subject('Demande de Pack Partenaire - ' $fullname)
  1978.                 ->htmlTemplate('emails/pack_request_team.html.twig')
  1979.                 ->context([
  1980.                     'fullname' => $fullname,
  1981.                     'email' => $email,
  1982.                     'phone' => $phone,
  1983.                     'org' => $org,
  1984.                     'packType' => $packType,
  1985.                     'message' => $message
  1986.                 ]);
  1987.             $this->mailer->send($email);
  1988.             // Email de confirmation au client
  1989.             $clientEmail = (new TemplatedEmail())
  1990.                 ->from(new Address('noreply@5sur5sejour.fr''5sur5 Séjour'))
  1991.                 ->to($email)
  1992.                 ->subject('Demande de Pack Partenaire reçue - 5sur5 Séjour')
  1993.                 ->htmlTemplate('emails/pack_request_partner.html.twig')
  1994.                 ->context([
  1995.                     'fullname' => $fullname,
  1996.                     'packType' => $packType
  1997.                 ]);
  1998.             $this->mailer->send($clientEmail);
  1999.             return new JsonResponse([
  2000.                 'success' => true,
  2001.                 'message' => 'Votre demande de pack partenaire a été envoyée avec succès ! Nous vous recontacterons dans les 24 heures.'
  2002.             ]);
  2003.         } catch (\Exception $e) {
  2004.             $this->logger->error('Erreur lors de l\'envoi de la demande de pack', [
  2005.                 'error' => $e->getMessage(),
  2006.                 'trace' => $e->getTraceAsString()
  2007.             ]);
  2008.             return new JsonResponse([
  2009.                 'success' => false,
  2010.                 'message' => 'Une erreur est survenue lors de l\'envoi de la demande. Veuillez réessayer plus tard.'
  2011.             ], 500);
  2012.         }
  2013.     }
  2014.     /**
  2015.      * @Route("/checkout-create-session", name="checkout_create_session", methods={"POST"})
  2016.      */
  2017.     public function checkoutCreateSession(Request $request): JsonResponse
  2018.     {
  2019.         try {
  2020.             $company $request->request->get('company');
  2021.             $contactName $request->request->get('contact_name');
  2022.             $email $request->request->get('email');
  2023.             $phone $request->request->get('phone');
  2024.             $packType $request->request->get('pack_type');
  2025.             $address $request->request->get('address');
  2026.             $city $request->request->get('city');
  2027.             $postalCode $request->request->get('postal_code');
  2028.             $siret $request->request->get('siret');
  2029.             // Validation basique
  2030.             if (empty($company) || empty($contactName) || empty($email) || empty($packType)) {
  2031.                 return new JsonResponse([
  2032.                     'success' => false,
  2033.                     'message' => 'Les champs obligatoires doivent être remplis.'
  2034.                 ], 400);
  2035.             }
  2036.             // Envoyer email à l'équipe
  2037.             $email = (new TemplatedEmail())
  2038.                 ->from(new Address('noreply@5sur5sejour.fr''5sur5 Séjour'))
  2039.                 ->to('contact@5sur5sejour.fr')
  2040.                 ->subject('Nouvelle souscription en ligne - ' $company)
  2041.                 ->htmlTemplate('emails/checkout_request_team.html.twig')
  2042.                 ->context([
  2043.                     'company' => $company,
  2044.                     'contactName' => $contactName,
  2045.                     'email' => $email,
  2046.                     'phone' => $phone,
  2047.                     'packType' => $packType,
  2048.                     'address' => $address,
  2049.                     'city' => $city,
  2050.                     'postalCode' => $postalCode,
  2051.                     'siret' => $siret
  2052.                 ]);
  2053.             $this->mailer->send($email);
  2054.             // Email de confirmation au client
  2055.             $clientEmail = (new TemplatedEmail())
  2056.                 ->from(new Address('noreply@5sur5sejour.fr''5sur5 Séjour'))
  2057.                 ->to($email)
  2058.                 ->subject('Souscription reçue - 5sur5 Séjour')
  2059.                 ->htmlTemplate('emails/checkout_request_client.html.twig')
  2060.                 ->context([
  2061.                     'company' => $company,
  2062.                     'contactName' => $contactName,
  2063.                     'packType' => $packType
  2064.                 ]);
  2065.             $this->mailer->send($clientEmail);
  2066.             return new JsonResponse([
  2067.                 'success' => true,
  2068.                 'message' => 'Votre souscription a été enregistrée avec succès ! Nous vous recontacterons dans les 24 heures pour finaliser l\'activation.'
  2069.             ]);
  2070.         } catch (\Exception $e) {
  2071.             $this->logger->error('Erreur lors de l\'enregistrement de la souscription', [
  2072.                 'error' => $e->getMessage(),
  2073.                 'trace' => $e->getTraceAsString()
  2074.             ]);
  2075.             return new JsonResponse([
  2076.                 'success' => false,
  2077.                 'message' => 'Une erreur est survenue lors de l\'enregistrement. Veuillez réessayer plus tard.'
  2078.             ], 500);
  2079.         }
  2080.     }
  2081.     /**
  2082.      * @Route("/quote-request", name="quote_request", methods={"POST"})
  2083.      */
  2084.     public function quoteRequest(Request $request): JsonResponse
  2085.     {
  2086.         try {
  2087.             $orgType $request->request->get('org_type');
  2088.             $orgName $request->request->get('org_name');
  2089.             $contactName $request->request->get('contact_name');
  2090.             $email $request->request->get('email');
  2091.             $phone $request->request->get('phone');
  2092.             $packType $request->request->get('pack_type');
  2093.             $address $request->request->get('address');
  2094.             $city $request->request->get('city');
  2095.             $postalCode $request->request->get('postal_code');
  2096.             $message $request->request->get('message');
  2097.             // Validation basique
  2098.             if (empty($orgType) || empty($orgName) || empty($contactName) || empty($email) || empty($packType)) {
  2099.                 return new JsonResponse([
  2100.                     'success' => false,
  2101.                     'message' => 'Les champs obligatoires doivent être remplis.'
  2102.                 ], 400);
  2103.             }
  2104.             // Envoyer email à l'équipe
  2105.             $email = (new TemplatedEmail())
  2106.                 ->from(new Address('noreply@5sur5sejour.fr''5sur5 Séjour'))
  2107.                 ->to('contact@5sur5sejour.fr')
  2108.                 ->subject('Demande de devis - ' $orgName)
  2109.                 ->htmlTemplate('emails/quote_request_team.html.twig')
  2110.                 ->context([
  2111.                     'orgType' => $orgType,
  2112.                     'orgName' => $orgName,
  2113.                     'contactName' => $contactName,
  2114.                     'email' => $email,
  2115.                     'phone' => $phone,
  2116.                     'packType' => $packType,
  2117.                     'address' => $address,
  2118.                     'city' => $city,
  2119.                     'postalCode' => $postalCode,
  2120.                     'message' => $message
  2121.                 ]);
  2122.             $this->mailer->send($email);
  2123.             // Email de confirmation au client
  2124.             $clientEmail = (new TemplatedEmail())
  2125.                 ->from(new Address('noreply@5sur5sejour.fr''5sur5 Séjour'))
  2126.                 ->to($email)
  2127.                 ->subject('Demande de devis reçue - 5sur5 Séjour')
  2128.                 ->htmlTemplate('emails/quote_request_client.html.twig')
  2129.                 ->context([
  2130.                     'orgName' => $orgName,
  2131.                     'contactName' => $contactName,
  2132.                     'packType' => $packType
  2133.                 ]);
  2134.             $this->mailer->send($clientEmail);
  2135.             return new JsonResponse([
  2136.                 'success' => true,
  2137.                 'message' => 'Votre demande de devis a été envoyée avec succès ! Nous vous recontacterons dans les 24 heures.'
  2138.             ]);
  2139.         } catch (\Exception $e) {
  2140.             $this->logger->error('Erreur lors de l\'envoi de la demande de devis', [
  2141.                 'error' => $e->getMessage(),
  2142.                 'trace' => $e->getTraceAsString()
  2143.             ]);
  2144.             return new JsonResponse([
  2145.                 'success' => false,
  2146.                 'message' => 'Une erreur est survenue lors de l\'envoi de la demande. Veuillez réessayer plus tard.'
  2147.             ], 500);
  2148.         }
  2149.     }
  2150.     /**
  2151.      * @Route("/creation-simple/send-rib-codes", name="app_creation_simple_send_rib_codes", methods={"POST","GET"})
  2152.      */
  2153.     public function sendRibCodes(Request $requestEntityManagerInterface $em): JsonResponse
  2154.     {
  2155.         if ($request->isMethod('GET')) {
  2156.             return new JsonResponse([
  2157.                 'success' => false,
  2158.                 'message' => 'Cette URL est réservée aux requêtes sécurisées. Merci d\'utiliser le formulaire de création de séjour.'
  2159.             ], 405);
  2160.         }
  2161.         try {
  2162.             $data json_decode($request->getContent(), true);
  2163.             $sejourId $data['sejour_id'] ?? null;
  2164.             $billingEmail $data['billing_email'] ?? null;
  2165.             $billingContact $data['billing_contact'] ?? null;
  2166.             // Note: Anti-spam désactivé ici car la validation reCAPTCHA est faite sur la requête initiale
  2167.             error_log("📧 sendRibCodes appelé pour séjour #{$sejourId}, email: {$billingEmail}");
  2168.             if (!$sejourId || !$billingEmail) {
  2169.                 return new JsonResponse([
  2170.                     'success' => false,
  2171.                     'message' => 'Données manquantes'
  2172.                 ], 400);
  2173.             }
  2174.             $sejour $em->getRepository(\App\Entity\Sejour::class)->find($sejourId);
  2175.             if (!$sejour) {
  2176.                 return new JsonResponse([
  2177.                     'success' => false,
  2178.                     'message' => 'Séjour non trouvé'
  2179.                 ], 404);
  2180.             }
  2181.             $commande $em->getRepository(Commande::class)->findOneBy(['idSejour' => $sejour]);
  2182.             if (!$commande) {
  2183.                 return new JsonResponse([
  2184.                     'success' => false,
  2185.                     'message' => 'Commande non trouvée'
  2186.                 ], 404);
  2187.             }
  2188.             $user $sejour->getIdPartenaire();
  2189.             if (!$user) {
  2190.                 return new JsonResponse([
  2191.                     'success' => false,
  2192.                     'message' => 'Utilisateur non trouvé'
  2193.                 ], 404);
  2194.             }
  2195.             // Envoyer email avec RIB + codes séjour
  2196.             $email = (new TemplatedEmail())
  2197.                 ->from(new Address('info@5sur5sejour.com''5sur5 Séjour'))
  2198.                 ->to($billingEmail)
  2199.                 ->subject('RIB et codes séjour - ' $sejour->getCodeSejour())
  2200.                 ->htmlTemplate('emails/sejour_simple/rib_codes.html.twig')
  2201.                 ->context([
  2202.                     'sejour' => $sejour,
  2203.                     'commande' => $commande,
  2204.                     'user' => $user,
  2205.                     'billingContact' => $billingContact
  2206.                 ]);
  2207.             $this->mailer->send($email);
  2208.             // Envoyer aussi les codes à l'accompagnateur
  2209.             $accompagnateur $sejour->getIdAcommp();
  2210.             if ($accompagnateur && $accompagnateur->getReponseemail()) {
  2211.                 $this->UserService->EnvoyerEmailAcommpatActivationNewMail($sejour$accompagnateur);
  2212.             }
  2213.             return new JsonResponse([
  2214.                 'success' => true,
  2215.                 'message' => 'RIB et codes séjour envoyés avec succès'
  2216.             ]);
  2217.         } catch (\Exception $e) {
  2218.             $this->logger->error('Erreur lors de l\'envoi RIB + codes', [
  2219.                 'error' => $e->getMessage(),
  2220.                 'trace' => $e->getTraceAsString()
  2221.             ]);
  2222.             return new JsonResponse([
  2223.                 'success' => false,
  2224.                 'message' => 'Une erreur est survenue lors de l\'envoi'
  2225.             ], 500);
  2226.         }
  2227.     }
  2228.     /**
  2229.      * @Route("/creation-simple/checkout", name="app_creation_simple_checkout", methods={"GET"})
  2230.      */
  2231.     public function checkoutSejourSimple(Request $requestEntityManagerInterface $em): Response
  2232.     {
  2233.         $sejourId $request->query->get('sejour_id');
  2234.         if (!$sejourId) {
  2235.             return $this->redirectToRoute('app_creation_simple_creer');
  2236.         }
  2237.         $sejour $em->getRepository(\App\Entity\Sejour::class)->find($sejourId);
  2238.         if (!$sejour) {
  2239.             return $this->redirectToRoute('app_creation_simple_creer');
  2240.         }
  2241.         $commande $em->getRepository(Commande::class)->findOneBy(['idSejour' => $sejour]);
  2242.         if (!$commande) {
  2243.             return $this->redirectToRoute('app_creation_simple_creer');
  2244.         }
  2245.         // Stocker la commande en session pour Payplug
  2246.         $session $request->getSession();
  2247.         $session->set('commandeActual'$commande->getId());
  2248.         // Rediriger vers Payplug (route dédiée aux séjours simples, sans authentification requise)
  2249.         return $this->redirectToRoute('sejour_simple_payplug_chargement_form');
  2250.     }
  2251. }