src/Controller/CheckoutController.php line 29

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Repository\CategoryRepository;
  4. use App\Repository\ProductRepository;
  5. use App\Services\Cart;
  6. use App\Services\Paypal;
  7. use Doctrine\ORM\EntityManagerInterface;
  8. use Exception;
  9. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  10. use Symfony\Component\HttpFoundation\JsonResponse;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\Mailer\MailerInterface;
  14. use Symfony\Component\Routing\Annotation\Route;
  15. class CheckoutController extends AbstractController
  16. {
  17.     private $paypalService;
  18.     public function __construct()
  19.     {
  20.         
  21.         $this->paypalService = new Paypal();
  22.     }
  23.     #[Route('/checkout'name'checkout')]
  24.     public function index(Request $requestCategoryRepository $catRepoProductRepository $productRepo): Response
  25.     {
  26.         $products $productRepo->findAll();
  27.         $categories $catRepo->findAll();
  28.         
  29.         //TO COPY TO EVERY ROUTE THAT DISPLAY CART
  30.         $session $request->getSession();
  31.         $cart = new Cart($session);
  32.         $totalQuantity $cart->getCartTotalQuantity();
  33.         $maxQuantity $cart->getMaxQuantityAllowed();
  34.         $cartProducts $cart->getCartProducts($productRepo);
  35.         $totalWeight $cart->getTotalWeightForDisplay($productRepo);
  36.         $totalPrice $cart->getTotalPrice($productRepo);
  37.         $ttcPrice $cart->getTtcPrice($productRepo);
  38.         $weightPrice $cart->getWeightPrice();
  39.         return $this->render('checkout/checkout.html.twig', [
  40.             'products' => $products,
  41.             'categories' => $categories,
  42.             //TO COPY TO EVERY ROUTE THAT DISPLAY CART
  43.             'totalQuantity' => $totalQuantity,
  44.             'totalWeight' => $totalWeight,
  45.             'weightPrice' => $weightPrice,
  46.             'maxQuantity' => $maxQuantity,
  47.             'cartProducts' => $cartProducts,
  48.             'totalPrice' => $totalPrice,
  49.             'ttcPrice' => $ttcPrice
  50.         ]);
  51.     }
  52.     /**
  53.      * Route used with PaymentDataManager.js
  54.      * Get billing details and store data inside billing-detailes session
  55.      *
  56.      * @param Request $request
  57.      * @param ProductRepository $productRepo
  58.      * @return JsonResponse
  59.      */
  60.     #[Route('/billing-details'name'billing_details')]
  61.     public function billingDetails(Request $requestProductRepository $productRepo): JsonResponse
  62.     {
  63.         $data $request->getContent();
  64.         $data json_decode($datatrue);
  65.         try
  66.         {
  67.             foreach ($data as $key => $value) {
  68.                 // Vérification si la donnée est vide
  69.                 if (empty($value) && $value !== '0') {
  70.                     throw new \InvalidArgumentException("La valeur pour $key ne peut pas être vide.");
  71.                 }
  72.             
  73.                 if ($key === 'firstname') {
  74.                     // Vérification prénom : alphabétique, espace, tiret et longueur entre 1 et 50 caractères
  75.                     if (!preg_match('/^[a-zA-Zà-ÿÀ-ÿ\s\-]+$/'$value) || strlen($value) < || strlen($value) > 50) {
  76.                         throw new \InvalidArgumentException("Le prénom ($value) n'est pas valide. Il doit contenir uniquement des lettres, des espaces et des tirets, et avoir entre 1 et 50 caractères.");
  77.                     }
  78.                 }
  79.             
  80.                 if ($key === 'lastname') {
  81.                     // Vérification nom : alphabétique, espace, tiret et longueur entre 1 et 50 caractères
  82.                     if (!preg_match('/^[a-zA-Zà-ÿÀ-ÿ\s\-]+$/'$value) || strlen($value) < || strlen($value) > 50) {
  83.                         throw new \InvalidArgumentException("Le nom ($value) n'est pas valide. Il doit contenir uniquement des lettres, des espaces et des tirets, et avoir entre 1 et 50 caractères.");
  84.                     }
  85.                 }
  86.                 if ($key === 'address') {
  87.                     // Vérification adresse : autorise lettres, chiffres, espaces, virgules, tirets, points et caractères accentués.
  88.                     // Limite de longueur à 255 caractères (longueur raisonnable pour une adresse)
  89.                     if (!preg_match('/^[a-zA-Z0-9à-ÿÀ-ÿ\s\-,\.#()]+$/'$value) || strlen($value) > 255) {
  90.                         throw new \InvalidArgumentException("L'adresse ($value) n'est pas valide. Elle doit contenir des lettres, chiffres, espaces, virgules, tirets, points et avoir moins de 255 caractères.");
  91.                     }
  92.                 }
  93.                 if ($key === 'additional-address-information') {
  94.                     // Vérification informations additionnelles : autorise lettres, chiffres, espaces, tirets, points, slashes, # et ne dépasse pas 100 caractères
  95.                     if (!preg_match('/^[a-zA-Z0-9à-ÿÀ-ÿ\s\-\.\,\/#]*$/'$value) || strlen($value) > 100) {
  96.                         throw new \InvalidArgumentException("Les informations additionnelles ($value) ne sont pas valides. Elles doivent contenir des lettres, chiffres, espaces, tirets, points, slashes, # et ne pas dépasser 100 caractères.");
  97.                     }
  98.                 }
  99.                 if ($key === 'zip') {
  100.                     if (!preg_match('/^\d{5}$|^\d{5}[-\s]?\d{4}$|^[A-Za-z]{1,2}\d{1,2}[A-Za-z]?\s?\d[A-Za-z]{2}$/'$value)) {
  101.                         throw new \InvalidArgumentException("Le code postal ($value) n'est pas valide. Il doit correspondre à un format valide français ou international.");
  102.                     }
  103.                 }
  104.                 if ($key === 'phone') {
  105.                     if ($value !== null && !preg_match('/^(\+?[0-9]{1,3})?[\s.-]?[0-9]{1,4}[\s.-]?[0-9]{1,4}[\s.-]?[0-9]{1,4}$/'$value)) {
  106.                         throw new \InvalidArgumentException("Le téléphone ($value) n'est pas valide. Il doit être un numéro valide, international ou local.");
  107.                     }
  108.                 }
  109.                 if ($key === 'email') {
  110.                     if (!filter_var($valueFILTER_VALIDATE_EMAIL)) {
  111.                         throw new \InvalidArgumentException("L'email ($value) n'est pas valide. Il doit être une adresse email correcte.");
  112.                     }
  113.                 }
  114.                 if ($key === 'additional-delivery-information') {
  115.                     // La taille maximale de 500 caractères est raisonnable pour un champ textarea
  116.                     // Autorisation des lettres, chiffres, espaces, ponctuation, retours à la ligne
  117.                     if (strlen($value) > 500) {
  118.                         throw new \InvalidArgumentException("Les informations supplémentaires ne doivent pas dépasser 500 caractères.");
  119.                     }
  120.                 
  121.                     // Si des caractères spéciaux sont utilisés dans le texte (mais sans risque de script, etc)
  122.                     if (!preg_match('/^[a-zA-Z0-9\s,.\-!?\n]*$/'$value)) {
  123.                         throw new \InvalidArgumentException("Les informations supplémentaires contiennent des caractères invalides. Seuls les lettres, chiffres, espaces, virgules, points, tirets, points d'exclamation, points d'interrogation et retours à la ligne sont autorisés.");
  124.                     }
  125.                 }
  126.             }
  127.             //Save inside session billingDetails
  128.             $session $request->getSession();
  129.             $session->set('billing-details'$data);
  130.             
  131.             return new JsonResponse([
  132.                 'message' => 'Données reçues avec succès',
  133.                 'receivedData' => $data
  134.             ]);
  135.         }
  136.         
  137.         catch(\InvalidArgumentException $e)
  138.         {
  139.             return new JsonResponse([
  140.                 'message' => $e->getMessage()
  141.             ], 400);
  142.         }
  143.     }
  144.     #[Route('/payment'name'payment')]
  145.     public function payment(Request $requestProductRepository $productRepoCategoryRepository $catRepo): Response
  146.     {
  147.         $products $productRepo->findAll();
  148.         $categories $catRepo->findAll();
  149.         
  150.         //TO COPY TO EVERY ROUTE THAT DISPLAY CART
  151.         $session $request->getSession();
  152.         $cart = new Cart($session);
  153.         $totalQuantity $cart->getCartTotalQuantity();
  154.         $maxQuantity $cart->getMaxQuantityAllowed();
  155.         $cartProducts $cart->getCartProducts($productRepo);
  156.         $totalWeight $cart->getTotalWeightForDisplay($productRepo);
  157.         $totalPrice $cart->getTotalPrice($productRepo);
  158.         $ttcPrice $cart->getTtcPrice($productRepo);
  159.         $weightPrice $cart->getWeightPrice();
  160.         //Billing details
  161.         $session $request->getSession();
  162.         $billingDetailsSession $session->get('billing-details');
  163.         if (!$billingDetailsSession)
  164.         {
  165.             $response $this->redirectToRoute('app_home');   
  166.             $response->headers->set('Cache-Control',
  167.             'no-store, no-cache, must-revalidate, max-age=0');
  168.             $response->headers->set('Pragma''no-cache');
  169.             $response->headers->set('Expires''0');
  170.             return $response;
  171.         }
  172.         $cartSession = new Cart($session);
  173.         $ttcPrice $cartSession->getTtcPrice($productRepo);
  174.         $this->paypalService->setTtcPrice($ttcPrice);
  175.         $response $this->render('checkout/payment.html.twig', [
  176.             'products' => $products,
  177.             'categories' => $categories,
  178.             'billingDetails' => $billingDetailsSession,
  179.             'ttcPrice' => $ttcPrice,
  180.             //TO COPY TO EVERY ROUTE THAT DISPLAY CART
  181.             'totalQuantity' => $totalQuantity,
  182.             'totalWeight' => $totalWeight,
  183.             'weightPrice' => $weightPrice,
  184.             'maxQuantity' => $maxQuantity,
  185.             'cartProducts' => $cartProducts,
  186.             'totalPrice' => $totalPrice,
  187.             'ttcPrice' => $ttcPrice,
  188.             'paypalTest' => 'No testing ...'
  189.         ]);
  190.         $response->headers->set('Cache-Control',
  191.         'no-store, no-cache, must-revalidate, max-age=0');
  192.         $response->headers->set('Pragma''no-cache');
  193.         $response->headers->set('Expires''0');
  194.         return $response;
  195.     }
  196.     #[Route('/api/orders'name:'paypal_create_orders')]
  197.     public function createOrder(Request $requestProductRepository $productRepo): JsonResponse
  198.     {
  199.         $data json_decode($request->getContent(), true);
  200.         
  201.         $session $request->getSession();
  202.         $cartSession = new Cart($session);
  203.         $ttcPrice $cartSession->getTtcPrice($productRepo);
  204.         
  205.         $cart $data["cart"];
  206.         $cart['ttcPrice'] = $ttcPrice;
  207.         
  208.         $orderResponse $this->paypalService->createOrder($cart);
  209.         if (isset($orderResponse['error'])) {
  210.             return new JsonResponse(['error' => $orderResponse['error']], 500);
  211.         }
  212.         return new JsonResponse($orderResponse['jsonResponse']);
  213.     }
  214.     #[Route('/api/orders/{orderId}/capture'name:'paypal_capture'methods: ['POST'])]
  215.     public function createCapture(Request $request$orderIdProductRepository $productRepoEntityManagerInterface $em): JsonResponse
  216.     {
  217.         //Check product quantity stock available
  218.         $session $request->getSession();
  219.         $cart = new Cart($session);
  220.         $cartProducts $cart->getCartProducts($productRepo);
  221.         $cartProductsLength count($cartProducts);
  222.         for ($i 0$i $cartProductsLength $i++)
  223.         {
  224.             $productId $cartProducts[$i]["product"]->getId();
  225.             $productName $cartProducts[$i]["product"]->getName();
  226.             $productQuantityAsked $cartProducts[$i]["quantity"];
  227.             $productStockData $this->forward('App\Controller\MainController::getProductStock', [
  228.                 'id' => $productId,
  229.             ]);
  230.             $productStock intvaljson_decode($productStockData->getContent(), true));
  231.             try
  232.             {
  233.                 if ($productQuantityAsked $productStock)
  234.                 {
  235.                     throw new Exception('Le produit '.$productName.'n\'est plus disponible');
  236.                 }
  237.             }
  238.             catch(Exception $e)
  239.             {
  240.                 return new JsonResponse(["error" => $e->getMessage()]);
  241.             }
  242.             //REMOVE PRODUCT ASKED QUANTITY TO PRODUCT STOCK ENTITY
  243.             $product $productRepo->find($productId);
  244.             $updatedProductStock $productStock $productQuantityAsked;
  245.             $product->setStock($updatedProductStock);
  246.             $em->persist($product);
  247.             $em->flush();
  248.         }
  249.         try {
  250.             $captureResponse $this->paypalService->captureOrder($orderId);
  251.             $session $request->getSession();
  252.             $billingDetails $session->get('billing-details');
  253.             $billingDetails['captureResponse'] = $captureResponse;
  254.             $session->set('billing-details'$billingDetails);
  255.             return new JsonResponse($captureResponse["jsonResponse"]);
  256.         } catch (Exception $e) {
  257.             return new JsonResponse(["error" => $e->getMessage()]);
  258.             
  259.         }
  260.     }
  261.     #[Route('/payment-sucess'name:'payment_sucess')]
  262.     public function paymentSucessMessage(Request $requestMailerInterface $mailerProductRepository $productRepoCategoryRepository $catRepo)
  263.     {        
  264.         $categories $catRepo->findAll();
  265.         
  266.         $session $request->getSession();
  267.         $cart = new Cart($session);
  268.         $user $this->getUser();
  269.         $billingDetails $session->get('billing-details');
  270.         if (!$billingDetails)
  271.         {
  272.             return $this->redirectToRoute('app_home');
  273.         }
  274.         $transactionId $billingDetails['captureResponse']['jsonResponse']['purchase_units'][0]['payments']['captures'][0]['id'];
  275.         if ($cart->sendEndTransactionMail($user$mailer$billingDetails$productRepo))
  276.         {   
  277.             $statusMessage "N° de commande: ".$transactionId.".";//." Mail correctement envoyé";
  278.         }
  279.         else
  280.         {
  281.             $statusMessage "Erreur, échec de la transaction, veuillez vérifier vos informations de paiement.";
  282.         }
  283.         $session->remove('cart');
  284.         $session->remove('billing-details');
  285.         $totalQuantity $cart->getCartTotalQuantity();
  286.         $maxQuantity $cart->getMaxQuantityAllowed();
  287.         $cartProducts $cart->getCartProducts($productRepo);
  288.         $totalPrice $cart->getTotalPrice($productRepo);
  289.         
  290.         return $this->render('checkout/success.html.twig', [
  291.             'categories' => $categories,
  292.             'totalQuantity' => $totalQuantity,
  293.             'maxQuantity' => $maxQuantity,
  294.             'cartProducts' => $cartProducts,
  295.             'totalPrice' => $totalPrice,
  296.             'statusMessage' => $statusMessage                   
  297.         ]);
  298.     }
  299. }