<?php
/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Enterprise License (PEL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PEL
*/
namespace App\Controller;
use App\Form\DeliveryAddressFormType;
use App\Form\JoinFormType;
use App\Form\PasswordFormType;
use App\Model\SecretsManager;
use App\Model\Product\AbstractProduct;
use App\Services\PermissionService;
use App\Services\TrycareService;
use App\Stripe\UserHandler;
use App\Website\Navigation\BreadcrumbHelperService;
use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
use Pimcore\Controller\FrontendController;
use Pimcore\Model\DataObject;
use Pimcore\Model\DataObject\OnlineShopOrder;
use Pimcore\Model\Redirect;
use Pimcore\Bundle\EcommerceFrameworkBundle\Model\AbstractOrder;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Intl\Countries;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class CheckoutController extends FrontendController
{
const SUB_LICENSE_ID = 2454;
const TRYCARE_CART_NAME = 'trycare';
const PLAN_OPTIONS = ['annual', 'monthly'];
const PLAN_TYPES = ['starter', 'premium'];
private $stripeSK;
public function __construct()
{
$secretsManager = new SecretsManager();
$this->stripeSK = $secretsManager->getSecret('stripe-private-key');
}
/**
* @Route("/checkout-address", name="shop-checkout-address")
*
* @param Factory $factory
* @param Request $request
* @param BreadcrumbHelperService $breadcrumbHelperService
* @param Factory $ecommerceFactory
*
* @return Response|RedirectResponse
*/
public function checkoutAddressAction(Factory $factory, Request $request, BreadcrumbHelperService $breadcrumbHelperService, Factory $ecommerceFactory)
{
$cartManager = $factory->getCartManager();
$cart = $cartManager->getOrCreateCartByName('cart');
$checkoutManager = $factory->getCheckoutManager($cart);
$deliveryAddress = $checkoutManager->getCheckoutStep('deliveryaddress');
$deliveryAddressDataArray = $this->fillDeliveryAddressFromCustomer($deliveryAddress->getData());
$form = $this->createForm(DeliveryAddressFormType::class, $deliveryAddressDataArray, []);
$form->handleRequest($request);
$breadcrumbHelperService->enrichCheckoutPage();
if ($request->getMethod() == Request::METHOD_POST) {
$address = new \stdClass();
$formData = $form->getData();
foreach ($formData as $key => $value) {
$address->{$key} = $value;
}
// save address if we have no errors
if (count($form->getErrors()) === 0) {
// commit step
$checkoutManager->commitStep($deliveryAddress, $address);
//TODO remove this - only needed, because one step only is not supported by the framework right now
$confirm = $checkoutManager->getCheckoutStep('confirm');
$checkoutManager->commitStep($confirm, true);
return $this->redirectToRoute('shop-checkout-payment');
}
}
$trackingManager = $ecommerceFactory->getTrackingManager();
$trackingManager->trackCheckoutStep($deliveryAddress, $cart, 1);
return $this->render('checkout/checkout_address.html.twig', [
'cart' => $cart,
'form' => $form->createView(),
]);
}
/**
* @param $deliveryAddress
*
* @return array|null
*/
protected function fillDeliveryAddressFromCustomer($deliveryAddress)
{
$user = $this->getUser();
$deliveryAddress = (array) $deliveryAddress;
if ($user) {
if ($deliveryAddress === null) {
$deliveryAddress = [];
}
$params = ['email', 'firstname', 'lastname', 'street', 'zip', 'city', 'countryCode'];
foreach ($params as $param) {
if (empty($deliveryAddress[$param])) {
$deliveryAddress[$param] = $user->{'get' . ucfirst($param)}();
}
}
}
return $deliveryAddress;
}
/**
* @Route("/sign-up", name="sign-up")
*
* @param Factory $factory
* @param Request $request
* @param BreadcrumbHelperService $breadcrumbHelperService
* @param Factory $ecommerceFactory
*
* @return Response|RedirectResponse
*/
public function SignUpAction(Factory $factory, Request $request)
{
$queryPlan = 'monthly';
$id = $request->query->get('id');
$errors = [];
$licence = DataObject\License::getById($id);
if (!$licence) {
$errors[] = "Invalid licence type";
} elseif (!$this->checkStripeProductHasUsers($licence->getStripeId())) {
$errors[] = "Something went wrong, please contact our support.";
}
$form = $this->createForm(JoinFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid() && $licence && $this->checkStripeProductHasUsers($licence->getStripeId())) {
$formData = $form->getData();
if (!$formData['termsAndPrivacy']) {
$errors[] = 'Sorry, an error occured. Go back to memberships page and try again.';
} else {
$entries = new DataObject\Customer\Listing();
$entries->setCondition("email LIKE ?", ["%" . $formData['email'] . "%"]);
$entries->load();
if (!empty($entries->load())) {
$errors[] = 'Invalid email.';
} else {
$environment = Factory::getInstance()->getEnvironment();
$environment->setCurrentCheckoutTenant('licence');
$environment->save();
$cartManager = $factory->getCartManager();
$cart = $cartManager->getOrCreateCartByName('licence');
$cart->clear();
$checkoutManager = $factory->getCheckoutManager($cart);
$mainInfo = $checkoutManager->getCheckoutStep('collectInfo');
$cart->addItem($licence, 1);
$cart->save();
$userData = new \stdClass();
foreach ($formData as $key => $value) {
$userData->{$key} = $value;
}
$userData->{'plan'} = $queryPlan;
// commit step
$checkoutManager->commitStep($mainInfo, $userData);
//TODO remove this - only needed, because one step only is not supported by the framework right now
$confirm = $checkoutManager->getCheckoutStep('confirm');
$checkoutManager->commitStep($confirm, true);
return $this->redirectToRoute('stripe-subscription-payment');
}
}
}
if ($form->isSubmitted() && !$form->isValid()) {
foreach ($form->getErrors(true) as $error) {
$errors[] = $error->getMessage();
}
}
$name = $licence ? $licence->getName() : '';
return $this->render('checkout/sign-up.html.twig', [
'form' => $form->createView(),
'errors' => $errors,
'plan' => $queryPlan,
'name' => $name
]);
}
/**
* THis function checks if users are defined in the metadata field
*/
public function checkStripeProductHasUsers($productId)
{
\Stripe\Stripe::setApiKey($this->stripeSK);
$product = \Stripe\Product::retrieve($productId);
return is_numeric($product->metadata->users);
}
/**
* @Route("/join-us", name="join-us")
*
* @param Factory $factory
* @param Request $request
* @param BreadcrumbHelperService $breadcrumbHelperService
* @param Factory $ecommerceFactory
*
* @return Response|RedirectResponse
*/
public function JoinAction(Factory $factory, Request $request, BreadcrumbHelperService $breadcrumbHelperService, Factory $ecommerceFactory)
{
$cartManager = $factory->getCartManager();
$cart = $cartManager->getOrCreateCartByName('cart');
$checkoutManager = $factory->getCheckoutManager($cart);
$mainInfo = $checkoutManager->getCheckoutStep('collectInfo');
/* $mainInfoArray = $this->fillDeliveryAddressFromCustomer($deliveryAddress->getData()); */
$form = $this->createForm(JoinFormType::class);
$form->handleRequest($request);
$breadcrumbHelperService->enrichCheckoutPage();
if ($request->getMethod() == Request::METHOD_POST) {
$userData = new \stdClass();
$formData = $form->getData();
$subLicensesQty = $formData['sublicenses'];
$entries = new DataObject\Customer\Listing();
$entries->setCondition("email LIKE ?", ["%" . $formData['email'] . "%"]);
$entries->load();
if (!empty($entries->load())) {
$form->addError(new FormError('Invalid Email.'));
}
if (!is_null($subLicensesQty) && $subLicensesQty !== 0) {
$subLicense = AbstractProduct::getById(self::SUB_LICENSE_ID);
$cartManager->removeFromCart(self::SUB_LICENSE_ID, $cart->getId());
$cart->addItem($subLicense, $subLicensesQty);
$cart->save();
}
foreach ($formData as $key => $value) {
$userData->{$key} = $value;
}
// save address if we have no errors
if (count($form->getErrors()) === 0) {
// commit step
$response = $checkoutManager->commitStep($mainInfo, $userData);
//TODO remove this - only needed, because one step only is not supported by the framework right now
$confirm = $checkoutManager->getCheckoutStep('confirm');
$checkoutManager->commitStep($confirm, true);
/* return $this->redirectToRoute('shop-checkout-start-payment'); */
return $this->redirectToRoute('stripe-checkout-payment');
/* return $this->redirectToRoute('shop-checkout-payment'); */
}
}
// $cart = $this->addMainLicenseToCart($cart);
/* $trackingManager = $ecommerceFactory->getTrackingManager();
$trackingManager->trackCheckoutStep($deliveryAddress, $cart, 1); */
$errors = [];
foreach ($form->getErrors() as $error) {
$errors[] = $error->getMessage();
}
return $this->render('checkout/join.html.twig', [
'cart' => $cart,
'form' => $form->createView(),
'errors' => $errors
]);
}
/**
* @Route("/rejoin", name="rejoin")
*
* @param Factory $factory
* @param Request $request
* @param BreadcrumbHelperService $breadcrumbHelperService
* @param Factory $ecommerceFactory
*
* @return Response|RedirectResponse
*/
public function rejoinAction(Factory $factory, Request $request, UserHandler $userHandler)
{
$planId = $request->query->get('id');
$loggedUser = $this->getUser();
if (!$loggedUser) {
return $this->redirect('/account/login?referer=' . urlencode('/rejoin?id=' . $planId));
}
if ($loggedUser->getTypeOfUser() != 'superuser') {
return $this->redirect('/');
}
$mainSubscriptionStatus = $loggedUser->getSubscription()->getStatus();
if ($mainSubscriptionStatus === 'active') {
return $this->redirect('/');
}
$queryPlan = 'monthly';
$errors = [];
$licence = DataObject\License::getById($planId);
if (!$licence) {
$errors[] = "Invalid licence type";
} elseif (!$this->checkStripeProductHasUsers($licence->getStripeId())) {
$errors[] = "Something went wrong, please contact our support.";
}
$licenceMaxSubusers = $userHandler->getAdditionalUsersFromLicence($licence->getStripeId());
$superUserIncludedSubs = count($loggedUser->getIncludedSubscriptions());
if ($superUserIncludedSubs > $licenceMaxSubusers) {
$errors[] = 'This plan does not support the number of additional users you currently have. Please choose Another plan or contact support.';
}
$name = $licence ? $licence->getName() : 'Custom Plan';
if ($request->isMethod('get') || !empty($errors)) {
return $this->render('checkout/rejoin.html.twig', [
'errors' => $errors,
'plan' => $queryPlan,
'name' => $name,
'id' => $planId
]);
}
$userSubscription = $loggedUser->getSubscription();
$isFreeTrial = $userSubscription->getIsFreeTrial();
//check if stripe sub is expired, if yes redirect to customer portal, otherwise create a new stripe sub
if (!$isFreeTrial && $userHandler->hasExpiredStripeSubscription($userSubscription)) {
return $this->redirect('/create-customer-portal-session');
}
$cartManager = $factory->getCartManager();
$cart = $cartManager->getOrCreateCartByName('licence');
$cart->clear();
$checkoutManager = $factory->getCheckoutManager($cart);
$mainInfo = $checkoutManager->getCheckoutStep('collectInfo');
$mainLicence = $isFreeTrial ? $licence : $userHandler->getOrCreateLicenceFromStripeProduct($userSubscription->getStripeProductId());
$cart->addItem($mainLicence, 1);
$cart->save();
$userData = new \stdClass();
$userData->{'reactivation'} = $loggedUser->getStripeCustomerId();
$userData->{'superuserId'} = $loggedUser->getId();
$userData->{'email'} = $loggedUser->getEmail();
$userData->{'plan'} = $queryPlan;
$userData->{'stripeProductId'} = $mainLicence->getStripeId();
$userData->{'stripeId'} = $userSubscription->getStripeId();
// commit step
$checkoutManager->commitStep($mainInfo, $userData);
//TODO remove this - only needed, because one step only is not supported by the framework right now
$confirm = $checkoutManager->getCheckoutStep('confirm');
$checkoutManager->commitStep($confirm, true);
return $this->redirectToRoute('stripe-subscription-payment');
}
/**
* @Route("/additional-users/reactivation", name="subuser-reactivation")
*
* @param Factory $factory
* @param Request $request
* @param BreadcrumbHelperService $breadcrumbHelperService
* @param Factory $ecommerceFactory
*
* @return Response|RedirectResponse
*/
public function subuserReactivationAction(Request $request, UserHandler $userHandler)
{
//authorization checks
$loggedUser = $this->getUser();
if (!$loggedUser) {
throw new UnauthorizedHttpException('Not authorized.');
}
if (!$loggedUser || $loggedUser->getTypeOfUser() != 'superuser') {
return $this->redirect('/');
}
$mainSubscription = $loggedUser->getSubscription();
if ($mainSubscription->getStatus() != 'active') {
return $this->redirect('/');
}
//check if it has expired or canceled pimcore subscriptions
$expiredOrCancelledExtraSub = array_filter($loggedUser->getExtraSubscriptions(), function ($sub) {
return $sub->getStatus() != 'active';
});
$hasExpiredOrCancelledExtraSubscriptions = !empty($expiredOrCancelledExtraSub);
if (!$hasExpiredOrCancelledExtraSubscriptions) {
return $this->redirect('/');
}
$paymentFailed = $request->query->get('failed');
if ($request->isMethod('get')) {
return $this->render('checkout/add-users-reactivation.html.twig', ['clientSecret' => null, 'paymentFailed' => $paymentFailed]);
}
if ($request->request->get('yes') !== null) {
$stripeResponse = $userHandler->reactivateSublicences($loggedUser);
if ($stripeResponse['status'] === 'requires_action') {
return $this->render('checkout/add-users-reactivation.html.twig', ['clientSecret' => $stripeResponse['secret'], 'paymentFailed' => null]);
}
if ($stripeResponse['status'] === 'failed') {
return $this->render('checkout/add-users-reactivation.html.twig', ['clientSecret' => null, 'paymentFailed' => true]);
}
}
if (
$request->request->get('no') !== null
) {
$userHandler->deactivateSublicences($loggedUser);
}
return $this->redirect('/workplace/home');
}
/**
* @Route("/set-password", name="set-password")
*
* @param SessionInterface $session
* @param Factory $ecommerceFactory
*
* @return Response
*/
public function setPasswordAction(Request $request, Factory $ecommerceFactory, SessionInterface $session, TokenStorageInterface $tokenStorage)
{
$user = $this->getUser();
if (!$this->isGranted('ROLE_USER')) {
return $this->redirect('/');
}
if (!$user) {
throw new AccessDeniedException('Not authorized.');
}
$form = $this->createForm(PasswordFormType::class);
$form->handleRequest($request);
$errors = [];
if ($form->isSubmitted() && $form->isValid()) {
$plainPass = $form->getData()['password'];
$user->setPassword($plainPass);
$user->save();
return $this->redirect('/home');
}
if ($form->isSubmitted() && !$form->isValid()) {
foreach ($form->get('password')->getErrors() as $error) {
$errors[] = $error->getMessage();
}
}
return $this->render('account/first_time_password.html.twig', [
'form' => $form->createView(),
'errors' => $errors,
]);
}
/**
* @Route("/checkout-completed", name="shop-checkout-completed")
*
* @param SessionInterface $session
* @param Factory $ecommerceFactory
*
* @return Response
*/
public function checkoutCompletedAction(SessionInterface $session, Factory $ecommerceFactory)
{
$orderId = $session->get('last_order_id');
$order = OnlineShopOrder::getById($orderId);
$trackingManager = $ecommerceFactory->getTrackingManager();
$trackingManager->trackCheckoutComplete($order);
$cartManager = $ecommerceFactory->getCartManager();
$cart = $cartManager->getOrCreateCartByName('cart');
$checkoutManager = $ecommerceFactory->getCheckoutManager($cart);
return $this->render('checkout/checkout_completed.html.twig', [
'order' => $order,
'hideBreadcrumbs' => true
]);
}
/**
* @Route("/api/checkout-completed/{orderNumber}", name="api-shop-checkout-completed")
*
* @param SessionInterface $session
* @param Factory $ecommerceFactory
*
* @return Response
*/
public function getCompletedCheckoutAction(SessionInterface $session, Factory $ecommerceFactory, $orderNumber)
{
$loggedUser = $this->getUser();
if (!$loggedUser) {
throw new UnauthorizedHttpException('Not authorized.');
}
$order = OnlineShopOrder::getByOrdernumber($orderNumber);
if (!$order) {
throw new BadRequestHttpException('No valid order provided.');
}
$orderData = $order->getData()[0];
if ($loggedUser->getEmail() != $orderData->getCustomerEmail() && $loggedUser->getId() != $orderData->getCustomerOrderedBy()) {
throw new AccessDeniedException('You have no access to this order.');
}
$items = [];
foreach ($orderData->getItems() as $item) {
$items[] = [
'sku' => $item->getPart(),
'name' => $item->getProductName(),
'quantity' => $item->getAmount(),
'totalPrice' => (float)$item->getTotalNetPrice(),
];
}
foreach ($orderData->getGiftItems() as $item) {
$items[] = [
'sku' => $item->getPart(),
'name' => $item->getProductName(),
'quantity' => $item->getAmount(),
'totalPrice' => 'FREE',
];
}
$deliveryAddress = [];
$deliveryAddress['addressLine1'] = $orderData->getDeliveryLine1();
$deliveryAddress['addressLine2'] = $orderData->getDeliveryLine2();
$deliveryAddress['city'] = $orderData->getDeliveryCity();
$deliveryAddress['county'] = $orderData->getDeliveryCounty();
$deliveryAddress['postcode'] = $orderData->getDeliveryZip();
$deliveryAddress['country'] = Countries::getName($orderData->getDeliveryCountry() == 'UK' ? 'GB' : $orderData->getDeliveryCountry());
$customerInfo = [];
$customerInfo['firstName'] = $orderData->getCustomerFirstname();
$customerInfo['lastName'] = $orderData->getCustomerLastname();
$customerInfo['emailAddress'] = $orderData->getCustomerEmail();
$customerInfo['gdcNumber'] = $orderData->getCustomerGdcNumber();
$data = [
'state' => $orderData->getOrderState(),
'deliveryAddress' => $deliveryAddress,
'customerInfo' => $customerInfo,
'items' => $items,
'amount' => [
'net' => $orderData->getTotalNetPrice(),
'delivery' => floatval($orderData->getTotalNetPrice()) - floatval($orderData->getSubTotalNetPrice()),
'gross' => $orderData->getTotalPrice()
]
];
$response = new Response();
$response->headers->set('Content-Type', 'application/json');
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->setContent(json_encode(['order' => $data]));
return $response;
}
/**
* @Route("/api/checkout-default", name="checkout-default", methods="POST")
*
* @param Factory $factory
* @param Request $request
* @param BreadcrumbHelperService $breadcrumbHelperService
* @param Factory $ecommerceFactory
*
* @return Response|RedirectResponse
*/
public function defaultCheckoutAction(Factory $factory, Request $request, BreadcrumbHelperService $breadcrumbHelperService, PermissionService $permissionProvider, TrycareService $trycareProvider)
{
$loggedUser = $this->getUser();
if (!$loggedUser) {
throw new UnauthorizedHttpException('Not authorized.');
}
// $permissions = $permissionProvider->checkPermissions('placeOrder', $loggedUser);
$data = json_decode($request->getContent(), true);
$environment = Factory::getInstance()->getEnvironment();
$environment->setCurrentCheckoutTenant('oscare');
$environment->save();
$cartManager = $factory->getCartManager();
$cart = $cartManager->getOrCreateCartByName(self::TRYCARE_CART_NAME);
$items = $cart->getItems();
if (empty($items)) {
throw new BadRequestHttpException('Cart is empty');
}
if (!$data['deliveryAddress'] || !$data['billingAddress']) {
throw new BadRequestHttpException('Addresses must be provided.');
}
$checkoutManager = $factory->getCheckoutManager($cart);
$clinic = $loggedUser->getClinic();
$deliveryAddress = DataObject::getById($data['deliveryAddress']);
if (!$deliveryAddress) {
throw new BadRequestHttpException('Delivery id required');
}
$deliveryInputData = [
'comment' => $data['comment'],
'line1' => $deliveryAddress->getLine1(),
'line2' => $deliveryAddress->getLine2(),
'city' => $deliveryAddress->getCity(),
'zip' => $deliveryAddress->getPostcode(),
'county' => $deliveryAddress->getCounty(),
'countryCode' => $deliveryAddress->getCountry(),
'phone' => $clinic->getPhone(),
'company' => $clinic->getDisplayName(),
];
//commit delivery data step
$deliveryData = new \stdClass();
foreach ($deliveryInputData as $key => $value) {
$deliveryData->{$key} = $value;
}
$deliveryStep = $checkoutManager->getCheckoutStep('deliveryaddress');
$checkoutManager->commitStep($deliveryStep, $deliveryData);
$billingAddress = DataObject::getById($data['billingAddress']);
if (!$billingAddress) {
throw new BadRequestHttpException('Billing id required');
}
$billingInputData = [
'line1' => $billingAddress->getLine1(),
'line2' => $billingAddress->getLine2(),
'city' => $billingAddress->getCity(),
'zip' => $billingAddress->getPostcode(),
'county' => $billingAddress->getCounty(),
'countryCode' => $billingAddress->getCountry(),
'company' => $clinic->getDisplayName(),
];
//commit billing data step
$billingData = new \stdClass();
foreach ($billingInputData as $key => $value) {
$billingData->{$key} = $value;
}
$billingStep = $checkoutManager->getCheckoutStep('billingaddress');
$checkoutManager->commitStep($billingStep, $billingData);
$superuser = $clinic->getSuperuser();
$userData = new \stdClass();
$customerData = [
'firstname' => $superuser->getFirstname(),
'lastname' => $superuser->getLastname(),
'email' => $clinic->getSuperuser()->getEmail(),
'company' => $clinic->getDisplayName(),
'orderedBy' => $loggedUser->getId(),
'gdcNumber' => $clinic->getSuperuser()->getGdcNumber(),
];
foreach ($customerData as $key => $value) {
$userData->{$key} = $value;
}
// commit customer data step
$customerStep = $checkoutManager->getCheckoutStep('collectInfo');
$checkoutManager->commitStep($customerStep, $userData);
//TODO remove this - only needed, because one step only is not supported by the framework right now
$confirm = $checkoutManager->getCheckoutStep('confirm');
$checkoutManager->commitStep($confirm, true);
$order = $checkoutManager->commitOrder();
if ($order->getOrderState() == AbstractOrder::ORDER_STATE_ABORTED) {
throw new AccessDeniedHttpException('Order processing has failed.');
}
$response = new Response();
$response->headers->set('Content-Type', 'application/json');
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->setContent(json_encode(['order' => $order->getOrdernumber()]));
return $response;
}
/**
* @param Request $request
*
* @return Response
*/
public function confirmationMailAction(Request $request)
{
$order = $request->get('order');
if ($request->get('order-id')) {
$order = OnlineShopOrder::getById($request->get('order-id'));
}
return $this->render('checkout/confirmation_mail.html.twig', [
'order' => $order,
'ordernumber' => $request->get('ordernumber')
]);
}
}