<?php
namespace App\Controller;
use App\Api\ProthelisApi;
use App\Api\WeclappApi;
use App\Entity\Coupon;
use App\Entity\Device;
use App\Entity\DeviceGroup;
use App\Entity\Order;
use App\Entity\Payment;
use App\Entity\PaymentMethod;
use App\Entity\Servicepack;
use App\Entity\SubscriptionDevice;
use App\Entity\User;
use App\Entity\UserMandate;
use App\Form\OrderFormStep1;
use App\Form\OrderFormStep2;
use App\Form\OrderFormStep3;
use App\Form\OrderFormStep4;
use App\Service\CouponService;
use App\Service\DBLogService;
use App\Service\DeviceService;
use App\Service\MollieService;
use App\Service\SubscriptionService;
use Doctrine\ORM\EntityManagerInterface;
use Mollie\Api\Resources\Mandate;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatorInterface;
class ActivationFormController extends AbstractController implements RequireFullUserdataController
{
/** @var DBLogService $dbLogService */
private DBLogService $dbLogService;
private TranslatorInterface $translator;
private RequestStack $requestStack;
private DeviceService $deviceService;
private EntityManagerInterface $entityManager;
private MollieService $mollieService;
private SubscriptionService $subscriptionService;
/**
* AccountController constructor.
*/
public function __construct(EntityManagerInterface $entityManager, DBLogService $dbLogService, TranslatorInterface $translator, RequestStack $requestStack, DeviceService $deviceService, MollieService $mollieService, SubscriptionService $subscriptionService)
{
$this->dbLogService = $dbLogService;
$this->translator = $translator;
$this->requestStack = $requestStack;
$this->deviceService = $deviceService;
$this->entityManager = $entityManager;
$this->mollieService = $mollieService;
$this->subscriptionService = $subscriptionService;
}
/**
* @Route("/signup/overview", name="user_signup_overview")
* @return Response
*/
public function signupOverviewAction(Request $request)
{
return $this->render('activation/overview.html.twig', []);
}
/**
* @Route("/activate/servicepack", name="activate_servicepack")
* @return Response
*/
public function selectPackageAction(Request $request, CouponService $couponService)
{
return $this->redirectToRoute('activate_servicepack_step1', ['sn' => $request->get('sn')]);
}
/**
* @Route("/activate/servicepack/1", name="activate_servicepack_step1")
* @param Request $request
* @param CouponService $couponService
* @return Response
*/
public function selectPackageStep1Action(Request $request, CouponService $couponService)
{
$form = $this->createForm(OrderFormStep1::class);
$sn = $request->get('sn');
if (!empty($sn) && !$form->isSubmitted()) {
$form->get('deviceCode')->setData($sn);
}
$form->handleRequest($request);
$order = null;
if ($form->isSubmitted() && $form->isValid()) {
/** @var Order $order */
$order = $form->getData();
$order->setUser($this->getUser());
$order->setIsProcessed(false);
$order->setIsCanceled(false);
$order->setIsExtended(false);
$order->setMandateId(null);
$this->checkDeviceSN($form);
$this->setOrderDeviceData($order, $form->get('deviceCode')->getData());
$this->processCoupon($couponService, $order, $form);
$order->setTotalPrice();
if ($form->isValid()) {
$order->setDeviceCode(strtoupper($order->getDeviceCode()));
// update user mandates
$this->mollieService->updateUserMandates($this->getUser());
$this->requestStack->getSession()->set('orderData', $order);
// compare with subscription devices
$subscriptionDevice = $this->entityManager->getRepository(SubscriptionDevice::class)->findByDeviceCode($order->getDeviceCode());
// todo: binding should become an option for coupons. For now, we take the servicepack id
// $bindingServicepack = $order->getCoupon() && $order->getCoupon()->getValidServicepack() && in_array($order->getCoupon()->getValidServicepack()->getId(), [37,38]);
if ($subscriptionDevice) {
//todo: only check for valid previous orders
$alreadyActivated = $this->entityManager->getRepository(Order::class)->findByDeviceCodeAndUser($order->getDeviceCode(), $this->getUser(), true);
if (!$alreadyActivated) {
$order->setCoupon(null); // coupons currently don't support subscriptions
$paymentMethod = $this->entityManager->getRepository(PaymentMethod::class)->findOneBy(['provider' => 'subscription']);
$order->setPaymentMethod($paymentMethod);
$order->setServicepack($subscriptionDevice->getServicepack());
$this->deviceService->calculateRenewalDate($order, $subscriptionDevice->getDateAdded());
//dump($order);
return $this->redirectToRoute('activate_overview', ['formData' => $order]);
}
}
if ($order->getTotalPrice() === 0.0) {
$this->deviceService->calculateRenewalDate($order);
return $this->redirectToRoute('activate_overview', ['formData' => $order]);
}
// if servicepack is set in coupon, don't let user change it
if ($order->getCoupon() && $order->getCoupon()->getValidServicepack()) {
$order->setServicepack($order->getCoupon()->getValidServicepack());
$this->deviceService->calculateRenewalDate($order);
//dump($order);
return $this->redirectToRoute('activate_servicepack_step3', ['formData' => $order]);
}
return $this->redirectToRoute('activate_servicepack_step2', ['formData' => $order]);
} else {
// dump($form);
}
}
return $this->render('activation/select.packet.step1.html.twig', ['orderForm' => $form->createView()]);
}
/**
* Set order data for alternative service packs
* @Route("/activate/extra", name="activate_extra_step1")
* @return Response
*/
public function selectExtraStep1Action()
{
$order = new Order();
$order->setUser($this->getUser());
$order->setDeviceCode(Device::NO_DEVICE_SN);
$order->setDeviceType('extra');
$order->setTotalPrice($order->getProductPrice());
$order->setIsProcessed(false);
$this->requestStack->getSession()->set('orderData', $order);
return $this->redirectToRoute('activate_servicepack_step2', ['formData' => $order]);
}
/**
* @Route("/activate/servicepack/2", name="activate_servicepack_step2")
* @param Request $request
* @return Response
*/
public function selectPackageStep2Action(Request $request)
{
$order = null;
if ($this->requestStack->getSession()->has('orderData') && $this->requestStack->getSession()->get('orderData') !== null) {
/** @var Order $orderData */
$orderData = $this->requestStack->getSession()->get('orderData');
$orderData->setId(null);
$order = $orderData; //$em->merge($orderData);
} else {
$this->addFlash('error', $this->translator->trans('account.order.failed'));
$this->dbLogService->info('order', 'order_step2_error', ['message' => 'session does not exist'], $order, $this->getUser());
return $this->redirectToRoute('user_account');
}
$tags = $this->getUser()->getTags();
$today = new \DateTime();
$newPricesStart = new \DateTime('2023-10-01');
/** @var Servicepack[] $packs */
if (in_array(User::TAG_OLDCUSTOMER, $tags) && $today < $newPricesStart) {
$packsDefault = $this->entityManager->getRepository(Servicepack::class)->findAllDefaultOldOrdered();
} else {
$packsDefault = $this->entityManager->getRepository(Servicepack::class)->findAllDefaultOrdered();
}
$packsSubscription = $this->entityManager->getRepository(Servicepack::class)->findAllAboOrdered();
$packsExtra = $this->entityManager->getRepository(Servicepack::class)->findAllExtraOrdered();
$subscription_sets = [
'monthly' => [],
'total' => [],
];
/** @var Servicepack $pack */
foreach ($packsSubscription as $pack) {
if ($pack->getType() === Servicepack::TYPE_ABO_MONTHLY) {
$subscription_sets['monthly'][] = $pack;
} else {
$subscription_sets['total'][] = $pack;
}
}
try {
$order->setServicepack($order->getServicepack());
$order->setServicepack($this->entityManager->merge($order->getServicepack()));
} catch (\Exception $exception) {}
$form = $this->createForm(OrderFormStep2::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var Order $order */
$order = $form->getData();
$this->deviceService->calculateRenewalDate($order);
$order->setTotalPrice();
$order->setCallbackIdentifier();
$order->setIsRecurring($order->getServicepack()->getIsRecurring());
if ($form->isValid()) {
$order->setPaymentMethod(null);
$this->requestStack->getSession()->set('orderData', $order);
return $this->redirectToRoute('activate_servicepack_step3', ['formData' => $order]);
}
}
$orderForm = $form->createView();
$isDefault = $order->getDeviceCode() !== Device::NO_DEVICE_SN;
$mandates = $this->entityManager->getRepository(UserMandate::class)->findValidByUser($this->getUser());
$tags = $this->getUser()->getTags();
$today = new \DateTime();
$newPricesStart = new \DateTime('2023-10-01');
$oldCutomer = false;
if (in_array(User::TAG_OLDCUSTOMER, $tags) && $today < $newPricesStart) {
$oldCutomer = true;
}
$enable_subscription = in_array(User::TAG_ENABLE_ABO, $tags);
// dump($subscription_sets);
return $this->render('activation/select.packet.step2.html.twig', [
'packs' => $isDefault ? $packsDefault : $packsExtra,
'packs_subscription' => $subscription_sets,
'enable_subscription' => $enable_subscription,
'showDefaultPackets' => $isDefault,
'mandates' => $mandates,
'order' => $order,
'orderForm' => $orderForm,
'oldCustomer' => $oldCutomer]);
}
/**
* @Route("/activate/servicepack/3", name="activate_servicepack_step3")
* @param Request $request
* @return Response
*/
public function selectPackageStep3Action(Request $request)
{
$order = null;
$selectedPayment = null;
if ($this->requestStack->getSession()->has('orderData') && $this->requestStack->getSession()->get('orderData') !== null) {
/** @var Order $orderData */
$orderData = $this->requestStack->getSession()->get('orderData');
$orderData->setId(null);
$order = $orderData; //$em->merge($orderData);
} else {
$this->addFlash('error', $this->translator->trans('account.order.failed'));
$this->dbLogService->info('order', 'order_step2_error', ['message' => 'session does not exist'], $order, $this->getUser());
return $this->redirectToRoute('user_account');
}
$user = $this->getUser();
if ($user instanceof User) {
$country = $user->getAddressCountry();
} else {
$country = '1';
}
/** @var PaymentMethod[] $paymentMethod */
if ($order->getServicepack()->getIsRecurring()) {
$paymentMethod = $this->entityManager->getRepository(PaymentMethod::class)->findRecurringForCountryOrdered($country);
} else {
$paymentMethod = $this->entityManager->getRepository(PaymentMethod::class)->findNonRecurringForCountryOrdered($country);
}
try {
$order->setPaymentMethod($order->getPaymentMethod());
$order->setPaymentMethod($this->entityManager->merge($order->getPaymentMethod()));
} catch (\Exception $exception) {}
$form = $this->createForm(OrderFormStep3::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var Order $order */
$order = $form->getData();
$selectedPayment = $this->entityManager->getRepository(PaymentMethod::class)->find($orderData->getPaymentMethod());
if ($selectedPayment) {
$order->setPaymentMethod($selectedPayment);
} else {
$form->get('paymentMethod')->addError(
new FormError(
$this->translator->trans('account.export.msg_error_check_input')
)
);
}
// $formData = $form->getData();
// set payment method or assign mandate
// dump($formData);
// if (is_int($formData->getPaymentMethod())) {
// $payment = $this->entityManager->getRepository(PaymentMethod::class)->find($formData->getPaymentMethod());
// if ($payment) {
// $order->setPaymentMethod($payment);
// }
// } else {
// // find mandate
// /** @var UserMandate $mandate */
// $mandate = $this->entityManager->getRepository(UserMandate::class)->find('mdt_'.$formData->getPaymentMethod());
// if ($mandate) {
// if (!$mandate->getUser()->getId() === $order->getUser()->getId()) {
// $form->get('paymentMethod')->addError(
// new FormError(
// $this->translator->trans('error')
// )
// );
// }
// }
// }
if ($form->isValid()) {
// find payment method
$this->requestStack->getSession()->set('orderData', $order);
return $this->redirectToRoute('activate_overview', ['formData' => $order]);
}
} else {
// exit;
}
if (!$order) {
foreach ($paymentMethod as $pk => $pv) {
if ($pv->getProvider() === 'coupon') {
unset($paymentMethod[$pk]);
}
}
}
$orderForm = $form->createView();
$isDefault = $order->getDeviceCode() !== Device::NO_DEVICE_SN;
$mandates = $this->entityManager->getRepository(UserMandate::class)->findValidByUser($this->getUser());
return $this->render('activation/select.packet.step3.html.twig', [
'showDefaultPackets' => $isDefault,
'paymentMethods' => $paymentMethod,
'mandates' => $mandates,
'order' => $order,
'orderForm' => $orderForm]);
}
/**
* @Route("/activate/overview", name="activate_overview")
* @param Request $request
* @return RedirectResponse|Response|null
*/
public function overviewAction(Request $request)
{
$order = null;
if ($this->requestStack->getSession()->has('orderData') && $this->requestStack->getSession()->get('orderData') !== null) {
$order = $this->requestStack->getSession()->get('orderData');
} else {
$this->addFlash('error', $this->translator->trans('account.order.failed'));
$this->dbLogService->info('order', 'order_step3_error', ['message' => 'session does not exist'], $order, $this->getUser());
return $this->redirectToRoute('user_account');
}
$form = $this->createForm(OrderFormStep4::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var Order $order */
$order = $form->getData();
$this->requestStack->getSession()->set('orderData', $order);
return $this->redirectToRoute('activate_place', ['formData' => $order]);
}
$orderForm = $form->createView();
return $this->render('activation/overview_table.html.twig', [
'user' => $this->getUser(),
'orderData' => $order,
'orderForm' => $orderForm,
'show_info_deactivated' => $this->deviceService->isDeactivatedForInactivity($order),
]);
}
/**
* @Route("/activate/place", name="activate_place")
* @return RedirectResponse
*/
public function placeOrderAction(Request $request) {
try {
/** @var Order $order */
$order = $this->requestStack->getSession()->get('orderData');
if (!$order) {
// session not found
$this->addFlash('error', $this->translator->trans('account.order.failed'));
return $this->redirectToRoute('user_account');
}
} catch (\Exception $exception) {
// session not found
$this->addFlash('error', $this->translator->trans('account.order.failed'));
return $this->redirectToRoute('user_account');
}
switch ($order->getPaymentMethod()->getProvider()) {
case 'coupon':
case 'subscription':
$order->setCurrentStatus('activating');
$order->setPaymentStatus(PaymentMethod::STATUS_PAYMENT_COMPLETED);
$this->addFlash('success', $this->translator->trans('account.order.success'));
break;
case 'paypal':
case 'payever':
case 'mollie':
default:
$order->setCurrentStatus('complete_payment');
$order->setPaymentStatus(PaymentMethod::STATUS_COMPLETE_PAYMENT);
}
$order->setUserData($request->getClientIp());
$order->setIsRecurring($order->getServicepack()->getIsRecurring());
$order->setIsExtended(false);
$order->setIsCanceled(false);
/** @var User $user */
$user = $this->getUser();
if ($user->getSalutation()) {
$order->setUser($this->getUser());
}
$order->setServicepack($this->entityManager->merge($order->getServicepack()));
$order->setPaymentMethod($this->entityManager->merge($order->getPaymentMethod()));
if ($order->getCoupon()) {
/** @var Coupon $coupon */
$coupon = $order->getCoupon();
/** @var User $cuser */
if ($cuser = $coupon->getValidUser()) {
if ($cuser->getId() == $user->getId()) {
$coupon->setValidUser($this->getUser());
$order->setCoupon($coupon);
}
}
$order->setCoupon($this->entityManager->merge($order->getCoupon()));
}
if (!$order->getId()) {
$this->entityManager->persist($order);
$this->entityManager->flush();
$this->requestStack->getSession()->set('orderData', $order);
}
// skip if order already processed (browser back button, etc.) or if no cost
if (!$order->getWcOrderNumber() && $order->getTotalPrice() > 0 && $_ENV['APP_ENV'] !== 'dev') {
try {
// Connect with weclapp
$weclappApi = new WeclappApi($this->entityManager);
// get/create wc user and set wc_user_id
$wcCustomer = $weclappApi->connectUser($user);
// create order in wc
$wcOrder = $weclappApi->salesOrderManager->createAndBook($weclappApi, $order, $wcCustomer);
// get wc order_id (for payment)
$wcOrderNumber = $wcOrder->orderNumber;
$wcOrderId = $wcOrder->id;
$order->setWcOrderNumber($wcOrderNumber);
$order->setWcOrderId($wcOrderId);
$this->entityManager->flush();
$this->dbLogService->debug('order', 'weclapp', $wcOrder, $order, $this->getUser());
} catch (\Exception $exception) {
$this->dbLogService->error('order', 'weclapp', $exception, $order, $this->getUser());
}
}
// $this->dbLogService->info('order', 'place', $order, $order, $this->getUser());
// $this->dbLogService->info('order', 'place', null, $order, $this->getUser());
return $this->redirectToRoute('payment_prepare');
}
/**
* @Route("/activate/completeOrder/{id}", requirements={"id" = "\d+"}, name="activate_completeorder")
* @return RedirectResponse
*/
public function completeOrderAction($id, Request $request) {
/** @var Order $order */
$order = $this->entityManager->getRepository(Order::class)->findOneFromUser($this->getUser(), $id);
if ($order->getPaymentStatus() !== PaymentMethod::STATUS_COMPLETE_PAYMENT) {
$this->addFlash('error', $this->translator->trans('account.order.failed'));
return $this->redirectToRoute('user_account');
}
$this->requestStack->getSession()->set('orderData', $order);
return $this->redirectToRoute('payment_prepare');
}
/**
* @Route("/activate/cancelOrder/{id}", requirements={"id" = "\d+"}, name="activate_cancelorder")
* @return RedirectResponse
*/
public function cancelOrderAction($id, Request $request) {
/** @var Order $order */
$order = $this->entityManager->getRepository(Order::class)->findOneFromUser($this->getUser(), $id);
if ($order->getPaymentStatus() !== PaymentMethod::STATUS_COMPLETE_PAYMENT) {
$this->addFlash('error', $this->translator->trans('account.order.failed'));
return $this->redirectToRoute('user_account');
}
//$this->requestStack->getSession()->set('orderData', $order);
$order->setOrderComment('('.date('Y-m-d H:i:s').") Abgebrochen durch Kunden\n".$order->getOrderComment());
$order->setCurrentStatus('cancelled');
$order->setPaymentStatus(PaymentMethod::STATUS_PAYMENT_ABORTED);
$this->entityManager->flush();
return $this->redirect($request->headers->get('referer'));
}
/**
* @Route("/activate/completePayment/{id}", requirements={"id" = "\d+"}, name="activate_completepayment")
* @return RedirectResponse
*/
public function completePaymentAction($id, Request $request) {
/** @var Payment $payment */
$payment = $this->entityManager->getRepository(Payment::class)->findByUserAndId($this->getUser(), $id);
if ($payment->getStatus() !== Payment::STATUS_FAILED) {
$this->addFlash('error', $this->translator->trans('account.order.failed'));
return $this->redirectToRoute('user_account');
}
$order = $payment->getOrder();
$pack_description = $order->getDeviceType().' Service ' . $order->getServicepack()
->getRuntime() . 'M: ' . $order->getDeviceCode() . ' (' . $order->getId() . ')';
$molliePayment = $this->mollieService->retryOrderPayment($payment, $pack_description);
return $this->redirect($molliePayment->getCheckoutUrl());
}
/**
* @param CouponService $couponService
* @param Order $order
* @param FormInterface $form
* @return void
*/
protected function processCoupon(CouponService $couponService, Order $order, FormInterface $form)
{
// evaluate if device qualifies for bundled activation. If bundle is available, overwrite coupon field, otherwise keep it
$couponCode = $this->getBundleCode($order, $form->get('couponCode')->getData());
if (!empty($couponCode)) {
/** @var Coupon $coupon */
$coupon = $order->getCouponByCode($couponCode, $couponService);
if ($coupon->getError()) {
$form->get('couponCode')
->addError(new FormError($coupon->getError()));
} else {
if ($coupon->getId() !== null) {
$order->setCoupon($coupon);
}
}
} else {
// $order->setCoupon(null);
}
}
protected function getBundleCode(Order $order, $default = null) {
// find bundle groups
$bundle_groups = [];
foreach (['6', '12', '24'] as $month) {
// check for free month first
$tmp = $this->entityManager->getRepository(DeviceGroup::class)->getByTitle('Bundle '.$month.'+1 Monate');
if ($tmp instanceof DeviceGroup) {
$bundle_groups[$tmp->getId()] = 'BUNDLE'.$month;
} else {
$tmp = $this->entityManager->getRepository(DeviceGroup::class)->getByTitle('Bundle '.$month.' Monate');
if ($tmp instanceof DeviceGroup) {
$bundle_groups[$tmp->getId()] = 'BUNDLE'.$month;
}
}
}
$device_sn = $order->getDeviceCode();
// find orders
$orders = $this->entityManager->getRepository(Order::class)->findByDeviceCode($order->getDeviceCode());
if (count($orders) > 0) {
// device has been used before
return $default;
}
foreach ($bundle_groups as $group_id => $coupon_code) {
/** @var DeviceGroup $device_group */
$device_group = $this->entityManager->getRepository(DeviceGroup::class)->find($group_id);
if ($device_group->containsDeviceCode($device_sn)) {
return $coupon_code;
}
}
return $default;
}
protected function checkDeviceSN(FormInterface $form)
{
$device_sn = $form->get('deviceCode')->getData();
/** @var User $user */
$user = $this->getUser();
$device = ProthelisApi::getDevice($device_sn);
/**
* check for different owner
*/
if ($device && count($device->owners) > 0) {
$is_owner = false;
foreach ($device->owners as $owner) {
if ($owner->user_id === $user->getIdApi()) {
$is_owner = true;
}
}
if (!$is_owner) {
$form->get('deviceCode')->addError(
new FormError(
$this->translator->trans('account.order.error_device_taken')
)
);
}
}
/**
* check for deactivated
*/
if ($device && $device->sim_provider==='freeeway' && $device->properties->sim_state == 120) {
$form->get('deviceCode')->addError(
new FormError(
$this->translator->trans('account.order.error_device_deactivated')
)
);
}
/**
* check for incomplete orders
*/
$incomplete_order = $this->entityManager->getRepository(Order::class)->findUnfinishedBySnAndUser(
$device_sn,
$user
);
if (!empty($incomplete_order)) {
$form->get('deviceCode')->addError(
new FormError(
$this->translator->trans('account.order.error_open_order')
)
);
}
/**
* check for active subscription
*/
$hasSubscription = $this->subscriptionService->hasActiveSubscription($device_sn);
if ($hasSubscription) {
$form->get('deviceCode')->addError(
new FormError(
$this->translator->trans('account.order.error_active_subscription')
)
);
}
}
protected function setOrderDeviceData(Order $order, $device_sn) {
$device = ProthelisApi::getDevice($device_sn);
$device_name = 'undefined';
if ($device) {
//TODO: define a better mapping process
$needle = strtoupper(substr($device->hw_version, 0, 3));
switch ($needle) {
case 'G09':
//GRETA
$device_name = 'GRETA';
break;
case 'GT0':
case 'G02':
//meintal
$device_name = 'meintal';
break;
case 'GU0':
case 'G05':
//area
$device_name = 'area';
break;
default:
//undefined, probably GRETA
$device_name = 'GRETA';
}
}
$order->setDeviceType($device_name);
}
}