<?php
namespace App\Controller;
use App\Exception\InvalidCustomAddressInformation;
use App\Form\CartFormType;
use App\Model\Collection\ProductArticleCollection;
use App\Model\View\CartItem;
use App\Services\CartService;
use App\Services\ConfigService;
use App\Services\CouponService;
use App\Services\Factory\OrderFactory;
use App\Services\GeneralSendMailService;
use App\Services\MollieService;
use App\Services\ReCaptchaService;
use Carbon\Carbon;
use Exception;
use Mollie\Api\Resources\Payment;
use Pimcore\Log\ApplicationLogger;
use Pimcore\Log\Simple;
use Pimcore\Model\DataObject\Fieldcollection\Data\Coupon as CouponF;
use Pimcore\Model\DataObject\Coupon;
use Pimcore\Model\DataObject\Order;
use Pimcore\Model\DataObject\Order\Listing;
use Pimcore\Model\DataObject\ProductArticle;
use Pimcore\Model\DataObject\UserPortal;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;
class CartController extends BaseController
{
protected string $recaptchaVersion;
protected string $recaptchaPublicKey;
protected ReCaptchaService $recaptcha;
private CartService $cartService;
private OrderFactory $orderFactory;
private MollieService $mollieService;
private LoggerInterface $logger;
private CouponService $couponService;
protected GeneralSendMailService $generalSendMailService;
public function __construct(
Environment $twig,
RequestStack $requestStack,
ConfigService $config,
OrderFactory $orderFactory,
GeneralSendMailService $generalSendMailService,
ReCaptchaService $recaptcha,
CartService $cartService,
MollieService $mollieService,
LoggerInterface $logger,
CouponService $couponService
) {
parent::__construct($twig, $requestStack, $config);
$this->recaptcha = $recaptcha;
$this->recaptchaVersion = $recaptcha->getVersion();
$this->recaptchaPublicKey = $recaptcha->getPublicKey();
$this->generalSendMailService = $generalSendMailService;
$this->cartService = $cartService;
$this->mollieService = $mollieService;
$this->orderFactory = $orderFactory;
$this->logger = $logger;
$this->couponService = $couponService;
}
/**
* View cart page
*
* @Route("{_locale}/cart", name="cart-page")
*
* @param Request $request
* @return Response
*/
public function indexAction(Request $request): Response
{
$session = $request->getSession();
$user = $this->cartService->getUserFromSession($session);
$cartItems = $this->cartService->getCartFromSession($session);
$cartItemsViewModels = [];
foreach ($cartItems as $cartItem) {
$cartItemsViewModels[] = new CartItem($cartItem);
}
if (empty($cartItems)) {
return $this->render('cart/index.html.twig', [
'user' => $user
]);
}
$sessionProductArticleCollection = $this->cartService->getProductArticleCollectionFromSession($session);
$cartTotal = $sessionProductArticleCollection ? $sessionProductArticleCollection->getTotalValue() : 0;
return $this->render('cart/index.html.twig', [
'cartItems' => $cartItemsViewModels,
'cartTotal' => CartService::formatPrice($cartTotal),
'user' => $user,
'amountIsEditable' => true
]);
}
/**
* Update cart page
*
* @Route("{_locale}/cart/update", name="cartupdate-page")
*
* @param Request $request
* @return JsonResponse
* @throws Exception
*/
public function cartUpdateAction(Request $request): JsonResponse
{
$session = $request->getSession();
$sessionCart = $this->cartService->getProductArticleCollectionFromSession($session);
$productId = $request->get('productId');
$amount = $request->get('amount');
$actionWeb = $request->get('action');
if ($request->isXmlHttpRequest()) {
if (!$sessionCart instanceof ProductArticleCollection) {
$sessionCart = new ProductArticleCollection();
$session->set(CartService::$sessionCartIdentifier, $sessionCart);
}
$productArticle = ProductArticle::getById($productId);
if (!$productArticle instanceof ProductArticle) {
return $this->returnJsonErrorToCart(sprintf('Could not find article with id %s', $productId));
}
switch ($actionWeb) {
case 'add':
$sessionCart->addProductArticle($productArticle, $amount);
break;
case 'update':
$sessionCart->updateProductArticleCount($productArticle, $amount);
break;
case 'remove':
$sessionCart->removeProductArticle($productArticle);
break;
}
$cartCount = $sessionCart->getCartCount();
$cartValue = CartService::formatPrice($sessionCart->getTotalValue());
$totalProductValue = CartService::formatPrice($sessionCart->getTotalProductValue($productArticle));
return $this->returnJsonSuccess($actionWeb, $productArticle, $amount, $cartCount, $cartValue, $totalProductValue);
}
return $this->returnJsonErrorToCart();
}
/**
* empty cart
*
* @Route("{_locale}/cart/clear", name="cart-clear")
*
* @param Request $request
* @return Response
*/
public function emptyCartAction(Request $request): Response
{
$session = $request->getSession();
$oldSessionCart = $this->cartService->getProductArticleCollectionFromSession($session);
if ($oldSessionCart instanceof ProductArticleCollection) {
$oldOrder = $oldSessionCart->getOrder();
if ($oldOrder instanceof Order) {
try {
$oldOrder->delete();
} catch (Exception $e) {
$this->logger->error(sprintf('empty cart action threw exception: %s', $e->getMessage()));
}
}
}
$session->set(CartService::$sessionCartIdentifier,new ProductArticleCollection());
return new JsonResponse(['message' => 'cleared'], Response::HTTP_OK);
}
/**
* empty cart
*
* @Route("{_locale}/cart/clear-coupon", name="cart-clear-coupon")
*
* @param Request $request
* @return Response
*/
public function emptyCouponAction(Request $request): Response
{
$session = $request->getSession();
$oldSessionCart = $this->cartService->getProductArticleCollectionFromSession($session);
if ($oldSessionCart instanceof ProductArticleCollection) {
$order = $oldSessionCart->getOrder();
if ($order instanceof Order) {
try {
$order->setCode('');
$order->setPercentage(0);
$order->setOldprice(0);
$order->setNewprice(0);
$order->setOrderTotal(CartService::formatPrice($order->getCartTotal() + $order->getShippingCost()));
$order->setCoupon(false);
$order->save();
} catch (Exception $e) {
$this->logger->error(sprintf('empty coupon action threw exception: %s', $e->getMessage()));
}
}
}
return new JsonResponse(['message' => 'cleared'], Response::HTTP_OK);
}
/**
* Display form for delivery information + create order + link order to cart
*
* @Route("{_locale}/cart/delivery", name="cart-delivery")
*
* @param Request $request
* @return Response
* @throws Exception
*/
public function deliveryAction(Request $request): Response
{
$session = $request->getSession();
$user = $this->cartService->getUserFromSession($session);
$cart = $this->cartService->getProductArticleCollectionFromSession($session);
//no product articlecollection means there is no cart, redirect to cart page
//or when cart is empty, this step cannot be completed because this would result in an empty order
if (
!$cart instanceof ProductArticleCollection ||
$cart->getCartCount() === 0
) {
return $this->redirectToRoute('cart-page');
}
$formName = CartFormType::class;
$form = $this->createForm($formName);
$form->handleRequest($request);
//User's shipping information is submitted
if ($form->isSubmitted() && $form->isValid()) {
$shippingInformation = $form->getData();
$params = $request->request->all();
/*dd($shippingInformation['shipping_option']);*/
if($this->recaptchaVersion) {
if($this->recaptcha->captchaverify($params)){
if ($user instanceof UserPortal) {
try {
$order = $this->cartService->getOrderFromSession($session);
if (!$order instanceof Order) {
$order = $this->orderFactory->createOrder($user, $shippingInformation, $cart);
$cart->setOrder($order);
} else {
$order = $this->orderFactory->updateOrder($user, $order, $shippingInformation, $cart);
$cart->setOrder($order);
}
//successfully created order, redirect to overview to pay
return $this->redirectToRoute('cart-overview-page');
} catch (InvalidCustomAddressInformation $e) {
$this->addFlash(
'warning',
'Fout in leveringsadres, gelieve het leveringsadres correct in te vullen.'
);
return $this->render('cart/delivery.html.twig', [
'user' => $user,
'form' => $form->createView(),
]);
}
}
}else{
$message = "Captcha code is niet correct!";
$this->addFlash("warning", $message);
}
}
}
return $this->render('cart/delivery.html.twig', [
'user' => $user,
'form' => $form->createView(),
'recaptcha' => $this->recaptchaVersion,
'recaptchaPublic' => $this->recaptchaPublicKey
]);
}
/**
* Overview of order (final step)
*
* @Route("{_locale}/cart/overview", name="cart-overview-page")
*
* @param Request $request
* @return Response
* @throws Exception
*/
public function overviewAction(Request $request,\Pimcore\Config\Config $websiteConfig): Response
{
$session = $request->getSession();
$user = $this->cartService->getUserFromSession($session);
$cart = $this->cartService->getProductArticleCollectionFromSession($session);
if (
!$cart instanceof ProductArticleCollection ||
$cart->getCartCount() === 0
) {
return $this->redirectToRoute('cart-page');
}
$order = $this->cartService->getOrderFromSession($session);
if (!$order instanceof Order) {
$this->addFlash('waring', 'Er ging iets mis met het aanmaken van de bestelling, gelieve uw leveringsinformatie opnieuw in te vullen');
return $this->redirectToRoute('cart-delivery');
}
if ($user instanceof UserPortal) {
$order = $this->orderFactory->fillProducts($order, $cart);
$cart->setOrder($order);
}
$orderUser = $order->getUser();
$cartItems = $this->cartService->getCartFromSession($session);
$cartItemsViewModels = [];
foreach ($cartItems as $cartItem) {
$cartItemsViewModels[] = new CartItem($cartItem);
}
$cartTotal = $order->getCartTotal();
$redirectUrl = $this->generateUrl(
'cart-redirect-payment',
['order' => $order->getOrderNumber()??''],
UrlGeneratorInterface::ABSOLUTE_URL
);
$webhookUrl = $this->generateUrl(
'cart-check-payment',
['order' => $order->getOrderNumber()??''],
UrlGeneratorInterface::ABSOLUTE_URL
);
$payment = $this->mollieService->createPaymentForWebOrder($order, $redirectUrl, $webhookUrl);
if ($payment instanceof Payment) {
$this->orderFactory->updatePaymentInformation($order, $payment, true);
$paymentRoute = $payment->getCheckoutUrl();
} else {
$this->addFlash('waring','Er ging iets mis met het ophalen van de betalingslink.');
return $this->redirectToRoute('cart-delivery');
}
return $this->render('cart/overview.html.twig', [
'user' => $user,
'order' => $order,
'orderUser' => $orderUser,
'cartItems' => $cartItemsViewModels,
'cartTotal' => CartService::formatPrice($cartTotal),
'shippingTotal' => CartService::formatPrice($order->getShippingCost()),
'total' => $order->getOrderTotal(),
'paymentRoute' => $paymentRoute,
'totalCoupon' => $order->getNewprice(),
]);
}
/**
* User will be redirected to this page after payment, order status gets checked and updated here
*
* @Route("{_locale}/cart/redirectpayment", name="cart-redirect-payment")
*
* @param Request $request
*
* @return Response
* @throws Exception
* @throws TransportExceptionInterface
*/
public function redirectPaymentAction(Request $request,ApplicationLogger $logger): Response
{
$session = $request->getSession();
$session->set(CartService::$sessionCartIdentifier, new ProductArticleCollection()); //empty original cart, since order was finished
$orderNumber = $request->get('order');
$user = $this->cartService->getUserFromSession($request->getSession());
/** @var Listing $order */
$orderListing = Order::getByOrderNumber($orderNumber);
if (!empty($orderListing) && count($orderListing) === 1) {
$order = ($orderListing->getData())[0];
Simple::log('payment-redirect', 'OrderListing && count orderlisting OK');
$logger->info('OrderListing && count orderlisting OK',[ 'relatedObject' => $order]);
if ($order instanceof Order) {
Simple::log('payment-redirect', '$order instance of Order OK');
$logger->info('$order instance of Order OK',['relatedObject' => $order]);
$orderUser = $order->getUser();
//extra validation so other users can not find this user's order
if (
$user instanceof UserPortal &&
$orderUser instanceof UserPortal &&
$user->getKey() === $orderUser->getKey()
) {
Simple::log('payment-redirect', 'User -> UserPortal, orderUser -> UserPortal, userKey OK');
$logger->info('User -> UserPortal, orderUser -> UserPortal, userKey OK',['relatedObject' => $order]);
$paymentId = $order->getPaymentId();
$payment = $this->mollieService->getPaymentById($paymentId);
if (($payment instanceof Payment)) {
Simple::log('payment-redirect', 'Payment OK');
$logger->info('Payment OK',['relatedObject' => $order]);
$this->orderFactory->updatePaymentInformation($order, $payment, false);
}
if ($order->getPaymentStatus() == 'paid'){
Simple::log('payment-redirect', 'Mailstatus OK');
return $this->render('cart/thank_you.html.twig', [
'orderNumber' => $order->getOrderNumber()
]);
}else{
$this->redirectToRoute('cart-overview-page');
}
}
}
}
//this should not happen, but when this happens log this
if (count($orderListing)>1) {
$this->logger->error(sprintf('Duplicate orderNumber detected %s', $orderNumber));
}
Simple::log('payment-redirect', 'ReturnType Broken');
return $this->render('cart/thank_you.html.twig', []);
}
/**
* Webhook route for payment, if update is posted to order, it gets sent to this order
*
* @Route("{_locale}/cart/checkpayment", name="cart-check-payment")
*
* @param Request $request
* @return Response
* @throws Exception
*/
public function checkPaymentAction(Request $request): Response
{
if($request->isMethod('POST')) {
$orderNumber = $request->get('order');
$orderListing = Order::getByOrderNumber($orderNumber);
if (!empty($orderListing) && count($orderListing) === 1) {
$order = ($orderListing->getData())[0]; //get order from listing result
$paymentId = $order->getPaymentId();
if (!empty($paymentId)) {
$payment = $this->mollieService->getPaymentById($paymentId);
if (($payment instanceof Payment) && $order instanceof Order) {
$this->orderFactory->updatePaymentInformation($order, $payment, false);
}
}
}
//this should not happen, but when this happens log this
if (count($orderListing) > 1) {
$this->logger->error(sprintf('Duplicate orderNumber detected %s', $orderNumber));
}
}
$language = $request->attributes->get('_locale');
return $this->redirect(sprintf('/%s/', $language));
}
/**
* @param string|null $message
* @return JsonResponse
*/
private function returnJsonErrorToCart(?string $message = null): JsonResponse
{
$errorMessage = $message ?? 'Something went wrong while adding article to cart';
return new JsonResponse(
[$errorMessage],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
/**
* @param string $actionWeb
* @param ProductArticle $productArticle
* @param string|null $amount
* @param string $cartCount
* @param string $cartValue
* @param string|null $totalProductValue
*
* @return JsonResponse
*/
private function returnJsonSuccess(
string $actionWeb,
ProductArticle $productArticle,
?string $amount,
string $cartCount,
string $cartValue,
?string $totalProductValue,
): JsonResponse
{
if (empty($amount)) {
$amount = '0';
}
return new JsonResponse([
'action' => $actionWeb,
'article' => $productArticle,
'amount' => $amount,
'cartCount' => $cartCount,
'cartValue' => $cartValue,
'totalProductValue' => $totalProductValue,
], Response::HTTP_OK);
}
}