<?php
namespace App\Controller\Page;
use App\Entity\Airport;
use App\Entity\Booking;
use App\Entity\Game;
use App\Entity\StadiumCategory;
use App\Entity\VRExtraCosts;
use App\Event\BookingCompletedEvent;
use App\Event\SubmitBookingEvent;
use App\Form\BookerType;
use App\Form\MainBookerHomeType;
use App\Form\MainBookerType;
use App\Model\Basket;
use App\Renderer\Page;
use App\Service\Basket as BasketService;
use App\Service\BookingService;
use App\Service\CheckoutService;
use App\Service\ExtraCostsService;
use App\Service\Pyton;
use App\Templating\Decorator;
use DateTime;
use Exception;
use Flagception\Bundle\FlagceptionBundle\Annotations\Feature;
use GuzzleHttp\Client;
use Money\Exception\UnknownCurrencyException;
use Omnipay\Mollie\Gateway;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use App\Model\Pyton\Basket as PytonBasket;
use App\Service\GameService as GameService;
/**
* @Feature("feature_voetbalretour")
*/
class CheckoutController extends AbstractController
{
public const PAYMENT_DEPOSIT_TRESHOLD = 42;
public const PIGGY_PARTICIPATE_KEY = 'piggy_participate';
public const PIGGY_REFERRAL_KEY = 'piggy_referral';
public function __construct(
protected readonly Page $pageRenderer,
protected readonly Decorator $decorator,
protected readonly BasketService $basketService,
protected readonly Pyton $pyton,
protected readonly SessionInterface $session,
protected readonly CheckoutService $checkoutService,
protected readonly BookingService $service,
protected readonly Client $pytonClient,
protected readonly EventDispatcherInterface $eventDispatcher,
protected readonly ExtraCostsService $extraCostsService,
protected readonly GameService $gameService,
) {
setlocale(\LC_ALL, 'nl_NL');
}
/**
* @throws Twig_Error_Loader
* @throws Twig_Error_Runtime
* @throws Twig_Error_Syntax
* @throws UnknownCurrencyException
*/
public function checkout(Request $request, Game $match): Response
{
if($match->isDisableBooking()){
return $this->redirectToRoute('wedstrijden');
}
switch ($request->get('step', 1)) {
case 1:
default:
$response = $this->checkoutStep1($match);
break;
case 2:
$response = $this->checkoutStep2($request, $match);
break;
case 3:
$response = $this->checkoutStep3($match, $request);
break;
case 4:
$response = $this->checkoutStep4($request, $match);
break;
case 5:
$response = $this->checkoutStep5($request, $match);
break;
case 'test':
$response = $this->test();
break;
}
$response->setSharedMaxAge(0);
return $response;
}
/**
* @throws Twig_Error_Loader
* @throws Twig_Error_Runtime
* @throws Twig_Error_Syntax
* @throws Exception
*
* @return Response
*/
private function checkoutStep1(Game $match)
{
$basket = $this->basketService->getBasket($match);
$accomodation = null;
$chosenUnits = null;
$accomodationReceipt = null;
if ($basket->getPytonBasket() instanceof PytonBasket) {
$accomodation = json_decode($basket->getPytonBasket()->getAccomodation(), true);
$accomodationReceipt = json_decode($basket->getPytonBasket()->getAccommodationReceipt(), true);
if ($accomodationReceipt) {
$t = 1;
foreach ($accomodationReceipt['Receipt']['Accommodation']['Units'] as $unit) {
for ($i = 1; $i <= $unit['Allotment']['Chosen']; ++$i) {
$chosenUnits[$t] = [
'roomtype' => $unit['Description'].$unit['Type'].$unit['Boards'][0]['Name'],
'numberpersons' => $unit['Occupancy']['Maximal'],
'original' => $match->getCheapestAccommodation()->getAmount(),
];
++$t;
}
}
}
$newUnits = null;
if ($accomodation) {
foreach ($accomodation['Units'] as $unit) {
$unit['gnid'] = $unit['Description'].$unit['Type'].$unit['Boards'][0]['Name'];
$newprices = null;
foreach ($unit['Prices'] as $price) {
$price['Price']['OrgValue'] = $price['Price']['Value'];
$price['Price']['Value'] = ceil($price['Price']['Value'] / 100) * 100;
$newprices[] = $price;
}
$unit['Prices'] = $newprices;
$newUnits[] = $unit;
}
$accomodation['Units'] = $newUnits;
}
}
$departAirports = $this->getDoctrine()->getRepository(Airport::class)
->findBy([
'indexable_departure' => 1,
], [
'airportCountryCode' => 'desc', 'airportName' => 'ASC',
]);
$arriveAirportsFetch = $this->pyton->getArriveAirports($match);
$arriveAirports = [];
foreach ($arriveAirportsFetch as $airportId => $airport) {
if ($this->isWhitelistedAirport($airportId)) {
$arriveAirports[$airportId] = $airport;
}
}
$airports = $this->getDoctrine()->getRepository(Airport::class)
->findBy([
'airportId' => array_keys($arriveAirports),
], [
'default_arrival' => 'desc',
]);
foreach ($airports as $airport) {
$arriveAirports[$airport->getAirportId()] = ['name' => $airport->getAirportName(
), 'code' => $airport->getAirportCode()];
}
return $this->pageRenderer
->renderPage(
'{checkout}',
$this->decorator->getTemplate('pages/checkout_arrangement.html.twig'),
[
'departDates' => $this->checkoutService->getDepartDates($match),
'returnDates' => $this->checkoutService->getReturnDates($match),
'match' => $match,
'airports' => $departAirports,
'arriveAirports' => $arriveAirports,
'basket' => $basket,
'accommodation' => $accomodation,
'accommodationReceipt' => $accomodationReceipt,
'chosenUnits' => $chosenUnits,
]
);
}
/**
* @throws UnknownCurrencyException
* @throws Twig_Error_Loader
* @throws Twig_Error_Runtime
* @throws Twig_Error_Syntax
*/
private function checkoutStep2(Request $request, Game $match): Response
{
$basket = $this->basketService->getBasket($match);
$accomodation = $basket->getPytonBasket()?->getAccomodation();
if (!is_string($accomodation)) {
$this->addFlash('danger', 'Kies eerst een Hotel');
return $this->redirectToRoute('checkout', ['step' => 1, 'id' => $match->getId()]);
}
if(!$basket->getStadiumCategory() || empty($basket->getStadiumCategory())){
$this->addFlash('danger', 'Kies aub eerst een stadion categorie.');
return $this->redirectToRoute('checkout', ['step' => 1, 'id' => $match->getId()]);
}
$builder = $this->createFormBuilder($basket->getTravelersData(), [
'attr' => ['data-component' => 'VoetbalRetourBooking'],
]);
$builder->add('mainBooker', MainBookerType::class);
$subBuilder = $this->get('form.factory')->createNamedBuilder('persons');
for ($i = 1; $i <= ($basket->getAdults() + $basket->getChildren()); ++$i) {
$subBuilder->add('person_'.$i, BookerType::class);
}
$builder->add($subBuilder);
$builder->add('mainBookerHome', MainBookerHomeType::class);
$form = $builder->getForm();
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$basket->setTravelersData($form->getData());
$this->basketService->setBasket($match, $basket);
$this->pyton->updatePassengers($basket);
return $this->redirectToRoute('checkout', ['id' => $match->getId(), 'step' => 3]);
}
}
return $this->pageRenderer
->renderPage(
'{checkout}',
$this->decorator->getTemplate('pages/checkout_traveler_data.html.twig'),
[
'match' => $match,
'basket' => $basket,
'accommodation' => $accomodation,
'form' => $form->createView(),
]
);
}
/**
* @throws Twig_Error_Loader
* @throws Twig_Error_Runtime
* @throws Twig_Error_Syntax
* @throws UnknownCurrencyException
*/
private function checkoutStep3(Game $match, Request $request): Response
{
$basket = $this->basketService->getBasket($match);
if (
$request->get('couponcode') &&
!$basket->getDiscountReceipt() &&
$basket->getDiscountTotal()->getAmount() == 0
) {
if ($this->pyton->applyDiscount($basket, $request->get('couponcode'), $match)) {
$this->addFlash('coupon:success', 'Gelukt! Je kortingscode %code is succesvol toegevoegd');
} else {
$this->addFlash(
'coupon:error',
'Mislukt! Je opgegeven kortingscode is niet geldig en kon niet toegevoegd worden'
);
}
}
$stadiumCategory = null;
if ($basket->getStadiumCategory()) {
$stadiumCategory = $this->getDoctrine()
->getRepository(StadiumCategory::class)->find($basket->getStadiumCategory());
}
$flightData = [];
if ($basket->getFlight()) {
foreach ($basket->getFlightListNormalized() as $flight) {
if ($flight['tripId'] === $basket->getFlight()) {
$flightData = $flight;
break;
}
}
}
$template = 'pages/checkout_payment_details.html.twig';
return $this->pageRenderer
->renderPage(
'{checkout}',
$this->decorator->getTemplate($template),
[
'extraCosts' => $this->getDoctrine()->getRepository(VRExtraCosts::class)->findAll(),
'match' => $match,
'basket' => $basket,
'ticketCount' => $this->basketService->getTicketCount($match),
'accommodation' => $this->basketService->getSelectedAccommodation($basket),
'stadiumCategory' => $stadiumCategory,
'flightData' => $flightData,
'isPassedMinimumDepositPeriod' => $this->isPassedMinimumDepositPeriod($basket),
'depositPercentage' => $this->getParameter('site_booking_minimum_deposit_percentage'),
'minimumDepositPeriod' => self::PAYMENT_DEPOSIT_TRESHOLD,
'paymentGatewayTestMode' => $this->getParameter('payment_gateway_testmode'),
'couponCode' => $request->get('couponcode'),
'chosenaccommodation' => $this->basketService->getSelectedAccommodationRooms($basket),
'client' => $this->pytonClient,
'e_commerce_script' => $this->gameService->renderECommerceScript($match, $basket),
]
);
}
/**
* @param bool $pay100 |null
*
* @return float|int
*/
protected function calculateMinimumDeposit(Basket $basket, ?bool $pay100)
{
if ($pay100) {
$minimumDeposit = (int) $basket->getPrice()->getAmount();
} else {
$minimumDeposit = (int) $basket->getPrice()->getAmount() * ($this->getParameter(
'site_booking_minimum_deposit_percentage'
) / 100);
}
if (!$this->isPassedMinimumDepositPeriod($basket)) {
$minimumDeposit = (int) $basket->getPrice()->getAmount();
}
// Add the insurance amount on top of the minimum amount
$minimumDeposit += (int) $basket->getCalculatedExtraPrices()->getAmount();
if ($discountTotal = $basket->getDiscountTotal()) {
$minimumDeposit -= (int) $basket->getDiscountTotal()->getAmount();
}
return number_format($minimumDeposit / 100, 2, '.', '');
}
/**
* @return bool
*/
protected function isPassedMinimumDepositPeriod(Basket $basket)
{
$currentDate = new DateTime();
$interval = date_diff($currentDate, $basket->getDepartDate())->days;
if ($interval > self::PAYMENT_DEPOSIT_TRESHOLD) {
return true;
}
return false;
}
/**
* @return void
*/
private function checkoutStep4(Request $request, Game $match)
{
$paymentMethod = mb_strtolower($request->get('payment_method', 'ideal'));
$this->session->set(self::PIGGY_PARTICIPATE_KEY, $request->get(self::PIGGY_PARTICIPATE_KEY));
$this->session->set(self::PIGGY_REFERRAL_KEY, $request->get(self::PIGGY_REFERRAL_KEY));
$basket = $this->basketService->getBasket($match);
$pay100 = 1 == $request->get('pay100');
$minimumDeposit = $this->calculateMinimumDeposit($basket, $pay100);
$gateway = \Omnipay\Omnipay::create('Mollie', null, $request);
/* @var Gateway $gateway */
$gateway->setApiKey($this->getParameter('payment_gateway_api_key'));
$response = $gateway
->setTestMode((bool) $this->getParameter('payment_gateway_testmode'))
->purchase(
[
'paymentMethod' => $paymentMethod,
'amount' => $minimumDeposit,
'currency' => $basket->getPrice()->getCurrency(),
'description' => 'Boeking '.$match->getHomeClub()->getName().' vs. '.$match->getAwayClub()->getName(
),
'returnUrl' => $this->get('router')->generate(
'checkout',
['id' => $match->getId(), 'step' => 5],
UrlGeneratorInterface::ABSOLUTE_URL
),
]
)->send();
$this->session->set('transactionId', $response->getTransactionReference());
$response->redirect();
}
/**
* @throws Twig_Error_Loader
* @throws Twig_Error_Runtime
* @throws Twig_Error_Syntax
*/
private function checkoutStep5(Request $request, Game $match, int $paidAmount = 0): Response
{
$basket = $this->basketService->getBasket($match);
$gateway = \Omnipay\Omnipay::create('Mollie', null, $request);
/* @var Gateway $gateway */
$gateway->setApiKey($this->getParameter('payment_gateway_api_key'));
$returnUrl = $this->get('router')->generate(
'checkout',
['id' => $match->getId()],
UrlGeneratorInterface::ABSOLUTE_URL
);
try {
$response = $gateway
->setTestMode((bool) $this->getParameter('payment_gateway_testmode'))
->completePurchase(
[
'currency' => 'EUR',
'description' => 'Betaling Voetbalretour',
'returnUrl' => $returnUrl,
'transactionReference' => $this->session->get('transactionId'),
]
)->send();
} catch (Exception $e){
$this->addFlash(
'mollie:error',
'Error! Mollie transactie ID niet gevonden. Neem svp contact op met Voetbal Retour via info@voetbalretour.nl of +31 (0) 475 - 33 10 39. '
);
return $this->redirectToRoute('checkout', ['step' => 3, 'id' => $match->getId()]);
}
if ($response->isSuccessful()) {
$booking = null;
$template = 'pages/checkout_payment_failed.html.twig';
$user = $this->basketService->createUser($match);
// TODO: Pyton post request doesn't work, remove 'true || ' when pyton works again
// One or more of the provided arguments are invalid. 400 Bad Request
if ($this->pyton->book($basket)) {
$booking = $this->basketService->saveBasket($match, $user, $response->getData()['amount']['value']);
// TODO: Pdf related error after saveBasket
$this->eventDispatcher->dispatch(new SubmitBookingEvent($booking, $booking->getInvoices()->first()), 'booking.form.submitted');
try {
$this->service->savePayment($booking, (float) $response->getData()['amount']['value']);
}
catch (Exception $e){
//doe voor nu even niets.
}
return $this->redirectToRoute(
'booking_apply_thanks',
[
'booking_id' => $booking->getId(),
]
);
//return $this->redirectToRoute('boeking_bedankt', ['booking_id' => $booking->getId()]);
}
return $this->pageRenderer
->renderPage(
'{checkout}',
$this->decorator->getTemplate($template),
[
'user' => $user,
'match' => $match,
'basket' => $basket,
'booking' => $booking,
'ticketCount' => $this->basketService->getTicketCount($match),
'accommodation' => $this->basketService->getSelectedAccommodation($basket),
'trip' => $this->basketService->getSelectedTrip($basket)
]
);
} elseif ($response->isCancelled()) {
// dump('Cancelled payment');
return $this->redirectToRoute('checkout', ['step' => 3, 'id' => $match->getId()]);
}
else {
$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');
return $this->redirectToRoute('checkout', ['step' => 3, 'id' => $match->getId()]);
}
// Payment failed
echo $response->getMessage();
exit;
}
public function test()
{
$this->basketService->test();
}
/**
* @param $airportId
*/
private function isWhitelistedAirport($airportId): bool
{
$arriveAirportsWhitelist = $this->getDoctrine()->getRepository(Airport::class)->getArrivalsWhitelist();
return \in_array($airportId, $arriveAirportsWhitelist, true);
}
/**
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
// #[Route(path: '/thanks', name: 'booking_apply_thanks', options: ['expose' => true])]
public function thanksAction(
Request $request,
): Response {
$bookingRepo = $this->getDoctrine()->getRepository(Booking::class);
$booking = null;
if ($id = $request->get('booking_id')) {
$booking = $bookingRepo->find($id);
}
return $this->pageRenderer
->renderPage(
'{booking_apply_thanks}',
'@default/pages/booking_apply_thanks.html.twig',
[
'booking' => $booking,
'e_commerce_script' => $this->service->renderECommerceScript($request, $booking),
]
);
}
}