src/Controller/Page/CheckoutController.php line 110

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Page;
  3. use App\Entity\Airport;
  4. use App\Entity\Booking;
  5. use App\Entity\Game;
  6. use App\Entity\StadiumCategory;
  7. use App\Entity\VRExtraCosts;
  8. use App\Event\BookingCompletedEvent;
  9. use App\Event\SubmitBookingEvent;
  10. use App\Form\BookerType;
  11. use App\Form\MainBookerHomeType;
  12. use App\Form\MainBookerType;
  13. use App\Model\Basket;
  14. use App\Renderer\Page;
  15. use App\Service\Basket as BasketService;
  16. use App\Service\BookingService;
  17. use App\Service\CheckoutService;
  18. use App\Service\ExtraCostsService;
  19. use App\Service\Pyton;
  20. use App\Templating\Decorator;
  21. use DateTime;
  22. use Exception;
  23. use Flagception\Bundle\FlagceptionBundle\Annotations\Feature;
  24. use GuzzleHttp\Client;
  25. use Money\Exception\UnknownCurrencyException;
  26. use Omnipay\Mollie\Gateway;
  27. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  28. use Symfony\Component\HttpFoundation\Request;
  29. use Symfony\Component\HttpFoundation\Response;
  30. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  31. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  32. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  33. use App\Model\Pyton\Basket as PytonBasket;
  34. use App\Service\GameService as GameService;
  35. /**
  36.  * @Feature("feature_voetbalretour")
  37.  */
  38. class CheckoutController extends AbstractController
  39. {
  40.     public const PAYMENT_DEPOSIT_TRESHOLD 42;
  41.     public const PIGGY_PARTICIPATE_KEY 'piggy_participate';
  42.     public const PIGGY_REFERRAL_KEY 'piggy_referral';
  43.     public function __construct(
  44.         protected readonly Page $pageRenderer,
  45.         protected readonly Decorator $decorator,
  46.         protected readonly BasketService $basketService,
  47.         protected readonly Pyton $pyton,
  48.         protected readonly SessionInterface $session,
  49.         protected readonly CheckoutService $checkoutService,
  50.         protected readonly BookingService $service,
  51.         protected readonly Client $pytonClient,
  52.         protected readonly EventDispatcherInterface $eventDispatcher,
  53.         protected readonly ExtraCostsService $extraCostsService,
  54.         protected readonly GameService $gameService,
  55.     ) {
  56.         setlocale(\LC_ALL'nl_NL');
  57.     }
  58.     /**
  59.      * @throws Twig_Error_Loader
  60.      * @throws Twig_Error_Runtime
  61.      * @throws Twig_Error_Syntax
  62.      * @throws UnknownCurrencyException
  63.      */
  64.     public function checkout(Request $requestGame $match): Response
  65.     {
  66.         if($match->isDisableBooking()){
  67.             return $this->redirectToRoute('wedstrijden');
  68.         }
  69.         switch ($request->get('step'1)) {
  70.             case 1:
  71.             default:
  72.                 $response $this->checkoutStep1($match);
  73.                 break;
  74.             case 2:
  75.                 $response $this->checkoutStep2($request$match);
  76.                 break;
  77.             case 3:
  78.                 $response $this->checkoutStep3($match$request);
  79.                 break;
  80.             case 4:
  81.                 $response $this->checkoutStep4($request$match);
  82.                 break;
  83.             case 5:
  84.                 $response $this->checkoutStep5($request$match);
  85.                 break;
  86.             case 'test':
  87.                 $response $this->test();
  88.                 break;
  89.         }
  90.         $response->setSharedMaxAge(0);
  91.         return $response;
  92.     }
  93.     /**
  94.      * @throws Twig_Error_Loader
  95.      * @throws Twig_Error_Runtime
  96.      * @throws Twig_Error_Syntax
  97.      * @throws Exception
  98.      *
  99.      * @return Response
  100.      */
  101.     private function checkoutStep1(Game $match)
  102.     {
  103.         $basket $this->basketService->getBasket($match);
  104.         $accomodation null;
  105.         $chosenUnits null;
  106.         $accomodationReceipt null;
  107.         if ($basket->getPytonBasket() instanceof PytonBasket) {
  108.             $accomodation json_decode($basket->getPytonBasket()->getAccomodation(), true);
  109.             $accomodationReceipt json_decode($basket->getPytonBasket()->getAccommodationReceipt(), true);
  110.             if ($accomodationReceipt) {
  111.                 $t 1;
  112.                 foreach ($accomodationReceipt['Receipt']['Accommodation']['Units'] as $unit) {
  113.                     for ($i 1$i <= $unit['Allotment']['Chosen']; ++$i) {
  114.                         $chosenUnits[$t] = [
  115.                             'roomtype' => $unit['Description'].$unit['Type'].$unit['Boards'][0]['Name'],
  116.                             'numberpersons' => $unit['Occupancy']['Maximal'],
  117.                             'original' => $match->getCheapestAccommodation()->getAmount(),
  118.                         ];
  119.                         ++$t;
  120.                     }
  121.                 }
  122.             }
  123.             $newUnits null;
  124.             if ($accomodation) {
  125.                 foreach ($accomodation['Units'] as $unit) {
  126.                     $unit['gnid'] = $unit['Description'].$unit['Type'].$unit['Boards'][0]['Name'];
  127.                     $newprices null;
  128.                     foreach ($unit['Prices'] as $price) {
  129.                         $price['Price']['OrgValue'] = $price['Price']['Value'];
  130.                         $price['Price']['Value'] = ceil($price['Price']['Value'] / 100) * 100;
  131.                         $newprices[] = $price;
  132.                     }
  133.                     $unit['Prices'] = $newprices;
  134.                     $newUnits[] = $unit;
  135.                 }
  136.                 $accomodation['Units'] = $newUnits;
  137.             }
  138.         }
  139.         $departAirports $this->getDoctrine()->getRepository(Airport::class)
  140.             ->findBy([
  141.                 'indexable_departure' => 1,
  142.             ], [
  143.                 'airportCountryCode' => 'desc''airportName' => 'ASC',
  144.             ]);
  145.         $arriveAirportsFetch $this->pyton->getArriveAirports($match);
  146.         $arriveAirports = [];
  147.         foreach ($arriveAirportsFetch as $airportId => $airport) {
  148.             if ($this->isWhitelistedAirport($airportId)) {
  149.                 $arriveAirports[$airportId] = $airport;
  150.             }
  151.         }
  152.         $airports $this->getDoctrine()->getRepository(Airport::class)
  153.             ->findBy([
  154.                 'airportId' => array_keys($arriveAirports),
  155.             ], [
  156.                 'default_arrival' => 'desc',
  157.             ]);
  158.         foreach ($airports as $airport) {
  159.             $arriveAirports[$airport->getAirportId()] = ['name' => $airport->getAirportName(
  160.             ), 'code' => $airport->getAirportCode()];
  161.         }
  162.         return $this->pageRenderer
  163.             ->renderPage(
  164.                 '{checkout}',
  165.                 $this->decorator->getTemplate('pages/checkout_arrangement.html.twig'),
  166.                 [
  167.                     'departDates' => $this->checkoutService->getDepartDates($match),
  168.                     'returnDates' => $this->checkoutService->getReturnDates($match),
  169.                     'match' => $match,
  170.                     'airports' => $departAirports,
  171.                     'arriveAirports' => $arriveAirports,
  172.                     'basket' => $basket,
  173.                     'accommodation' => $accomodation,
  174.                     'accommodationReceipt' => $accomodationReceipt,
  175.                     'chosenUnits' => $chosenUnits,
  176.                 ]
  177.             );
  178.     }
  179.     /**
  180.      * @throws UnknownCurrencyException
  181.      * @throws Twig_Error_Loader
  182.      * @throws Twig_Error_Runtime
  183.      * @throws Twig_Error_Syntax
  184.      */
  185.     private function checkoutStep2(Request $requestGame $match): Response
  186.     {
  187.         $basket $this->basketService->getBasket($match);
  188.         $accomodation $basket->getPytonBasket()?->getAccomodation();
  189.         if (!is_string($accomodation)) {
  190.             $this->addFlash('danger''Kies eerst een Hotel');
  191.             return $this->redirectToRoute('checkout', ['step' => 1'id' => $match->getId()]);
  192.         }
  193.         if(!$basket->getStadiumCategory() || empty($basket->getStadiumCategory())){
  194.             $this->addFlash('danger''Kies aub eerst een stadion categorie.');
  195.             return $this->redirectToRoute('checkout', ['step' => 1'id' => $match->getId()]);
  196.         }
  197.         $builder $this->createFormBuilder($basket->getTravelersData(), [
  198.             'attr' => ['data-component' => 'VoetbalRetourBooking'],
  199.         ]);
  200.         $builder->add('mainBooker'MainBookerType::class);
  201.         $subBuilder $this->get('form.factory')->createNamedBuilder('persons');
  202.         for ($i 1$i <= ($basket->getAdults() + $basket->getChildren()); ++$i) {
  203.             $subBuilder->add('person_'.$iBookerType::class);
  204.         }
  205.         $builder->add($subBuilder);
  206.         $builder->add('mainBookerHome'MainBookerHomeType::class);
  207.         $form $builder->getForm();
  208.         $form->handleRequest($request);
  209.         if ($form->isSubmitted()) {
  210.             if ($form->isValid()) {
  211.                 $basket->setTravelersData($form->getData());
  212.                 $this->basketService->setBasket($match$basket);
  213.                 $this->pyton->updatePassengers($basket);
  214.                 return $this->redirectToRoute('checkout', ['id' => $match->getId(), 'step' => 3]);
  215.             }
  216.         }
  217.         return $this->pageRenderer
  218.             ->renderPage(
  219.                 '{checkout}',
  220.                 $this->decorator->getTemplate('pages/checkout_traveler_data.html.twig'),
  221.                 [
  222.                     'match' => $match,
  223.                     'basket' => $basket,
  224.                     'accommodation' => $accomodation,
  225.                     'form' => $form->createView(),
  226.                 ]
  227.             );
  228.     }
  229.     /**
  230.      * @throws Twig_Error_Loader
  231.      * @throws Twig_Error_Runtime
  232.      * @throws Twig_Error_Syntax
  233.      * @throws UnknownCurrencyException
  234.      */
  235.     private function checkoutStep3(Game $matchRequest $request): Response
  236.     {
  237.         $basket $this->basketService->getBasket($match);
  238.         if (
  239.             $request->get('couponcode') &&
  240.             !$basket->getDiscountReceipt() &&
  241.             $basket->getDiscountTotal()->getAmount() == 0
  242.         ) {
  243.             if ($this->pyton->applyDiscount($basket$request->get('couponcode'), $match)) {
  244.                 $this->addFlash('coupon:success''Gelukt! Je kortingscode %code is succesvol toegevoegd');
  245.             } else {
  246.                 $this->addFlash(
  247.                     'coupon:error',
  248.                     'Mislukt! Je opgegeven kortingscode is niet geldig en kon niet toegevoegd worden'
  249.                 );
  250.             }
  251.         }
  252.         $stadiumCategory null;
  253.         if ($basket->getStadiumCategory()) {
  254.             $stadiumCategory $this->getDoctrine()
  255.                 ->getRepository(StadiumCategory::class)->find($basket->getStadiumCategory());
  256.         }
  257.         $flightData = [];
  258.         if ($basket->getFlight()) {
  259.             foreach ($basket->getFlightListNormalized() as $flight) {
  260.                 if ($flight['tripId'] === $basket->getFlight()) {
  261.                     $flightData $flight;
  262.                     break;
  263.                 }
  264.             }
  265.         }
  266.         $template 'pages/checkout_payment_details.html.twig';
  267.         return $this->pageRenderer
  268.             ->renderPage(
  269.                 '{checkout}',
  270.                 $this->decorator->getTemplate($template),
  271.                 [
  272.                     'extraCosts' => $this->getDoctrine()->getRepository(VRExtraCosts::class)->findAll(),
  273.                     'match' => $match,
  274.                     'basket' => $basket,
  275.                     'ticketCount' => $this->basketService->getTicketCount($match),
  276.                     'accommodation' => $this->basketService->getSelectedAccommodation($basket),
  277.                     'stadiumCategory' => $stadiumCategory,
  278.                     'flightData' => $flightData,
  279.                     'isPassedMinimumDepositPeriod' => $this->isPassedMinimumDepositPeriod($basket),
  280.                     'depositPercentage' => $this->getParameter('site_booking_minimum_deposit_percentage'),
  281.                     'minimumDepositPeriod' => self::PAYMENT_DEPOSIT_TRESHOLD,
  282.                     'paymentGatewayTestMode' => $this->getParameter('payment_gateway_testmode'),
  283.                     'couponCode' => $request->get('couponcode'),
  284.                     'chosenaccommodation' => $this->basketService->getSelectedAccommodationRooms($basket),
  285.                     'client' => $this->pytonClient,
  286.                     'e_commerce_script' => $this->gameService->renderECommerceScript($match$basket),
  287.                 ]
  288.             );
  289.     }
  290.     /**
  291.      * @param bool $pay100 |null
  292.      *
  293.      * @return float|int
  294.      */
  295.     protected function calculateMinimumDeposit(Basket $basket, ?bool $pay100)
  296.     {
  297.         if ($pay100) {
  298.             $minimumDeposit = (int) $basket->getPrice()->getAmount();
  299.         } else {
  300.             $minimumDeposit = (int) $basket->getPrice()->getAmount() * ($this->getParameter(
  301.                         'site_booking_minimum_deposit_percentage'
  302.                     ) / 100);
  303.         }
  304.         if (!$this->isPassedMinimumDepositPeriod($basket)) {
  305.             $minimumDeposit = (int) $basket->getPrice()->getAmount();
  306.         }
  307.         // Add the insurance amount on top of the minimum amount
  308.         $minimumDeposit += (int) $basket->getCalculatedExtraPrices()->getAmount();
  309.         if ($discountTotal $basket->getDiscountTotal()) {
  310.             $minimumDeposit -= (int) $basket->getDiscountTotal()->getAmount();
  311.         }
  312.         return number_format($minimumDeposit 1002'.''');
  313.     }
  314.     /**
  315.      * @return bool
  316.      */
  317.     protected function isPassedMinimumDepositPeriod(Basket $basket)
  318.     {
  319.         $currentDate = new DateTime();
  320.         $interval date_diff($currentDate$basket->getDepartDate())->days;
  321.         if ($interval self::PAYMENT_DEPOSIT_TRESHOLD) {
  322.             return true;
  323.         }
  324.         return false;
  325.     }
  326.     /**
  327.      * @return void
  328.      */
  329.     private function checkoutStep4(Request $requestGame $match)
  330.     {
  331.         $paymentMethod mb_strtolower($request->get('payment_method''ideal'));
  332.         $this->session->set(self::PIGGY_PARTICIPATE_KEY$request->get(self::PIGGY_PARTICIPATE_KEY));
  333.         $this->session->set(self::PIGGY_REFERRAL_KEY$request->get(self::PIGGY_REFERRAL_KEY));
  334.         $basket $this->basketService->getBasket($match);
  335.         $pay100 == $request->get('pay100');
  336.         $minimumDeposit $this->calculateMinimumDeposit($basket$pay100);
  337.         $gateway \Omnipay\Omnipay::create('Mollie'null$request);
  338.         /* @var Gateway $gateway */
  339.         $gateway->setApiKey($this->getParameter('payment_gateway_api_key'));
  340.         $response $gateway
  341.             ->setTestMode((bool) $this->getParameter('payment_gateway_testmode'))
  342.             ->purchase(
  343.                 [
  344.                     'paymentMethod' => $paymentMethod,
  345.                     'amount' => $minimumDeposit,
  346.                     'currency' => $basket->getPrice()->getCurrency(),
  347.                     'description' => 'Boeking '.$match->getHomeClub()->getName().' vs. '.$match->getAwayClub()->getName(
  348.                         ),
  349.                     'returnUrl' => $this->get('router')->generate(
  350.                         'checkout',
  351.                         ['id' => $match->getId(), 'step' => 5],
  352.                         UrlGeneratorInterface::ABSOLUTE_URL
  353.                     ),
  354.                 ]
  355.             )->send();
  356.         $this->session->set('transactionId'$response->getTransactionReference());
  357.         $response->redirect();
  358.     }
  359.     /**
  360.      * @throws Twig_Error_Loader
  361.      * @throws Twig_Error_Runtime
  362.      * @throws Twig_Error_Syntax
  363.      */
  364.     private function checkoutStep5(Request $requestGame $matchint $paidAmount 0): Response
  365.     {
  366.         $basket $this->basketService->getBasket($match);
  367.         $gateway \Omnipay\Omnipay::create('Mollie'null$request);
  368.         /* @var Gateway $gateway */
  369.         $gateway->setApiKey($this->getParameter('payment_gateway_api_key'));
  370.         $returnUrl $this->get('router')->generate(
  371.             'checkout',
  372.             ['id' => $match->getId()],
  373.             UrlGeneratorInterface::ABSOLUTE_URL
  374.         );
  375.         try {
  376.             $response $gateway
  377.                 ->setTestMode((bool) $this->getParameter('payment_gateway_testmode'))
  378.                 ->completePurchase(
  379.                     [
  380.                         'currency' => 'EUR',
  381.                         'description' => 'Betaling Voetbalretour',
  382.                         'returnUrl' => $returnUrl,
  383.                         'transactionReference' => $this->session->get('transactionId'),
  384.                     ]
  385.                 )->send();
  386.         } catch (Exception $e){
  387.             $this->addFlash(
  388.                 'mollie:error',
  389.                 'Error! Mollie transactie ID niet gevonden. Neem svp contact op met Voetbal Retour via info@voetbalretour.nl of +31 (0) 475 - 33 10 39. '
  390.             );
  391.             return $this->redirectToRoute('checkout', ['step' => 3'id' => $match->getId()]);
  392.         }
  393.         if ($response->isSuccessful()) {
  394.             $booking null;
  395.             $template 'pages/checkout_payment_failed.html.twig';
  396.             $user $this->basketService->createUser($match);
  397.             // TODO: Pyton post request doesn't work, remove 'true || ' when pyton works again
  398.             // One or more of the provided arguments are invalid. 400 Bad Request
  399.             if ($this->pyton->book($basket)) {
  400.                 $booking $this->basketService->saveBasket($match$user$response->getData()['amount']['value']);
  401.                 // TODO: Pdf related error after saveBasket
  402.                 $this->eventDispatcher->dispatch(new SubmitBookingEvent($booking$booking->getInvoices()->first()), 'booking.form.submitted');
  403.                 try {
  404.                     $this->service->savePayment($booking, (float) $response->getData()['amount']['value']);
  405.                 }
  406.                 catch (Exception $e){
  407.                     //doe voor nu even niets.
  408.                 }
  409.                 
  410.                 return $this->redirectToRoute(
  411.                     'booking_apply_thanks',
  412.                     [
  413.                         'booking_id' => $booking->getId(),
  414.                     ]
  415.                 );
  416.                 //return $this->redirectToRoute('boeking_bedankt', ['booking_id' => $booking->getId()]);
  417.             }
  418.             return $this->pageRenderer
  419.                 ->renderPage(
  420.                     '{checkout}',
  421.                     $this->decorator->getTemplate($template),
  422.                     [
  423.                         'user' => $user,
  424.                         'match' => $match,
  425.                         'basket' => $basket,
  426.                         'booking' => $booking,
  427.                         'ticketCount' => $this->basketService->getTicketCount($match),
  428.                         'accommodation' => $this->basketService->getSelectedAccommodation($basket),
  429.                         'trip' => $this->basketService->getSelectedTrip($basket)
  430.                     ]
  431.                 );
  432.         } elseif ($response->isCancelled()) {
  433.             //  dump('Cancelled payment');
  434.             return $this->redirectToRoute('checkout', ['step' => 3'id' => $match->getId()]);
  435.         }
  436.         else {
  437.             $this->addFlash('ERROR''Er ging iets mis tijdens het boeken, boeking is niet succesvol opgeslagen. Neem contact op met VoetbalRetour middels info@voetbalretour.nl of +31 (0) 475 - 33 10 39');
  438.             return $this->redirectToRoute('checkout', ['step' => 3'id' => $match->getId()]);
  439.         }
  440.         // Payment failed
  441.         echo $response->getMessage();
  442.         exit;
  443.     }
  444.     public function test()
  445.     {
  446.         $this->basketService->test();
  447.     }
  448.     /**
  449.      * @param $airportId
  450.      */
  451.     private function isWhitelistedAirport($airportId): bool
  452.     {
  453.         $arriveAirportsWhitelist $this->getDoctrine()->getRepository(Airport::class)->getArrivalsWhitelist();
  454.         return \in_array($airportId$arriveAirportsWhitelisttrue);
  455.     }
  456.     /**
  457.      * @throws LoaderError
  458.      * @throws RuntimeError
  459.      * @throws SyntaxError
  460.      */
  461. //    #[Route(path: '/thanks', name: 'booking_apply_thanks', options: ['expose' => true])]
  462.     public function thanksAction(
  463.         Request $request,
  464.     ): Response {
  465.         $bookingRepo $this->getDoctrine()->getRepository(Booking::class);
  466.         $booking null;
  467.         if ($id $request->get('booking_id')) {
  468.             $booking $bookingRepo->find($id);
  469.         }
  470.         return $this->pageRenderer
  471.             ->renderPage(
  472.                 '{booking_apply_thanks}',
  473.                 '@default/pages/booking_apply_thanks.html.twig',
  474.                 [
  475.                     'booking' => $booking,
  476.                     'e_commerce_script' => $this->service->renderECommerceScript($request$booking),
  477.                 ]
  478.             );
  479.     }
  480. }