src/Services/CartManager.php line 1000

Open in your IDE?
  1. <?php
  2. namespace App\Services;
  3. use App\Entity\Main\Affiliate;
  4. use App\Entity\Main\AlternativeBilling;
  5. use App\Entity\Main\Bill;
  6. use App\Entity\Main\BillingAccount;
  7. use App\Entity\Main\CardPayment;
  8. use App\Entity\Main\Cart;
  9. use App\Entity\Main\CartAction;
  10. use App\Entity\Main\CartAddress;
  11. use App\Entity\Main\CartCreditNote;
  12. use App\Entity\Main\CartHasABTest;
  13. use App\Entity\Main\CartInterface;
  14. use App\Entity\Main\CartSupportRefund;
  15. use App\Entity\Main\DiscountCode;
  16. use App\Entity\Main\Pack;
  17. use App\Entity\Main\PackInterface;
  18. use App\Entity\Main\PackProduct;
  19. use App\Entity\Main\PackProductInterface;
  20. use App\Entity\Main\PackTemplate;
  21. use App\Entity\Main\PackTemplateProduct;
  22. use App\Entity\Main\PaymentToken;
  23. use App\Entity\Main\Product;
  24. use App\Entity\Main\Purchase;
  25. use App\Entity\Main\Site;
  26. use App\Entity\Main\SitePackTemplate;
  27. use App\Entity\Main\Tracking;
  28. use App\Entity\Main\TrackingHasABTest;
  29. use App\Entity\Main\Transaction;
  30. use App\Entity\Main\UpsellCart;
  31. use App\Entity\Main\User;
  32. use App\Entity\Main\UserAddress;
  33. use App\Entity\Main\UserPaymentToken;
  34. use App\Events\Main\Cart\CartCreatedEvent;
  35. use App\Events\Main\Cart\CartItemAddedEvent;
  36. use App\Events\Main\Cart\CartItemQuantityUpdatedEvent;
  37. use App\Events\Main\DiscountCode\DiscountCodeErrorEvent;
  38. use App\Events\Main\Order\OrderFailedCheckedEvent;
  39. use App\EventSubscribers\Main\CartSubscriber;
  40. use App\EventSubscribers\Main\DiscountCodeSubscriber;
  41. use App\EventSubscribers\Main\OrderSubscriber;
  42. use App\Exceptions\Main\Cart\CartAlreadyPaidException;
  43. use App\Exceptions\Main\DiscountCode\DiscountCodeAlreadyUsedException;
  44. use App\Exceptions\Main\DiscountCode\DiscountCodeException;
  45. use App\Exceptions\Main\DiscountCode\DiscountCodeExpiredException;
  46. use App\Exceptions\Main\DiscountCode\DiscountCodeNotApplicableException;
  47. use App\Exceptions\Main\DiscountCode\DiscountCodeNotValidException;
  48. use App\Exceptions\Main\DiscountCode\DiscountCodeNotValidRegistrationException;
  49. use App\Exceptions\Main\DiscountCode\DiscountCodeWrongUserException;
  50. use App\Services\PackManager;
  51. use App\Services\Setup;
  52. use App\Services\TrackingManager;
  53. use App\Psp\STSClient;
  54. use App\Repository\Main\CartActionRepository;
  55. use App\Services\ABTestManager;
  56. use App\Services\BillingAccountManager;
  57. use App\Services\DiscountCodeManager;
  58. use App\Services\GeoIPManager;
  59. use App\Services\SiteManager;
  60. use App\Services\TranslationManager;
  61. use App\Services\UserAddressManager;
  62. use App\Services\UserManager;
  63. use App\Tools\ConditionalPacks\FailedPackCondition;
  64. use App\Tools\ReviewsSystem\AvisVerifies;
  65. use App\Tools\ShortId;
  66. use Doctrine\Common\Collections\ArrayCollection;
  67. use Doctrine\ORM\EntityManagerInterface;
  68. use Doctrine\ORM\EntityRepository;
  69. use Exception;
  70. use League\Period\Period;
  71. use Predis\Client;
  72. use Psr\Log\LoggerInterface;
  73. use Symfony\Component\Asset\Packages;
  74. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  75. use Symfony\Component\HttpFoundation\Request;
  76. use Symfony\Component\HttpFoundation\RequestStack;
  77. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  78. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  79. use Symfony\Component\Routing\RouterInterface;
  80. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  81. use Symfony\Component\Workflow\WorkflowInterface;
  82. use Symfony\Contracts\Translation\TranslatorInterface;
  83. final class CartManager
  84. {
  85.     const
  86.         DISCOUNT_CODE_KEY 'dcode',
  87.         NO_COACHING_SESSION_KEY 'coaching_refused',
  88.         COACHING_CHOICE_SESSION_KEY 'coaching_choice_made',
  89.         CUSTOM_PACK_MAX_SINGLE_ITEMS 6,
  90.         CHECK_FAILED_PURCHASE_KEY 'check-failed-purchase',
  91.         OFFER_PREUPSELL 'offer-preupsell',
  92.         OFFER_UPSELL 'offer-upsell'
  93.     ;
  94.     private $is_active_smart_routing_billing false;
  95.     public static array $possibleOffers = [self::OFFER_PREUPSELLself::OFFER_UPSELL];
  96.     private EntityRepository $cartRepository;
  97.     private EntityRepository $cartActionRepository;
  98.     private mixed $invoiceSiteId;
  99.     private mixed $brandName;
  100.     private mixed $siteName;
  101.     private mixed $webSiteHost;
  102.     private ?Request $request;
  103.     /**
  104.      * @param EntityManagerInterface $em
  105.      * @param LoggerInterface $logger
  106.      * @param PackManager $packFactory
  107.      * @param TrackingManager $trackingFactory
  108.      * @param RequestStack $requestStack
  109.      * @param Client $redisCache
  110.      * @param EventDispatcherInterface $eventDispatcher
  111.      * @param UserManager $userManager
  112.      * @param WorkflowInterface $cartStateMachine
  113.      * @param $environment
  114.      * @param TokenStorageInterface $token_storage
  115.      * @param ABTestManager $ABTestManager
  116.      * @param DiscountCodeManager $discountCodeManager
  117.      * @param SiteManager $siteManager
  118.      * @param ShortId $shortIdGenerator
  119.      * @param UserAddressManager $userAddressManager
  120.      * @param TranslatorInterface $translator
  121.      * @param BillingAccountManager $billingAccountManager
  122.      * @param \App\Services\GeoIPManager $geoIPManager
  123.      */
  124.     public function __construct(
  125.         private EntityManagerInterface $em,
  126.         private LoggerInterface $logger,
  127.         private PackManager $packFactory,
  128.         public TrackingManager $trackingFactory,
  129.         private RequestStack $requestStack,
  130.         private Client $redisCache,
  131.         private EventDispatcherInterface $eventDispatcher,
  132.         private UserManager $userManager,
  133.         private WorkflowInterface $cartStateMachine,
  134.         private $environment,
  135.         public TokenStorageInterface $token_storage,
  136.         private ABTestManager $ABTestManager,
  137.         private DiscountCodeManager $discountCodeManager,
  138.         public SiteManager $siteManager,
  139.         private ShortId $shortIdGenerator,
  140.         private UserAddressManager $userAddressManager,
  141.         private TranslatorInterface $translator,
  142.         private BillingAccountManager $billingAccountManager,
  143.         private GeoIPManager $geoIPManager
  144.     ) {
  145.         $this->request $this->requestStack->getCurrentRequest();
  146.         $this->cartRepository $em->getRepository(Cart::class);
  147.         $this->cartActionRepository $em->getRepository(CartAction::class);
  148.         $this->invoiceSiteId $this->siteManager->getYamlConfigParameter('invoiceSiteId');
  149.         $this->brandName $siteManager->getYamlConfigParameter('brandName');
  150.         $this->siteName $siteManager->getYamlConfigParameter('websiteName');
  151.         $this->webSiteHost $siteManager->getYamlConfigParameter('websiteHost');
  152.     }
  153.     /**
  154.      * @return mixed
  155.      */
  156.     public function getInvoiceSiteId()
  157.     {
  158.         return $this->invoiceSiteId;
  159.     }
  160.     /**
  161.      * Tells whether to use Smart Routing on billings
  162.      *
  163.      * @param bool $state
  164.      */
  165.     public function useSmartRoutingBilling(bool $state)
  166.     {
  167.         $this->is_active_smart_routing_billing $state;
  168.     }
  169.     /**
  170.      * Tells whether to Smart Routing on billings is active
  171.      */
  172.     public function smartRoutingBillingIsActive(): bool
  173.     {
  174.         return $this->is_active_smart_routing_billing;
  175.     }
  176.     /**
  177.      * Tell whether the cart global transaction state is valid
  178.      *
  179.      * @param Cart $cart
  180.      *
  181.      * @return bool
  182.      */
  183.     public function isShippable(Cart $cart): bool
  184.     {
  185.         if (Cart::STATE_PENDING_SHIPPING !== $cart->getCartState()) {
  186.             $this->logger->notice(sprintf('The cart %s is not shippable because of its current state %s'$cart->getCartId(), $cart->getCartState()));
  187.             return false;
  188.         }
  189.         $user $cart->getUser();
  190.         if (!$user instanceof User) {
  191.             $this->logger->notice(sprintf('The cart %s is not shippable because no user was attached to it'$cart->getCartId()));
  192.             return false;
  193.         }
  194.         $cartAddress $cart->getCartAddress();
  195.         if (!$cartAddress instanceof CartAddress) {
  196.             $this->logger->notice(sprintf('The cart %s is not shippable because no user address was attached to its user %s'$cart->getCartId(), $user->getUsername()));
  197.             return false;
  198.         }
  199.         $state false;
  200.         $paymentTransaction $cart->getPaymentTransaction();
  201.         if ('prod' === $this->environment && $paymentTransaction->getBillingAccount()->isDummyAccount()) {
  202.             $this->logger->notice(sprintf(
  203.                 'The cart %s is not shippable because it uses a dummy billing account : %s',
  204.                 $cart->getCartId(),
  205.                 $paymentTransaction->getBillingAccount()->getBillingAccountName()
  206.             ));
  207.             return $state;
  208.         }
  209.         $tStatus $paymentTransaction->getTransactionStatus();
  210.         $tType $paymentTransaction->getTransactionType();
  211.         if (Transaction::OK !== $tStatus) {
  212.             return $state;
  213.         }
  214.         if (in_array($tType, [Transaction::CHARGEBACK], true)) {
  215.             $this->logger->notice(sprintf(
  216.                 'The cart %s is not shippable because a %s transaction is attached to it',
  217.                 $cart->getCartId(),
  218.                 'CHARGEBACK'
  219.             ));
  220.             return $state;
  221.         }
  222.         $state Transaction::BILL === $tType;
  223.         return $state;
  224.     }
  225.     public function cleanUpCurrentCart(Cart $currentCart)
  226.     {
  227.         // virer le currentCart si il n'est pas utile de le garder (en gros tous les status a la con avant pendingPayment ou meme apres si on offre pas la possiblite de "repayer" un cart)
  228.         $this->removeCartFromSession();
  229.         $currentCart->setCartAddress(null);
  230.         $this->transitState($currentCart'cleaning');
  231.         $this->em->flush();
  232.         // ok we don't delete anymore the carts, we will do some cleanup with a cron from now on.
  233.         return false;
  234. //        if ($currentCart instanceof Cart && !$currentCart->isPurchased() && !count($currentCart->getTransactions())) {
  235. //            try {
  236. //                $this->removeCartById($currentCart->getCartId());
  237. //                return true;
  238. //            } catch (\Exception $e) {
  239. //                $this->logger->critical("message: {$e->getMessage()}, cart : {$currentCart->getCartId()}");
  240. //            }
  241. //        }
  242. //
  243. //        return false;
  244.     }
  245.     public function removeCartFromSession()
  246.     {
  247.         $session $this->request->getSession();
  248.         $currentCartId $session->get('currentCartId');
  249.         if (isset($currentCartId)) {
  250.             $session->remove('currentCartId');
  251.         }
  252.     }
  253.     public function removeCartById($cartId)
  254.     {
  255.         $cart $this->em
  256.             ->getRepository('CastalisPlsBrulafineCoreBundle:Cart')
  257.             ->findOneBy(['cartId' => $cartId]);
  258.         if ($cart instanceof Cart) {
  259.             $this->em->remove($cart);
  260.             $this->em->flush();
  261.         }
  262.         $session $this->request->getSession();
  263.         $session->remove('currentCartId');
  264.     }
  265.     /**
  266.      * @param Cart $cart
  267.      * @param string $transitionName
  268.      * @return bool
  269.      */
  270.     public function transitState(Cart $cartstring $transitionName): bool
  271.     {
  272.         if (!$this->cartStateMachine->can($cart$transitionName)) {
  273.             return false;
  274.         }
  275.         return null !== $this->cartStateMachine->apply($cart$transitionName);
  276.     }
  277.     /**
  278.      * @param PackTemplate $packTemplate
  279.      * @param Site $site
  280.      * @param Tracking $tracking
  281.      * @param Affiliate $affiliate
  282.      * @param array $items
  283.      * @param null $user
  284.      * @param CartAddress|null $cartAddress
  285.      * @param false $forceCoaching (as we spend our time redoing the cart (still didn't have time to investigate why) here we pass the info if previous cart had coaching or not.)
  286.      * @return Cart
  287.      */
  288.     public function makeCartFromPackTemplate(
  289.         PackTemplate $packTemplate,
  290.         Site $site,
  291.         Tracking $tracking,
  292.         Affiliate $affiliate,
  293.         array $items = [],
  294.         $user null,
  295.         CartAddress $cartAddress null,
  296.         $forceCoaching false
  297.     ): Cart {
  298.         $cart = new Cart($affiliate$site);
  299.         $cart->setCartCurrency('EUR'); // todo ajouter la currency dans le template
  300.         $pack = new Pack(
  301.             $packTemplate->getPackTemplateName(),
  302.             $packTemplate->getPackTemplateType(),
  303.             $packTemplate->getPackTemplateDiscount(),
  304.             $packTemplate->getPackTemplateCurrency(),
  305.             $packTemplate->getPackTemplateOptions(),
  306.             $packTemplate
  307.         );
  308.         if ($user instanceof User) {
  309.             $cart->setUser($user);
  310.         }
  311.         $this->em->persist($pack);
  312.         $this->em->flush();
  313.         $this->updateTrackingInfos($cart$tracking);
  314.         $this->em->persist($cart);
  315.         $this->em->flush();
  316.         // Create a unique cart Shipping ID.
  317.         $cart->setCartShippingId(sprintf("CT{$this->invoiceSiteId}%'.09d"$cart->getCartId()));
  318.         // check if he refused coaching :
  319.         if (!$forceCoaching && !$packTemplate->isCoachingForced() && ($this->request->get('cnr') == || $this->request->getSession()->get(CartManager::NO_COACHING_SESSION_KEY) == || $this->siteManager->coachingIsPreventedByDefault($tracking))) {
  320.             $coachingExcluded true;
  321.         } else {
  322.             $coachingExcluded false;
  323.         }
  324.         /** @var PackTemplateProduct $packTemplateProduct */
  325.         foreach ($packTemplate->getPackTemplateProducts() as $packTemplateProduct) {
  326.             $externalQuantity $this->extractExternalQuantity($items$packTemplateProduct);
  327.             $productQuantity $packTemplateProduct->getProductQuantity();
  328.             if ($externalQuantity > -1) {
  329.                 $productQuantity $externalQuantity;
  330.             }
  331.             // If we exclude coaching, we add it but with 0 as quantity.
  332.             if ($coachingExcluded && $packTemplateProduct->getProduct()->isCoaching()) {
  333.                 $productQuantity 0;
  334.             }
  335.             if ($productQuantity > -1) {
  336.                 $packProduct = new PackProduct(
  337.                     $pack,
  338.                     $packTemplateProduct->getProduct(),
  339.                     $productQuantity,
  340.                     $packTemplateProduct->getPackTemplateProductOrder()
  341.                 );
  342.                 $packProduct->setDisplayed($packTemplateProduct->isDisplayed());
  343.                 $this->em->persist($packProduct);
  344.                 $this->em->flush();
  345.                 $pack->addPackProduct($packProduct);
  346.                 $event = new CartItemAddedEvent($cart$packProduct);
  347.                 $this->eventDispatcher->dispatch($eventCartSubscriber::CART_ITEM_ADDED);
  348.             }
  349.         }
  350.         $cart->addPack($pack);
  351.         $this->refreshCartPrices($cart);
  352.         $this->em->flush();
  353.         $event = new CartCreatedEvent($cart$user);
  354.         $this->eventDispatcher->dispatch($eventCartSubscriber::CART_CREATED);
  355.         $this->refreshCartPrices($cart);
  356.         $this->em->flush();
  357.         // Create a unique cart Shipping ID.
  358.         $cart->setCartShippingId(sprintf("CT{$this->invoiceSiteId}%'.09d"$cart->getCartId()));
  359.         if ($cartAddress instanceof CartAddress) {
  360.             $cart->setCartAddress($cartAddress);
  361.             $cartAddress->setCart($cart);
  362.         }
  363.         $this->em->flush();
  364.         return $cart;
  365.     }
  366.     public function makeTemporaryCart(
  367.         PackTemplate $packTemplate,
  368.         Site $site,
  369.         Tracking $tracking,
  370.         Affiliate $affiliate,
  371.         array $items = [],
  372.         bool $has_coaching null
  373.     ): Cart {
  374.         $cart = new Cart($affiliate$site);
  375.         $cart->setCartCurrency('EUR'); // todo ajouter la currency dans le template
  376.         $pack = new Pack(
  377.             $packTemplate->getPackTemplateName(),
  378.             $packTemplate->getPackTemplateType(),
  379.             $packTemplate->getPackTemplateDiscount(),
  380.             $packTemplate->getPackTemplateCurrency(),
  381.             $packTemplate->getPackTemplateOptions(),
  382.             $packTemplate
  383.         );
  384.         /** @var PackTemplateProduct $packTemplateProduct */
  385.         foreach ($packTemplate->getPackTemplateProducts() as $packTemplateProduct) {
  386.             $externalQuantity $this->extractExternalQuantity($items$packTemplateProduct);
  387.             $productQuantity $packTemplateProduct->getProductQuantity();
  388.             if ($externalQuantity > -1) {
  389.                 $productQuantity $externalQuantity;
  390.             }
  391.             if ($productQuantity > -1) {
  392.                 if (false === $has_coaching && $packTemplateProduct->getProduct()->getProductName() === 'Coaching') {
  393.                     $productQuantity 0;
  394.                 }
  395.                 $packProduct = new PackProduct(
  396.                     $pack,
  397.                     $packTemplateProduct->getProduct(),
  398.                     $productQuantity,
  399.                     $packTemplateProduct->getPackTemplateProductOrder()
  400.                 );
  401.                 $packProduct->setDisplayed($packTemplateProduct->isDisplayed());
  402.                 $pack->addPackProduct($packProduct);
  403.             }
  404.         }
  405.         $cart->addPack($pack);
  406.         $this->updateTrackingInfos($cart$tracking);
  407.         $this->refreshCartPrices($cart);
  408.         return $cart;
  409.     }
  410.     /**
  411.      * Replaced with a listener.
  412.      * @param Request $request
  413.      * @return bool
  414.      */
  415.     public function saveDiscountCodeName(Request $request): bool
  416.     {
  417.         $discountCodeName $request->query->get(self::DISCOUNT_CODE_KEY) ?? null;
  418.         if (null === $discountCodeName) {
  419.             return false;
  420.         }
  421.         $discountCodeName trim($discountCodeName);
  422.         if ('' === $discountCodeName) {
  423.             return false;
  424.         }
  425.         $discountCode $this->em->getRepository(DiscountCode::class)
  426.             ->findOneBy([
  427.                 'discountCodeName' => $discountCodeName,
  428.                 'discountCodeStatus' => DiscountCode::ACTIVE_STATUS,
  429.             ]);
  430.         if ($discountCode instanceof DiscountCode) {
  431.             $request->getSession()->set(self::DISCOUNT_CODE_KEY$discountCodeName);
  432.             return true;
  433.         }
  434.         return false;
  435.     }
  436.     /**
  437.      * Get the current discount code from request or session
  438.      *
  439.      * @param Cart|null $cart
  440.      * @return |null
  441.      */
  442.     public function getActualDiscountCode(Cart $cart null)
  443.     {
  444.         if ($this->request->request->get('discountCode')) {
  445.             return trim($this->request->request->get('discountCode'));
  446.         }
  447.         if ($cart instanceof Cart && $cart->getDiscountCode() instanceof DiscountCode) {
  448.             return $cart->getDiscountCode()->getDiscountCodeName();
  449.         }
  450.         if ($this->requestStack->getSession()->has(self::DISCOUNT_CODE_KEY)) {
  451.             $discountCodeName trim((string)$this->requestStack->getSession()->get(self::DISCOUNT_CODE_KEY));
  452.             if ('' === $discountCodeName) {
  453.                 $this->requestStack->getSession()->remove(self::DISCOUNT_CODE_KEY);
  454.                 return null;
  455.             }
  456.             return $discountCodeName;
  457.         }
  458.         return null;
  459.     }
  460.     /**
  461.      * Apply discountCode
  462.      *
  463.      * @param Cart $cart
  464.      * @param $discountCodeName
  465.      * @return array
  466.      * @throws Exception
  467.      */
  468.     public function addDiscountCode(Cart $cart$discountCodeName): array
  469.     {
  470.         $res = ['status' => 'failed''message' => "flashMessages.site.promoCodeNotExists"];
  471.         if (!$cart->allowDiscountCode()) {
  472.             $res['message'] = "flashMessages.site.promoCodeNotForThisPack";
  473.             $event = new DiscountCodeErrorEvent($discountCodeName$res['message']);
  474.             $this->eventDispatcher->dispatch($eventDiscountCodeSubscriber::DISCOUNT_CODE_APPLY_ERROR);
  475.             return $res;
  476.         }
  477.         if (null === $discountCodeName || '' === $discountCodeName) {
  478.             return $res;
  479.         }
  480.         $discount $this->em->getRepository(DiscountCode::class)->getActiveDiscountCodeByName($discountCodeName);
  481.         if (!$discount instanceof DiscountCode) {
  482.             $res['message'] = "flashMessages.site.promoCodeNotExists";
  483.             $event = new DiscountCodeErrorEvent($discountCodeName$res['message']);
  484.             $this->eventDispatcher->dispatch($eventDiscountCodeSubscriber::DISCOUNT_CODE_APPLY_ERROR);
  485.             return $res;
  486.         }
  487.         try {
  488.             $this->checkCanApplyCodeToCart($discount$cart);
  489.         } catch (DiscountCodeException $e) {
  490.             $event = new DiscountCodeErrorEvent($discount->getDiscountCodeName(), $e->getMessage(), $e);
  491.             $this->eventDispatcher->dispatch($eventDiscountCodeSubscriber::DISCOUNT_CODE_APPLY_ERROR);
  492.             $res['message'] = $e->getMessage();
  493.             return $res;
  494.         }
  495.         $cart->setDiscountCode($discount);
  496.         $this->em->persist($cart);
  497.         $this->em->flush();
  498.         $res['status'] = 'success';
  499.         $res['message'] = 'flashMessages.site.promoCodeAddedInAccount';
  500.         // save discount code for future carts
  501.         $this->requestStack->getSession()->set(self::DISCOUNT_CODE_KEY$discount->getDiscountCodeName());
  502.         return $res;
  503.     }
  504.     /**
  505.      * @param DiscountCode $discount
  506.      * @param Cart $cart
  507.      * @return mixed
  508.      * @throws Exception
  509.      * @throws DiscountCodeNotValidException
  510.      * @throws DiscountCodeExpiredException
  511.      * @throws DiscountCodeWrongUserException
  512.      * @throws DiscountCodeAlreadyUsedException
  513.      */
  514.     public function checkCanApplyCodeToCart(DiscountCode $discountCart $cart)
  515.     {
  516.         if (!$cart->getUser() instanceof User) {
  517.             throw new DiscountCodeNotValidException();
  518.         }
  519.         if (!$cart->allowDiscountCode()) {
  520.             throw new DiscountCodeNotApplicableException();
  521.         }
  522.         if ($this->discountCodeManager->isDiscountCodeExpired($discount)) {
  523.             throw new DiscountCodeExpiredException();
  524.         }
  525.         // checking discount code aff.
  526.         $user $cart->getUser();
  527.         $discountAffiliate $discount->getAffiliate();
  528.         if ($discountAffiliate instanceof Affiliate) {
  529.             $discountCodeHasSameAff $this->discountCodeManager->discountCodeHasSameAff($discountAffiliate$cart$user);
  530.             if (!$discountCodeHasSameAff) {
  531.                 throw new DiscountCodeNotValidException();
  532.             }
  533.         }
  534.         // checking if Promo code is attached to the scenario that user already received an email for.
  535.         if (count($discount->getMailingScenarios())) {
  536.             $userReceivedScenarioWithPromoCode $this->discountCodeManager->discountCodeHasSameMailingScenario($discount$user);
  537.             if (!$userReceivedScenarioWithPromoCode) {
  538.                 throw new DiscountCodeNotValidException("flashMessages.site.promoCodeNotForThisCart");
  539.             }
  540.         }
  541.         // checking if its SINGLE_USAGE
  542.         if (!$this->discountCodeManager->canApplySingleUsageCode($discount)) {
  543.             throw new DiscountCodeAlreadyUsedException();
  544.         }
  545.         // checking SINGLE_USER_USAGE
  546.         if ($cart->getUser() instanceof User) {
  547.             try {
  548.                 $this->discountCodeManager->canCodeBeUsedByUser($cart->getUser(), $discount);
  549.             } catch (DiscountCodeAlreadyUsedException $e) {
  550.                 throw new DiscountCodeAlreadyUsedException($e->getMessage());
  551.             } catch (DiscountCodeNotValidRegistrationException $e) {
  552.                 throw new DiscountCodeNotValidRegistrationException($this->translator->trans('flashMessages.site.promoCodeNotValidReg', ['{{ date }}' => $discount->getRegisteredUserDateLimit()->format('d-m-Y')], TranslationManager::TRANSLATION_DOMAIN_FLASH));
  553.             }
  554.         }
  555.         //checking SINGLE_USER_USAGE and if discount code has user and its for that specific user.
  556.         if ($cart->getUser() instanceof User && $discount->getUser() instanceof User) {
  557.             try {
  558.                 $this->discountCodeManager->canCodeBeUsedByUser($cart->getUser(), $discounttrue);
  559.             } catch (DiscountCodeAlreadyUsedException $e) {
  560.                 throw new DiscountCodeAlreadyUsedException($e->getMessage());
  561.             } catch (DiscountCodeWrongUserException $e) {
  562.                 throw new DiscountCodeWrongUserException($e->getMessage());
  563.             } catch (DiscountCodeNotValidRegistrationException $e) {
  564.                 throw new DiscountCodeNotValidRegistrationException($this->translator->trans('flashMessages.site.promoCodeNotValidReg', ['{{ date }}' => $discount->getRegisteredUserDateLimit()->format('d-m-Y')], TranslationManager::TRANSLATION_DOMAIN_FLASH));
  565.             }
  566.         }
  567.         // checking if its MULTIPLE_USAGE
  568.         if (DiscountCode::MULTIPLE_USAGE === $discount->getDiscountCodeUsage()) {
  569.             return true;
  570.         }
  571.         return true;
  572.     }
  573.     /**
  574.      * @param User $user
  575.      * @param Cart $cart
  576.      * @return array
  577.      * @throws Exception
  578.      */
  579.     public function discountCodeApplying(User $userCart $cart)
  580.     {
  581.         $redirectRoute 'brulafine_shipping';
  582.         if (!$cart) {
  583.             $redirectRoute 'brulafine_show_packs';
  584.         }
  585.         $routeParams = [];
  586.         if (!$user instanceof User) {
  587.             $redirectRoute 'brulafine_cart';
  588.             $routeParams = ['packTemplateId' => 123];
  589.         }
  590.         $discountCodeName $this->getActualDiscountCode($cart);
  591.         $res $this->addDiscountCode($cart$discountCodeName);
  592.         $status 'error';
  593.         $message $res['message'];
  594.         $cartTotal $this->computeCartTotal($cart);
  595.         if ('success' === $res['status']) {
  596.             $status 'info';
  597.             $message "flashMessages.site.discountCodeIsApplied";
  598.         }
  599.         return $result = [
  600.             'redirectRoute' => $redirectRoute,
  601.             'routeParams' => $routeParams,
  602.             'status' => $status,
  603.             'message' => $message,
  604.             'discountCodeName' => $discountCodeName,
  605.             'cartTotal' => $cartTotal,
  606.             'cart' => $cart,
  607.         ];
  608.     }
  609.     /**
  610.      * @param Cart $cart
  611.      * @return bool
  612.      */
  613.     public function cartIsEligibleForFreePurchase(Cart $cart)
  614.     {
  615.         $cartTotal $this->computeCartTotal($cart);
  616.         $discountCode $cart->getDiscountCode();
  617.         $cartCreditNote $cart->hasCreditNoteAssigned(truetrue);
  618.         if (== $cartTotal['packGrandTotal']) {
  619.             if ($discountCode instanceof DiscountCode && DiscountCode::FREE_PURCHASE === $discountCode->getDiscountCodeType()) {
  620.                 return true;
  621.             }
  622.             if ($cartCreditNote instanceof CartCreditNote) {
  623.                 return true;
  624.             }
  625.         }
  626.         return false;
  627.     }
  628.     /**
  629.      * @param User $user
  630.      * @param Cart $cart
  631.      * @return array
  632.      * @throws Exception
  633.      */
  634.     public function creditNoteApplying(Cart $cart)
  635.     {
  636.         $status 'info';
  637.         $message "The credit note is applied!";
  638.         $cartTotal = [];
  639.         try {
  640.             $cartTotal $this->computeCartTotal($cart);
  641.             $this->refreshPrices($cart);
  642.         } catch (Exception $e) {
  643.             $status 'error';
  644.             $message 'We could not applied the CN because: '$e->getMessage();
  645.             $this->logger->critical($message);
  646.         }
  647.         return $result = [
  648.             'status' => $status,
  649.             'message' => $message,
  650.             'cartTotal' => $cartTotal,
  651.             'cart' => $cart,
  652.         ];
  653.     }
  654.     /**
  655.      * @param $item
  656.      * @param int|null $product_type
  657.      * @return Bill
  658.      */
  659.     public function getBill($itemint $product_type null): Bill
  660.     {
  661.         if ($item instanceof CartInterface) {
  662.             $cartBill $this->getCartBill($item);
  663.             return $cartBill;
  664.         }
  665.         if ($item instanceof PackProductInterface) {
  666.             $productBill $this->getPackProductBill($item);
  667.             return $productBill;
  668.         }
  669.         if (!$item instanceof PackInterface) {
  670.             throw new TypeError('Unknown item');
  671.         }
  672.         if (null === $product_type) {
  673.             return $this->getPackBill($item);
  674.         }
  675.         foreach ($item->getPackProducts() as $packProduct) {
  676.             if ($product_type === $packProduct->getProduct()->getProductType()) {
  677.                 return $this->getPackProductTypeBill($item$product_type);
  678.             }
  679.         }
  680.         throw new InvalidArgumentException('Unknown product type');
  681.     }
  682.     /**
  683.      * @param Cart|null $cart
  684.      * @return array
  685.      */
  686.     public function computeCartTotal(CartInterface $cart null): array
  687.     {
  688.         if (null === $cart) {
  689.             return [
  690.                 'packPrice' => 0,
  691.                 'packPriceReduced' => 0,
  692.                 'packPriceDiscountRate' => 0,
  693.                 'packPriceDiscount' => 0,
  694.                 'packPromoDiscount' => 0,
  695.                 'packPriceShipping' => 0,
  696.                 'packGrandTotal' => 0,
  697.                 'pack' => [],
  698.             ];
  699.         }
  700.         $bill $this->getBill($cart);
  701.         $total = [
  702.             'packPrice' => $bill->getBaseAmount(),
  703.             'packPriceReduced' => $bill->getReducedAmount(),
  704.             'packPriceDiscountRate' => $bill->getDiscountRate(),
  705.             'packPriceDiscount' => $bill->getDiscountAmount(),
  706.             'packPromoDiscount' => $bill->getPromoAmount(),
  707.             'packPriceShipping' => $bill->getShippingAmount(),
  708.             'packGrandTotal' => $bill->getTotalAmount(),
  709.             'creditNoteAmount' => $bill->getCreditNoteAmount(),
  710.             'payedAfterCN' => $bill->getPayedAfterCNAmount(),
  711.             'pack' => [],
  712.         ];
  713.         foreach ($cart->getPacks() as $pack) {
  714.             foreach ($pack->getPackProducts() as $packProduct) {
  715.                 $packProductBill $this->getBill($packProduct);
  716.                 $total['pack'][$pack->getPackId()][$packProduct->getProduct()->getProductId()] = [
  717.                     'price' => $packProductBill->getBaseAmount(),
  718.                     'priceReduced' => $packProductBill->getReducedAmount(),
  719.                     'priceDiscount' => $packProductBill->getDiscountAmount(),
  720.                     'priceEffective' => $packProductBill->getAmount(),
  721.                 ];
  722.             }
  723.         }
  724.         return $total;
  725.     }
  726.     /**
  727.      * Retuns the bills associated to a given Cart
  728.      *
  729.      * @param Cart|null $cart
  730.      * @param BillingAccount|null $billingAccount
  731.      * @param UserPaymentToken|null $userPaymentToken
  732.      * @param STSClient|null $stsClient
  733.      * @param false $disableSmartRouting
  734.      * @return array
  735.      */
  736.     public function computeCartBills(
  737.         CartInterface $cart null,
  738.         BillingAccount $billingAccount null,
  739.         UserPaymentToken $userPaymentToken null,
  740.         STSClient $stsClient null,
  741.         $disableSmartRouting false
  742.     ): array {
  743.         $bills = [];
  744.         if (null === $cart) {
  745.             return $bills;
  746.         }
  747.         $totalShipping $this->getBill($cart)->getShippingAmount();
  748.         // les ELECTRONIC_PRODUCT sont billes sur un mid Ã  part tout le reste (y compris le shipping sur les autres)
  749.         // le Cart::Discount s'applique a la fin sur le montant total non ELECTRONIC
  750.         foreach ($cart->getPacks() as $pack) {
  751.             foreach ($pack->getPackProducts() as $packProduct) {
  752.                 /** @var Product $product */
  753.                 $product $packProduct->getProduct();
  754.                 // todo analyse limitation (only 2 pacounts 1 shipping one electronic ?? )
  755.                 // 1 rebill plan par type also
  756.                 if (!isset($bills[$product->getProductType()])) {
  757.                     $bill $this->getBill($pack$product->getProductType());
  758.                     if (Product::SHIPPIED_PRODUCT === $product->getProductType()) {
  759. //                        $bill = $bill->withShippingAmount($totalShipping);
  760. //                        i don`t know why we used that before, since getBill is applying everything needed
  761.                         $bill $this->getBill($cart);
  762.                     }
  763.                     // If no BA is provided, we take the one specified for this product.
  764.                     $finalBillingAccount $billingAccount ?? $product->getBillingAccount();
  765.                     if (!$finalBillingAccount instanceof BillingAccount) {
  766.                         throw new \LogicException("You must provide a BA !");
  767.                     }
  768.                     // there is a list with supported countries for Hipay.
  769.                     // if the user`s Country Token is from diff country and the final BA is Hipay we select randomly another BA.
  770.                     $userIpCountry $this->geoIPManager->getCountryCode($cart->getUser()->getUserIp());
  771.                     if ($userPaymentToken instanceof UserPaymentToken) {
  772.                         $paymentCountry $userPaymentToken->getCountryToken();
  773.                     } else {
  774.                         $paymentCountry $userIpCountry;
  775.                     }
  776.                     if ($this->shouldApplyHipayRule($finalBillingAccount$paymentCountry$userIpCountry)) {
  777.                         $finalBillingAccount $this->applyHipayRule($finalBillingAccount);
  778.                     }
  779.                     if ($finalBillingAccount->getBillingAccountIs3ds()) {
  780.                         $final3dsBillingAccount $finalBillingAccount;
  781.                         $finalBillingAccount $final3dsBillingAccount->getLinkedNormalAccount();
  782.                     } else {
  783.                         $final3dsBillingAccount $finalBillingAccount->getLinked3dsAccount();
  784.                     }
  785.                     // If the 3ds account doesn't exists, ALERT THE DEVS
  786.                     if (!$final3dsBillingAccount instanceof BillingAccount) {
  787.                         $final3dsBillingAccount $finalBillingAccount;
  788.                         $this->logger->critical("You must provide a 3DS BA ! => nothing linked to {$finalBillingAccount->getBillingAccountName()} found in 3ds accounts.");
  789.                     }
  790.                     $bills[$product->getProductType()] = [
  791.                         'totalAmount' => $bill->getTotalAmount(),
  792.                         'productType' => $product->getProductType(),
  793.                         'billingCurrency' => $product->getProductCurrency(),
  794.                         'billingAccount' => $finalBillingAccount,
  795.                         'billingRebillRule' => $product->getBillingRebillRule(),
  796.                         'billingPriority' => $product->getProductBillingPriority(),
  797.                         'productId' => $product->getProductId(),
  798.                         'billingAccount3ds' => $final3dsBillingAccount,
  799.                         'billTrBillingAccount' => $billingAccount,
  800.                     ];
  801.                 }
  802.             }
  803.         }
  804.         // sort bills Electronic first
  805.         usort($bills, function (array $item1, array $item2): int {
  806.             return $item2['billingPriority'] <=> $item1['billingPriority'];
  807.         });
  808.         // we are applying the billing type of the cart here to find the billing account.
  809.         if ($cart->getCartBillingType() === Cart::BILLING_TYPE_ALTERNATIVE || $cart->getCartBillingType() === Cart::BILLING_TYPE_FREE) {
  810.             if ($cart->getCartBillingType() === Cart::BILLING_TYPE_ALTERNATIVE) {
  811.                 $typeTofind AlternativeBilling::ALTERNATIVE_BILLING_TYPE_TRANSFER;
  812.             }
  813.             if ($cart->getCartBillingType() === Cart::BILLING_TYPE_FREE) {
  814.                 $typeTofind AlternativeBilling::ALTERNATIVE_BILLING_TYPE_FREE;
  815.             }
  816.             $billingAccountAlternate $this->em->getRepository(AlternativeBilling::class)->findOneBy([
  817.                 'alternativeBillingType' => $typeTofind,
  818.             ]);
  819.             if ($billingAccountAlternate instanceof AlternativeBilling) {
  820.                 $alternateBillingAccount $this->em->getRepository(BillingAccount::class)->findOneBy([
  821.                     'billingAccountId' => $billingAccountAlternate->getBillingAccount(),
  822.                 ]);
  823.             } else {
  824.                 $alternateBillingAccount null;
  825.             }
  826.             if (null !== $alternateBillingAccount) {
  827.                 return $this->addBillingAccount($bills$alternateBillingAccount);
  828.             }
  829.         }
  830.         if (false === $disableSmartRouting && $this->is_active_smart_routing_billing && null !== $userPaymentToken && null !== ($token $userPaymentToken->getPaymentToken()->getToken())) {
  831.             return $this->addSmartRoutingBillingAccount($bills$stsClient ?? new STSClient(), $token);
  832.         }
  833.         return $bills;
  834.     }
  835.     /**
  836.      * @param $finalBillingAccount
  837.      * @param $paymentCountry
  838.      * @return bool
  839.      */
  840.     public function shouldApplyHipayRule($finalBillingAccount$paymentCountry$userIPCountry): bool
  841.     {
  842.         $allowedHipayCountries = ["FR""MC""LU""CH""BE"];
  843.         $allHipayBillingAccounts $this->billingAccountManager->getAllBillingAccountByName('HIPAY@');
  844.         $isHiPay false;
  845.         foreach ($allHipayBillingAccounts as $account) {
  846.             if ($account['billingAccountId'] == $finalBillingAccount->getBillingAccountId()) {
  847.                 $isHiPay true;
  848.             }
  849.         }
  850.         // if card country or user Ip country is diff than the allowed ones - apply the rule
  851.         if ($isHiPay && (!in_array($paymentCountry$allowedHipayCountries) || !in_array($userIPCountry$allowedHipayCountries))) {
  852.             return true;
  853.         }
  854.         return false;
  855.     }
  856.     /**
  857.      * @param $finalBillingAccount
  858.      * @return BillingAccount
  859.      */
  860.     public function applyHipayRule($finalBillingAccount)
  861.     {
  862.         $allOtherBas $this->billingAccountManager->getAllBillingAccounts('HIPAY@'$finalBillingAccount->getBillingAccountType());
  863.         $key array_rand($allOtherBas);
  864.         return $allOtherBas[$key];
  865.     }
  866.     /**
  867.      * @return object|void
  868.      */
  869.     public function resolveCurrentCart()
  870.     {
  871.         if ($this->requestStack) {
  872.             $currentCartId $this->requestStack->getSession()->get('currentCartId');
  873.         }
  874.         if (!isset($currentCartId)) {
  875.             $this->logger->debug('Can not take cartId from session:  ' $currentCartId);
  876.             return;
  877.         }
  878.         /** @var Cart $currentCart */
  879.         $currentCart $this->em
  880.             ->getRepository(Cart::class)
  881.             ->findOneBy(['cartId' => $currentCartId'site' => $this->siteManager->getCurrentSite()]);
  882.         if (!$currentCart instanceof Cart) {
  883.             $this->logger->debug('Can not find the cart with ID: ' $currentCartId ' Cart result: ' serialize($currentCart));
  884.             return;
  885.         }
  886.         $this->refreshCartPrices($currentCart);
  887.         return $currentCart;
  888.     }
  889.     public function makePackTemplateDiscounts(PackTemplate $pack)
  890.     {
  891.         $totalPrice 0;
  892.         $totalPriceReduced 0;
  893.         $productCount 0;
  894.         foreach ($pack->getPackTemplateProducts() as $pTP) {
  895.             $productCount $pTP->getProductQuantity();
  896.             $productPrice $pTP->getProduct()->getProductPrice();
  897.             $totalPrice += $productPrice;
  898.             $productDiscount $pTP->getProduct()->getProductDiscount();
  899.             list($productPriceDiscountRate$productPriceReduced) = $productDiscount->applyDiscount($productCount$productPrice);
  900.             $totalPriceReduced += $productPriceReduced;
  901.             $pTP->setPackTemplateProductPriceReduced($productPriceReduced);
  902.             $pTP->getProduct()->setProductPriceDiscountRate($productPriceDiscountRate);
  903.         }
  904.         list($packPriceDiscountRate$packPriceReduced$shippingPrice) =
  905.             $this->packFactory->applyDiscount($pack->getPackTemplateDiscount(), $productCount$totalPriceReduced);
  906.         $pack->setPackTemplatePriceDiscountRate($packPriceDiscountRate);
  907.         $pack->setPackTemplatePriceReduced($totalPriceReduced);
  908.         $pack->setPackTemplatePrice($totalPrice);
  909.     }
  910.     /**
  911.      * @param Setup $setup
  912.      * @param int $packTemplateId
  913.      * @param array $items
  914.      * @return mixed
  915.      */
  916.     public function getCustomTemplatePack(Setup $setupint $packTemplateId, array $items)
  917.     {
  918.         $items array_filter($items, function ($value) {
  919.             return $value >= && $value <= self::CUSTOM_PACK_MAX_SINGLE_ITEMS;
  920.         });
  921.         $defaultProposedPacks $this->packFactory->getDefaultProposedPacks($setup->getSite());
  922.         $selectKey null;
  923.         foreach ($defaultProposedPacks as $key => $packs) {
  924.             if ($packTemplateId == $packs->getPackTemplate()->getPackTemplateId()) {
  925.                 $selectedKey $key;
  926.                 break;
  927.             }
  928.         }
  929.         if (null !== $selectedKey) {
  930.             return $this
  931.                 ->packFactory
  932.                 ->updateProposedPack($defaultProposedPacks$selectedKey$items)[$selectedKey];
  933.         }
  934.     }
  935.     /**
  936.      * @param Setup $setup
  937.      * @param $packIndex
  938.      * @param $packProductIndex
  939.      * @param $quantity
  940.      */
  941.     public function updateCartProductQuantity(Setup $setup$packIndex$packProductIndex$quantity)
  942.     {
  943.         $quantity filter_var(
  944.             $quantity,
  945.             FILTER_VALIDATE_INT,
  946.             ['options' => ['min_range' => 0'max_range' => 6]]
  947.         );
  948.         if (false === $quantity) {
  949.             throw new OutOfRangeException('invalid product quantity');
  950.         }
  951.         $currentCart $setup->getCart();
  952.         if (!$currentCart instanceof Cart) {
  953.             throw new InvalidArgumentException('no cart found');
  954.         }
  955.         $packs $currentCart->getPacks();
  956.         if (!isset($packs[$packIndex])) {
  957.             throw new OutOfRangeException('invalid pack index');
  958.         }
  959.         $packProducts $packs[$packIndex]->getPackProducts();
  960.         if (!isset($packProducts[$packProductIndex])) {
  961.             throw new OutOfRangeException('invalid pack product index');
  962.         }
  963.         /** @var PackProduct $product */
  964.         $product $packProducts[$packProductIndex];
  965.         $oldQuantity $product->getProductQuantity();
  966.         $product->setProductQuantity($quantity);
  967.         $newQuantity $product->getProductQuantity();
  968.         // Fire the event for eventual processing
  969.         $event = new CartItemQuantityUpdatedEvent($currentCart$product$oldQuantity$newQuantity);
  970.         $this->eventDispatcher->dispatch($eventCartSubscriber::CART_ITEM_QTY_UPDATED);
  971.         $this->refreshCartPrices($currentCart);
  972.         $this->em->flush();
  973.     }
  974.     /**
  975.      * @param Cart $cart
  976.      * @return mixed
  977.      */
  978.     public function getCurrentPack(Cart $cart)
  979.     {
  980.         $defaultProposedPacks $this->packFactory->getDefaultProposedPacks($cart->getSite());
  981.         $packName $cart->getPacks()[0]->getPackName();
  982.         foreach ($defaultProposedPacks as $sitePackTemplate) {
  983.             if ($packName == $sitePackTemplate->getPackTemplate()->getPackTemplateName()) {
  984.                 return $sitePackTemplate->getPackTemplate();
  985.             }
  986.         }
  987.     }
  988.     /**
  989.      * @param Setup $setup
  990.      * @return array
  991.      */
  992.     public function getNextPackTemplate(Cart $cart)
  993.     {
  994.         $packs $this->packFactory->getDefaultProposedPacks($cart->getSite());
  995.         // filter the pack to exclude the abtest ones.
  996.         $defaultProposedPacks $this->ABTestManager->getPacksForTracking($this->trackingFactory->getCurrentTracking(), $packs);
  997.         $packTemplates $this->getPushPack($defaultProposedPacks$cart);
  998.         return [
  999.             'packTemplates' => $packTemplates,
  1000.             'hasCoaching' => (int)$this->hasCoaching($cart),
  1001.         ];
  1002.     }
  1003.     /**
  1004.      * @param array $result
  1005.      * @param Cart $cart
  1006.      * @param Setup $setup
  1007.      * @return array
  1008.      */
  1009.     public function generatePossibleNextPacksData(array $resultCart $cartSetup $setup)
  1010.     {
  1011.         $data = [];
  1012.         foreach ($result['packTemplates'] as $packTemplate) {
  1013.             $nextCart $this->makeTemporaryCart(
  1014.                 $packTemplate->getPackTemplate(),
  1015.                 $setup->getSite(),
  1016.                 $setup->getTracking(),
  1017.                 $setup->getAffiliate(),
  1018.                 [],
  1019.                 $result['hasCoaching']
  1020.             );
  1021.             $nextCart->setUser($cart->getUser());
  1022.             try {
  1023.                 $nextCart->setTracking($cart->getTracking());
  1024.             } catch (Exception $e) {
  1025.                 $nextCart->setTracking(null);
  1026.                 $this->logger->critical("{$this->environment} - {$this->siteManager->getCurrentSite()->getSiteName()} - could not set tracking in cart with exception : {$e->getMessage()}");
  1027.             }
  1028.             $nextCart->setCartShippingCountry($cart->getCartShippingCountry());
  1029.             $nextCart->setDiscountCode($cart->getDiscountCode());
  1030.             $nextCart->setShippingRule($cart->getShippingRule());
  1031.             $nextCart->setPickUpPoint($cart->getPickUpPoint());
  1032.             $nextCartTotal $this->computeCartTotal($nextCart);
  1033.             $this->packFactory->makePackTemplateDiscounts($packTemplate->getPackTemplate());
  1034.             $data[] = [
  1035.                 'nextPack' => $packTemplate->getPackTemplate(),
  1036.                 'hasCoaching' => $result['hasCoaching'],
  1037.                 'nextCart' => $nextCart,
  1038.                 'nextCartTotal' => $nextCartTotal,
  1039.                 'nextCartBill' => $this->getCartBill($nextCart)
  1040.             ];
  1041.         }
  1042.         return $data;
  1043.     }
  1044.     public function getCartFromId(int $cartId nullUser $user nullstring $state '')
  1045.     {
  1046.         //no cartId nothing to do here
  1047.         if (null === $cartId) {
  1048.             throw new \RuntimeException('Undefined cart Id');
  1049.         }
  1050.         $params = ['cartId' => $cartId];
  1051.         if ('' != $state) {
  1052.             $params['cartState'] = $state;
  1053.         }
  1054.         $cart $this->cartRepository->findOneBy($params);
  1055.         //no cart found nothing to do here
  1056.         if (!$cart) {
  1057.             throw new \RuntimeException(sprintf('No cart found for the given parameters %s'json_encode($params)));
  1058.         }
  1059.         if (!$user instanceof User) {
  1060.             return $cart;
  1061.         }
  1062.         if ($cart->getUser() != $user) {
  1063.             throw new \RuntimeException('The cart does not belong to the submitted user');
  1064.         }
  1065.         $this->refreshCartPrices($cart);
  1066.         return $cart;
  1067.     }
  1068.     /**
  1069.      * @param Setup $setup
  1070.      * @param int $packTemplateId
  1071.      * @param int $hasCoaching
  1072.      * @param UserAddressManager $userAddressManager
  1073.      * @return Cart
  1074.      */
  1075.     public function getPaymentCart(Setup $setupint $packTemplateIdint $hasCoachingUserAddressManager $userAddressManager)
  1076.     {
  1077.         $cart $setup->getCart();
  1078.         $checkFailedPurchase $this->request->getSession()->get(CartManager::CHECK_FAILED_PURCHASE_KEY);
  1079.         if ($checkFailedPurchase) {
  1080.             $user $setup->getUser();
  1081.             if (!$user instanceof User && $this->requestStack->getSession()->has(User::GUEST_USER_SESSION_KEY)) {
  1082.                 $user $this->em->getRepository(User::class)->findOneBy(['id' => $this->request->getSession()->get(User::GUEST_USER_SESSION_KEY)]);
  1083.             }
  1084.             $lastFailedPaidCart $user->getLastCart(true);
  1085.             if ($lastFailedPaidCart instanceof Cart) {
  1086.                 $cart $lastFailedPaidCart;
  1087.                 $hasDcodeParam $this->request->getSession()->get(CartManager::DISCOUNT_CODE_KEY);
  1088.                 if ($hasDcodeParam && !$cart->getDiscountCode() instanceof DiscountCode) {
  1089.                     try {
  1090.                         $this->addDiscountCode($cart$hasDcodeParam);
  1091.                     } catch (Exception $e) {
  1092.                         $this->logger->critical('We could not add promo code: ' $hasDcodeParam ' to the cart: ' $cart->getCartId() . ' Because: ' $e->getMessage());
  1093.                     }
  1094.                 }
  1095.                 $orderFailedCheckedEvent = new OrderFailedCheckedEvent($cart$this->request);
  1096.                 $this->eventDispatcher->dispatch($orderFailedCheckedEventOrderSubscriber::CHECKED_FOR_FAILED_ORDER);
  1097.             }
  1098.         }
  1099.         if (!$cart instanceof Cart) {
  1100.             throw new \RuntimeException('No cart was found');
  1101.         }
  1102.         $user $setup->getUser();
  1103.         if (!$user instanceof User) {
  1104.             $user $cart->getUser();
  1105.         }
  1106.         if (!$user instanceof User) {
  1107.             throw new \RuntimeException('No user found');
  1108.         }
  1109.         $state $cart->getCartState();
  1110.         if (Cart::STATE_ADDRESS_ASSIGNED !== $state) {
  1111.             if ($cart->isPaid()) {
  1112.                 throw new CartAlreadyPaidException("Cart is already paid but shouldn't, returning exception {$cart->getCartId()}");
  1113.             } elseif (Cart::STATE_FREEZE_PENDING_PAYMENT == $state) {
  1114.                 $this->transitState($cart'paymentAlternateFailed');
  1115.             } else {
  1116.                 throw new \RuntimeException(sprintf('Expected cart to be in %s state but cart is in %s state'Cart::STATE_ADDRESS_ASSIGNED$state));
  1117.             }
  1118.         }
  1119.         if ($cart->getUser() != $user) {
  1120.             throw new \RuntimeException('User does not belong to current cart');
  1121.         }
  1122.         if ($packTemplateId 1) {
  1123.             if (!$this->checkUserIsEligibleForCoaching($user) || == $hasCoaching) {
  1124.                 $this->em->persist($cart);
  1125.                 foreach ($cart->getPacks()[0]->getPackProducts() as $packProduct) {
  1126.                     if ($packProduct->getProduct()->getProductName() === 'Coaching') {
  1127.                         $packProduct->setProductQuantity(0);
  1128.                         break;
  1129.                     }
  1130.                 }
  1131.                 $this->em->flush();
  1132.             }
  1133.             return $cart;
  1134.         }
  1135.         $items = [];
  1136.         $packTemplate $this->em
  1137.             ->getRepository(PackTemplate::class)
  1138.             ->findOneBy(['packTemplateId' => $packTemplateId]);
  1139.         if (!$packTemplate) {
  1140.             return $cart;
  1141.         }
  1142.         $discount $cart->getDiscountCode();
  1143.         $oldCartAddress $cart->getCartAddress();
  1144.         $oldCartAddress->setCart(null);
  1145.         $this->cleanUpCurrentCart($cart);
  1146.         // I am not sure why we redo completely the cart here ...?
  1147.         $newCart $this->makeCartFromPackTemplate(
  1148.             $packTemplate,
  1149.             $setup->getSite(),
  1150.             $setup->getTracking(),
  1151.             $setup->getAffiliate(),
  1152.             $items,
  1153.             $user,
  1154.             $oldCartAddress,
  1155.             $cart->hasCoaching()
  1156.         );
  1157.         $tracking $user->getTracking();
  1158.         $this->em->persist($tracking);
  1159.         $newCart->setUser($user);
  1160.         $newCart->setTracking($tracking);
  1161.         $newCart->setDiscountCode($discount);
  1162.         $newCart->setShippingRule($cart->getShippingRule());
  1163.         $newCart->setPickUpPoint($cart->getPickUpPoint());
  1164.         // add Unique CartShippingId
  1165.         if (null == $newCart->getCartShippingId()) {
  1166.             $newCart->setCartShippingId(sprintf("CT{$this->invoiceSiteId}%'.09d"$newCart->getCartId()));
  1167.         }
  1168.         if (!$this->checkUserIsEligibleForCoaching($user) || == $hasCoaching) {
  1169.             foreach ($newCart->getPacks()[0]->getPackProducts() as $packProduct) {
  1170.                 if ($packProduct->getProduct()->getProductName() === 'Coaching') {
  1171.                     $packProduct->setProductQuantity(0);
  1172.                     break;
  1173.                 }
  1174.             }
  1175.         }
  1176.         // If the cart has no address here, we need to create one.
  1177.         if (!$newCart->getCartAddress() instanceof CartAddress) {
  1178.             $cartAddress $userAddressManager->getAddressForCart($user->getAddress());
  1179.             $cartAddress->setCart($newCart);
  1180.             $newCart->setCartAddress($cartAddress);
  1181.         } elseif (!$userAddressManager->checkAddressIsSameForCart($user->getAddress(), $newCart->getCartAddress())) {
  1182.             $userAddressManager->updateCartAddress($newCart->getCartAddress(), $user->getAddress());
  1183.         }
  1184.         $this->transitState($newCart'clientIdentification');
  1185.         $this->transitState($newCart'clientAddressFilling');
  1186.         $this->transitState($newCart'payment');
  1187.         $this->em->flush();
  1188.         return $newCart;
  1189.     }
  1190.     /**
  1191.      * @param Cart $cart
  1192.      * @return bool
  1193.      */
  1194.     public function hasCoaching(Cart $cart): bool
  1195.     {
  1196.         $packProducts $cart->getPacks()[0]->getPackProducts();
  1197.         foreach ($packProducts as $packProduct) {
  1198.             if ($packProduct->getProduct()->getProductName() === 'Coaching' && $packProduct->getProductQuantity() > 0) {
  1199.                 return true;
  1200.             }
  1201.         }
  1202.         return false;
  1203.     }
  1204.     /**
  1205.      * @param Cart $cart
  1206.      * @return bool
  1207.      */
  1208.     public function removeCoaching(Cart $cart)
  1209.     {
  1210.         if ($cart->getPacks()[0] instanceof Pack) {
  1211.             /** @var Pack $pack */
  1212.             $pack $cart->getPacks()[0];
  1213.             // can't remove coaching from a forced coaching pack.
  1214.             if ($pack->getPackTemplate() instanceof PackTemplate && $pack->getPackTemplate()->isCoachingForced()) {
  1215.                 return;
  1216.             }
  1217.             $packProducts $cart->getPacks()[0]->getPackProducts();
  1218.             /** @var PackProduct $packProduct */
  1219.             foreach ($packProducts as $packProduct) {
  1220.                 if ($packProduct->getProduct()->getProductName() === 'Coaching' && $packProduct->getProductQuantity() > 0) {
  1221.                     $packProduct->setProductQuantity(0);
  1222.                 }
  1223.             }
  1224.         }
  1225.     }
  1226.     /**
  1227.      * @param User $user
  1228.      * @param bool $shipped
  1229.      * @return mixed
  1230.      */
  1231.     public function getLastPaidCart(User $user$shipped false)
  1232.     {
  1233.         $lastPaidCart null;
  1234.         $method $shipped 'isShipped' 'isPaidButNotShipped';
  1235.         /** @var Cart $cart */
  1236.         foreach ($user->getCarts() as $cart) {
  1237.             if (!$cart->$method()) {
  1238.                 continue;
  1239.             }
  1240.             if (null === $lastPaidCart) {
  1241.                 $lastPaidCart $cart;
  1242.                 continue;
  1243.             }
  1244.             if ($cart->getCartCreateAt() > $lastPaidCart->getCartCreateAt()) {
  1245.                 $lastPaidCart $cart;
  1246.             }
  1247.         }
  1248.         return $lastPaidCart;
  1249.     }
  1250.     /**
  1251.      * Check if the user already made an order recently (default 30 mins) that is not shipped.
  1252.      *
  1253.      * @param User $user
  1254.      * @param \DateInterval $delayToCheck (default 30 mins)
  1255.      * @param bool $shipped (default false)
  1256.      * @return bool
  1257.      * @throws Exception
  1258.      */
  1259.     public function hasOrderInDelay(User $user\DateInterval $delayToCheck null$shipped false)
  1260.     {
  1261.         if (!$delayToCheck instanceof \DateInterval) {
  1262.             $delayToCheck = new \DateInterval("PT30M");
  1263.         }
  1264.         $lastOrder $this->getLastPaidCart($user$shipped);
  1265.         if (!$lastOrder instanceof Cart) {
  1266.             return false;
  1267.         }
  1268.         $dateLimit = new \DateTime();
  1269.         $dateLimit->sub($delayToCheck);
  1270.         if ($lastOrder->getPaymentTransaction() instanceof Transaction && $lastOrder->getPaymentTransaction()->getTransactionStamp() >= $dateLimit) {
  1271.             return true;
  1272.         }
  1273.         return false;
  1274.     }
  1275.     /**
  1276.      * @param \DateInterval|null $interval (default 1 month)
  1277.      * @param \DateTime $endDate
  1278.      * @return array
  1279.      * @throws \Doctrine\DBAL\DBALException
  1280.      */
  1281.     public function getSoldProductsForPeriod(\DateInterval $interval null\DateTime $endDate null)
  1282.     {
  1283.         if (!$interval instanceof \DateInterval) {
  1284.             $interval = new \DateInterval("P1M");
  1285.         }
  1286.         if (!$interval instanceof \DateTime) {
  1287.             $endDate = new \DateTime('now');
  1288.         }
  1289.         $period Period::createFromDurationBeforeEnd($endDate$interval);
  1290.         $startPeriod $period->getStartDate()->format('Y-m-d');
  1291.         $endPeriod $period->getEndDate()->format('Y-m-d');
  1292.         // check if result exist in cache, if yes return it
  1293.         if ('prod' == $this->environment && $this->redisCache->exists('stats_stock_'.$startPeriod.'_'.$endPeriod)) {
  1294.             $sells json_decode($this->redisCache->get('stats_stock_'.$startPeriod.'_'.$endPeriod), true);
  1295.         } else {
  1296.             // if not request it from db
  1297.             $sells $this->cartRepository
  1298.                 ->getAllShippedProductsForPeriod($period);
  1299.             // create the cache for 4 hours
  1300.             $this->redisCache->setex('stats_stock_'.$startPeriod.'_'.$endPeriod14400json_encode($sells));
  1301.         }
  1302.         return $sells;
  1303.     }
  1304.     /**
  1305.      * @return array
  1306.      * @throws Exception
  1307.      */
  1308.     public function getStatsArray()
  1309.     {
  1310.         //get Products for periods: month, week, day
  1311.         $soldProductsLastMonth $this->getSoldProductsForPeriod();
  1312.         $soldProductsLastWeek $this->getSoldProductsForPeriod($interval = new \DateInterval('P1W'));
  1313.         $soldProductsLastDay $this->getSoldProductsForPeriod($interval = new \DateInterval('P1D'));
  1314.         $stats = [];
  1315.         if (!empty($soldProductsLastMonth)) {
  1316.             foreach ($soldProductsLastMonth as $soldProducts) {
  1317.                 $stats[$soldProducts['productCode']] = [
  1318.                     "productName" => $soldProducts['productName'],
  1319.                     "productCode" => $soldProducts['productCode'],
  1320.                     "soldItemsLastMonth" => $soldProducts['totalSells'],
  1321.                     "averageSellsPerDay" => round($soldProducts['totalSells']/302),
  1322.                     "averageSellsPerWeek" => round(($soldProducts['totalSells']/30)*72),
  1323.                 ];
  1324.             }
  1325.         }
  1326.         if (!empty($soldProductsLastWeek)) {
  1327.             foreach ($soldProductsLastWeek as $soldProducts) {
  1328.                 $stats[$soldProducts['productCode']]["soldItemsLastWeek"] = $soldProducts['totalSells'];
  1329.                 $stats[$soldProducts['productCode']]["estimatedSellsNextMonth"] = round(($soldProducts['totalSells'] / 7) * 30);
  1330.             }
  1331.         }
  1332.         if (!empty($soldProductsLastDay)) {
  1333.             foreach ($soldProductsLastDay as $soldProducts) {
  1334.                 $stats[$soldProducts['productCode']]["soldItemsLastDay"] = $soldProducts['totalSells'];
  1335.             }
  1336.         }
  1337.         return $stats;
  1338.     }
  1339.     /**
  1340.      * @param $shippingId
  1341.      * @param $state
  1342.      * @return Cart|object|null
  1343.      */
  1344.     public function getCartByShippingId($shippingId$state null)
  1345.     {
  1346.         $params = ['cartShippingId' => $shippingId];
  1347.         if (null !== $state) {
  1348.             $params ['cartState'] = $state;
  1349.         }
  1350.         return $this->cartRepository->findOneBy($params);
  1351.     }
  1352.     /**
  1353.      * @param Cart $cart
  1354.      * @param Tracking $tracking
  1355.      */
  1356.     public function updateTrackingInfos(Cart $cartTracking $tracking)
  1357.     {
  1358.         $cart->setTracking($tracking);
  1359.         $cart->setAffiliate($tracking->getAff());
  1360.         // clean the current cart AB tests.
  1361.         if (count($cart->getAbTests())) {
  1362.             /** @var CartHasABTest $abTest */
  1363.             foreach ($cart->getAbTests() as $abTest) {
  1364.                 $abTest->setCart(null);
  1365.                 $this->em->remove($abTest);
  1366.             }
  1367.             $cart->setAbTests(new ArrayCollection());
  1368.         }
  1369.         // put the active AB tests of this new tracking.
  1370.         if (count($tracking->getAbTests())) {
  1371.             /** @var TrackingHasABTest $trackingABTest */
  1372.             foreach ($tracking->getAbTests() as $trackingABTest) {
  1373.                 if ($trackingABTest->getSiteABTest()->isActive()) {
  1374.                     if (!$trackingABTest->getSiteABTest()->isForCartSwitch() || ($trackingABTest->getSiteABTest()->isForCartSwitch() && $this->ABTestManager->cartCanReceiveSwitchTest($cart$cart->getSite(), $trackingABTest->getSiteABTest()))) {
  1375.                         $cartABTest = new CartHasABTest();
  1376.                         $cartABTest->setCart($cart);
  1377.                         $cartABTest->setVersion($trackingABTest->getVersion());
  1378.                         $cartABTest->setSiteABTest($trackingABTest->getSiteABTest());
  1379.                         $this->em->persist($cartABTest);
  1380.                         $cart->addABTest($cartABTest);
  1381.                     }
  1382.                 }
  1383.             }
  1384.         }
  1385.     }
  1386.     /**
  1387.      * @param Cart $cart
  1388.      * @param $status
  1389.      */
  1390.     public function updateCartStatus(Cart $cart$status)
  1391.     {
  1392.         $cart->setCartState($status);
  1393.         $this->em->flush();
  1394.     }
  1395.     /**
  1396.      * @param Cart $cart
  1397.      * @return string
  1398.      */
  1399.     public function getEncodedId(Cart $cart)
  1400.     {
  1401.         return $this->shortIdGenerator->encode($cart->getCartId());
  1402.     }
  1403.     /**
  1404.      * @param $encodedId
  1405.      * @return string
  1406.      */
  1407.     public function getDecodedId($encodedId)
  1408.     {
  1409.         return $this->shortIdGenerator->decode($encodedId);
  1410.     }
  1411.     /**
  1412.      * @param User $user
  1413.      * @return bool
  1414.      */
  1415.     public function checkUserIsEligibleForCoaching(User $user)
  1416.     {
  1417.         $dateTime = (new \DateTime('now'))->sub(new \DateInterval('P90D'));
  1418.         if ($this->userManager->haveRunningRebillManager($user)
  1419.             || $this->userManager->hasSuccessfulTransactionForCoaching($user$dateTime)
  1420.             || $this->userManager->hasRefundOrChargebackForCoaching($user)
  1421.         ) {
  1422.             return false;
  1423.         }
  1424.         return true;
  1425.     }
  1426.     /**
  1427.      * @param Cart $cart
  1428.      * @param RouterInterface $router
  1429.      * @return array
  1430.      */
  1431.     public function getOrderInfoFromCartForAvisVerifie(Cart $cartRouterInterface $router)
  1432.     {
  1433.         /** @var User $user */
  1434.         $user $cart->getUser();
  1435.         /** @var UserAddress $address */
  1436.         $userAddress $user->getAddress();
  1437.         $transactions $cart->getTransactions();
  1438.         if (!count($transactions)) {
  1439.             return [];
  1440.         }
  1441.         /** @var Transaction $transaction */
  1442.         $transaction $transactions[0];
  1443. //        if (BillingAccount::BILLING_ACCOUNT_TYPE_DUMMY == $transaction->getBillingAccount()->getBillingAccountType() || $transaction->isRealTest()) {
  1444. //            return [];
  1445. //        }
  1446.         $productsArray = [
  1447.             'products' => []
  1448.         ];
  1449.         $context $router->getContext();
  1450.         $context->setHost($this->webSiteHost);
  1451.         $packsUrl $router->generate('brulafine_show_packs', [], UrlGeneratorInterface::ABSOLUTE_URL);
  1452.         $homeUrl $router->generate('brulafine_home', [], UrlGeneratorInterface::ABSOLUTE_URL);
  1453.         /** @var Pack $pack */
  1454.         foreach ($cart->getPacks() as $pack) {
  1455.             if ($pack->getPackTemplate()->isUpsell()) {
  1456.                 continue;
  1457.             }
  1458.             if (Pack::AUTO_PACK == $pack->getPackType()) {
  1459.                 $packOptions $pack->getPackOptions();
  1460.                 if (empty($packOptions)) {
  1461.                     return [];
  1462.                 }
  1463.                 if (!isset($packOptions['EAN']) || !isset($packOptions['avis_reference'])) {
  1464.                     $this->logger->critical("Pack:" $pack->getPackId() . " and Pack template: " $pack->getPackTemplate()->getPackTemplateId() .
  1465.                         " does not have one of the following options: 'avis_reference', 'EAN'!");
  1466.                     return [];
  1467.                 }
  1468.                 $urlProductImage '';
  1469.                 switch ($this->siteName) {
  1470.                     case "brulafine":
  1471.                         $urlProductImage $homeUrl 'kits/produits/x' $pack->getPackOptions()['month'] . '.png';
  1472.                         break;
  1473.                     case "slimalis":
  1474.                     case "nocticalm":
  1475.                         $urlProductImage '';
  1476.                         break;
  1477.                 }
  1478.                 $productsArray['products'][] = [
  1479.                     'id_product' => $packOptions['avis_reference'],
  1480.                     'name_product' => $pack->getPackName(),
  1481.                     'url_product' => $packsUrl,
  1482.                     'url_product_image' => $urlProductImage,
  1483.                     'GTIN_EAN' => $packOptions['EAN'],
  1484.                     'brand_name' => $this->brandName
  1485.                 ];
  1486.             } elseif (Pack::CUSTOM_PACK == $pack->getPackType()) {
  1487.                 /** @var PackProduct $packProduct */
  1488.                 foreach ($pack->getPackProducts() as $packProduct) {
  1489.                     if (Product::ELECTRONIC_PRODUCT == $packProduct->getProduct()->getProductType()) {
  1490.                         continue;
  1491.                     }
  1492.                     $productOptions $packProduct->getProduct()->getProductOptions();
  1493.                     if (empty($productOptions)) {
  1494.                         return [];
  1495.                     }
  1496.                     if (!isset($productOptions['EAN'])
  1497.                         || !isset($productOptions['avis_reference'])
  1498.                     ) {
  1499.                         $this->logger->critical("Product: " $packProduct->getProduct()->getProductId() .
  1500.                             " does not have one of the following options: 'avis_reference', 'EAN'!");
  1501.                         return [];
  1502.                     }
  1503.                     if ($packProduct->getProductQuantity() > 0) {
  1504.                         $urlProductImage '';
  1505.                         switch ($this->siteName) {
  1506.                             case "brulafine":
  1507.                                 $urlProductImage $homeUrl 'kits/produits/' strtolower($packProduct->getProduct()->getProductName()) . '.png';
  1508.                                 break;
  1509.                             case "slimalis":
  1510.                             case "nocticalm":
  1511.                                 $urlProductImage '';
  1512.                                 break;
  1513.                         }
  1514.                         $productsArray['products'][] = [
  1515.                             'id_product' => $productOptions['avis_reference'],
  1516.                             'name_product' => $packProduct->getProduct()->getProductName(),
  1517.                             'url_product' => $packsUrl,
  1518.                             'url_product_image' => $urlProductImage,
  1519.                             'GTIN_EAN' => $productOptions['EAN'],
  1520. //                            'MPN' => $productOptions['avis_mpn'],
  1521.                             'brand_name' => $this->brandName,
  1522.                         ];
  1523.                     }
  1524.                 }
  1525.             }
  1526.         }
  1527.         return [
  1528.             'order_ref'        => $this->shortIdGenerator->encode($cart->getCartId()),
  1529.             'email'            => $user->getEmail(),
  1530.             'lastname'         => $userAddress->getLastName(),
  1531.             'firstname'        => $userAddress->getFirstName(),
  1532.             'order_date'       => $transaction->getTransactionStamp()->format('Y-m-d h:i:s'),
  1533.             'delay_product'    => AvisVerifies::DELAY_PRODUCT_VALUE,
  1534.             'PRODUCTS'         => $productsArray['products']
  1535.         ];
  1536.     }
  1537.     /**
  1538.      * @param Cart $cart
  1539.      * @return boolean
  1540.      */
  1541.     public function fixCartMissingAddress(Cart $cart)
  1542.     {
  1543.         if ($cart->getCartAddress() instanceof CartAddress) {
  1544.             return false;
  1545.         }
  1546.         $userAddress null;
  1547.         /** @var Cart $otherCart */
  1548.         foreach ($cart->getUser()->getCarts() as $otherCart) {
  1549.             if ($cart->getCartId() <= $otherCart->getCartId()) {
  1550.                 continue;
  1551.             }
  1552.             if ($otherCart->getCartAddress() instanceof CartAddress) {
  1553.                 $userAddress $cart->getCartAddress();
  1554.             }
  1555.         }
  1556.         if (null === $userAddress) {
  1557.             $userAddress $this->userAddressManager->getAddressForCart($cart->getUser()->getAddress());
  1558.         }
  1559.         $cart->setCartAddress($userAddress);
  1560.         $this->em->flush();
  1561.     }
  1562.     /**
  1563.      * This function is used to check that the pack in the cart is allowed to be purchased.
  1564.      * This is now necessary due to the conditional packs.
  1565.      *
  1566.      * @param Cart $cart
  1567.      * @return bool|FailedPackCondition
  1568.      */
  1569.     public function isCartPurchasePossible(Cart $cart$conditionType PackTemplate::DISPLAY_CONDITION UserPaymentToken $userPaymentToken null)
  1570.     {
  1571.         $packs $cart->getPacks();
  1572.         /** @var Pack $pack */
  1573.         foreach ($packs as $pack) {
  1574.             $result $this->packFactory->isConditionalPackTemplatePassingConditions($pack->getPackTemplate(), $conditionType$cart$userPaymentToken);
  1575.             if ($result instanceof FailedPackCondition) {
  1576.                 return $result;
  1577.             }
  1578.         }
  1579.         return true;
  1580.     }
  1581.     /**
  1582.      * This function is used to identify potential rm that should have been created but that were not
  1583.      * So it is finding carts with coaching, that should have rm (not same token) and list them.
  1584.      * @param string $delay
  1585.      */
  1586.     public function findCartWithoutCreatedRebillManagers($delay "P30D")
  1587.     {
  1588.         return $this->cartRepository->findCartsWithoutRmButWithCoaching($delay);
  1589.     }
  1590.     /**
  1591.      * @param Cart $cart
  1592.      * @param string $action
  1593.      */
  1594.     public function addNewAction(Cart $cartstring $action)
  1595.     {
  1596.         if (!$cart->hasEvent($action)) {
  1597.             $cartAction $this->cartActionRepository->createNewAction($action);
  1598.             $cart->addAction($cartAction);
  1599.             $this->em->flush();
  1600.         }
  1601.     }
  1602.     /**
  1603.      * @param Cart $cart
  1604.      * @return bool
  1605.      */
  1606.     public function cartIsEligibleForPreUpsell(Cart $cart): bool
  1607.     {
  1608.         $preUpsellPack $this->getLinkedPreUpsellFromCart($cart);
  1609.         if ($cart->getSite()->hasPreUpsells()
  1610.             && $preUpsellPack instanceof SitePackTemplate
  1611.             && !$cart->hasPreUpsellTransactions()
  1612.             && !$cart->hasAction(CartAction::REFUSED_PREUPSELL)
  1613.         ) {
  1614.             return true;
  1615.         }
  1616.         return false;
  1617.     }
  1618.     /**
  1619.      * @param Cart $cart
  1620.      * @return null|SitePackTemplate
  1621.      */
  1622.     public function getLinkedPreUpsellFromCart(Cart $cart): ?SitePackTemplate
  1623.     {
  1624.         $preUpsellPack null;
  1625.         $pack $cart->getPacks()[0];
  1626.         /** @var PackTemplate $linked */
  1627.         $linked $pack->getPackTemplate()->getLinkedPreUpsellPackTemplates();
  1628.         $sitePackTemplates = [];
  1629.         /** @var PackTemplate $pack */
  1630.         foreach ($linked as $packTemplate) {
  1631.             if (isset($packTemplate->getPackTemplateOptions()['pack_type']) && $packTemplate->getPackTemplateOptions()['pack_type'] == 'offer') {
  1632.                 continue;
  1633.             }
  1634.             /** @var SitePackTemplate $sitePackTemplate */
  1635.             $sitePackTemplate $packTemplate->getSitePackTemplates()[0];
  1636.             if (!$sitePackTemplate->isActive()) {
  1637.                 continue;
  1638.             }
  1639.             $sitePackTemplates[] = $sitePackTemplate;
  1640.         }
  1641.         $personalisedPack $sitePackTemplates;
  1642.         if (count($personalisedPack) && $personalisedPack[0] instanceof SitePackTemplate) {
  1643.             $linked $personalisedPack[0]->getPackTemplate();
  1644.         }
  1645.         if ($linked instanceof PackTemplate) {
  1646.             $preUpsellPack $this->packFactory->getPreUpsellPack($linked);
  1647.         }
  1648.         return $preUpsellPack;
  1649.     }
  1650.     /**
  1651.      * @param Cart $cart
  1652.      * @param string $format
  1653.      * $format is a random string (you can choose abbr),
  1654.      * for now we only cover the format "qXn" - means quantityXname => 1xBrulafine, 2xC-Konjac, 3xBrulafine1xC-Konjac etc.
  1655.      * @return array|string
  1656.      */
  1657.     public function extractProductsFromCart(Cart $cartstring $format 'qXn')
  1658.     {
  1659.         $cartPacks $cart->getPacks();
  1660.         $tempArray = [];
  1661.         /** @var Pack $pack */
  1662.         foreach ($cartPacks as $pack) {
  1663.             $packProducts $pack->getPackProducts();
  1664.             /** @var PackProduct $packProduct */
  1665.             foreach ($packProducts as $packProduct) {
  1666.                 $product $packProduct->getProduct();
  1667.                 if ($product->getProductType() == Product::ELECTRONIC_PRODUCT || == $packProduct->getProductQuantity()) {
  1668.                     continue;
  1669.                 }
  1670.                 $productQuantity $packProduct->getProductQuantity();
  1671.                 $productName $packProduct->getProduct()->getProductName();
  1672.                 if (!key_exists($productName$tempArray)) {
  1673.                     $tempArray[$productName] = $productQuantity;
  1674.                 } else {
  1675.                     $tempArray[$productName] = $tempArray[$productName] + $productQuantity;
  1676.                 }
  1677.             }
  1678.         }
  1679.         // we can extend this, by given format to have diff return result
  1680.         if ('qXn' == $format) {
  1681.             $string '';
  1682.             foreach ($tempArray as $product => $quantity) {
  1683.                 $string .= $quantity 'x' $product;
  1684.             }
  1685.             return  $string;
  1686.         }
  1687.         // by default, we return the array.
  1688.         return $tempArray;
  1689.     }
  1690.     /**
  1691.      * @param User $user
  1692.      * @param PackTemplate $packTemplate
  1693.      * @return array
  1694.      */
  1695.     public function createFreeCartForUserByPackTemplate(User $userPackTemplate $packTemplate)
  1696.     {
  1697.         $tracking $user->getTracking();
  1698.         /** @var Cart $lastCart */
  1699.         $lastCart $user->getLastOrder();
  1700.         $cartAddress $lastCart->getCartAddress();
  1701.         $duplicateCartAddress = clone $cartAddress;
  1702.         $aff $lastCart->getAffiliate();
  1703.         $site $user->getRegisteredFrom();
  1704.         $packTemplate $this->em
  1705.             ->getRepository(PackTemplate::class)
  1706.             ->findOneBy(['packTemplateId' => $packTemplate->getPackTemplateId()]);
  1707.         $cart $this->makeCartFromPackTemplate($packTemplate$site$tracking$aff, [], $user$duplicateCartAddress);
  1708.         $this->removeCoaching($cart);
  1709.         $cart->setCartState(Cart::STATE_ADDRESS_ASSIGNED);
  1710.         $purchase = new Purchase();
  1711.         $purchase->setPaymentMethod(Purchase::PAYMENT_FREE);
  1712.         $cart->setCartBillingType(Cart::BILLING_TYPE_FREE);
  1713.         $this->em->flush();
  1714.         $userPayment = new CardPayment($user->getUserIp(), $user->getUserAgent());
  1715.         return [
  1716.             "cart" => $cart,
  1717.             "site" => $site,
  1718.             "userPayment" => $userPayment,
  1719.             "purchase" => $purchase,
  1720.         ];
  1721.     }
  1722.     /**
  1723.      * @param User $user
  1724.      * @param $promoAmount
  1725.      * @return bool
  1726.      */
  1727.     public function createPromoCodeForUser(User $user$promoAmount$generationType DiscountCodeManager::DISCOUNT_CODE_NAME_GENERATION_TYPE_CREDIT_NOTE)
  1728.     {
  1729.         try {
  1730.             $this->discountCodeManager->addNewDiscountCodeFromSupportToUser($user$promoAmount$generationType);
  1731.             $created true;
  1732.         } catch (Exception $exception) {
  1733.             $this->logger->critical("Failed to create a promo code from the support for user: " $user->getId() . " and exception: " $exception->getMessage());
  1734.             $created false;
  1735.         }
  1736.         return $created;
  1737.     }
  1738.     /**
  1739.      * @param Cart $cart
  1740.      * @param Cart $refundedCart
  1741.      * @param User $supportUser
  1742.      */
  1743.     public function addCartSupportRefund(
  1744.         Cart $cart,
  1745.         Cart $refundedCart,
  1746.         User $supportUser
  1747.     ) {
  1748.         $cartRefundedBySupport = new CartSupportRefund();
  1749.         $cartRefundedBySupport->setCartForRefund($cart);
  1750.         $cartRefundedBySupport->setRefundedCart($refundedCart);
  1751.         $cartRefundedBySupport->setSupportBy($supportUser);
  1752.         $cartRefundedBySupport->setCreatedDate(new \DateTime('now'));
  1753.         $this->em->persist($cartRefundedBySupport);
  1754.         $this->em->flush();
  1755.     }
  1756.     /**
  1757.      * @param Cart $refundedCart
  1758.      * @return bool
  1759.      */
  1760.     public function cartAlreadyRefundedBySupport(Cart $refundedCart)
  1761.     {
  1762.         $found $this->em->getRepository(CartSupportRefund::class)->findOneBy(["refundedCart" => $refundedCart]);
  1763.         if ($found instanceof CartSupportRefund) {
  1764.             return true;
  1765.         }
  1766.         return false;
  1767.     }
  1768.     /**
  1769.      * @param Cart $lastOrder
  1770.      * @param Setup $setup
  1771.      * @param Packages $package
  1772.      * @param Request $request
  1773.      * @return array
  1774.      */
  1775.     public function getUpsellOfferData(Cart $lastOrderSetup $setupPackages $packageRequest $request): array
  1776.     {
  1777.         $template 'offer_upsell.html.twig';
  1778.         $locale $request->getLocale();
  1779.         $packForCart null;
  1780.         $createCart false;
  1781.         $upsellProducts = [];
  1782.         if ($lastOrder->getSite()->hasUpsells()) {
  1783.             $upsellPacks $this->packFactory->getUpsellPacks(true);
  1784.             $upsellProducts $this->ABTestManager->getPacksForTracking($setup->getTracking(), $upsellPacks);
  1785.         }
  1786.         $selectedProduct null;
  1787.         $selectedId = (int) $request->request->get('id');
  1788.         if (!empty($upsellProducts) && null != $selectedId) {
  1789.             foreach ($upsellProducts as $upsellProduct) {
  1790.                 if ($upsellProduct->getSitePackTemplateId() === $selectedId) {
  1791.                     $selectedProduct $upsellProduct;
  1792.                     break;
  1793.                 }
  1794.             }
  1795.         }
  1796.         if ($selectedProduct instanceof SitePackTemplate) {
  1797.             $packForCart $selectedProduct->getPackTemplate();
  1798.             $createCart true;
  1799.         }
  1800.         // we create a cart for upsell pack when:
  1801.         // 1. Summary modal is displayed.
  1802.         // OR
  1803.         // 2. Quantity is changed
  1804.         $selectedQuantity = (int) $request->request->get('quantity');
  1805.         if ($request->request->get('action') == "showSummaryModal" || $selectedQuantity) {
  1806.             $createCart true;
  1807.         }
  1808.         $upsellProductsArrayForTwig = [];
  1809.         $upsellProductsArrayForTwigWithKeys = [];
  1810.         foreach ($upsellProducts as $upsellProduct) {
  1811.             /** @var PackTemplate $packTemplate */
  1812.             $packTemplate $upsellProduct->getPackTemplate();
  1813.             $options $packTemplate->getPackTemplateOptions();
  1814.             $title $options['title'][$locale];
  1815.             $product = [
  1816.                 'id' => $upsellProduct->getSitePackTemplateId(),
  1817.                 'name' => $packTemplate->getPackTemplateName(),
  1818.                 'title' => $title,
  1819.                 'desc_short' => $options['short_desc'][$locale],
  1820.                 'description_medium' => $options['medium_desc'][$locale],
  1821.                 'reduction_percent' => $options['reduc_percent'],
  1822.                 'price' => $options['normal_price'],
  1823.                 'reducted_price' => $options['reduc_price'],
  1824.                 'quantity' => 1,
  1825.                 'image' => $package->getUrl($options['image_link'][$locale]),
  1826.                 'image_large' => $package->getUrl($options['image_large_link'][$locale]),
  1827.                 'category' => $options['category'][$locale],
  1828.                 'category_color' => $options['category_color'],
  1829.             ];
  1830.             if (isset($options['suggest_pack'])) {
  1831.                 $product['pack'] = $options['suggest_pack'];
  1832.                 $product['pack_modal_title'] = $options['modal_title'][$locale];
  1833.             }
  1834.             $upsellProductsArrayForTwig[] = $product;
  1835.             $upsellProductsArrayForTwigWithKeys[$product['name']] = $product;
  1836.         }
  1837.         return [
  1838.             'template' => $template,
  1839.             'packForCart' => $packForCart,
  1840.             'createCart' => $createCart,
  1841.             'templateData' => [
  1842.                 'upsellProducts' => $upsellProducts,
  1843.                 'upsellProductsArray' => $upsellProductsArrayForTwigWithKeys,
  1844.                 'upsellProductsJs' => json_encode($upsellProductsArrayForTwig),
  1845.                 'lastFour' => $lastOrder->getPaymentTransaction()->getPaymentToken() instanceof PaymentToken $lastOrder->getPaymentTransaction()->getPaymentToken()->getLastFour() : PaymentToken::DEFAULT_EMPTY_LAST_FOUR,
  1846.             ]
  1847.         ];
  1848.     }
  1849.     /**
  1850.      * @param Cart $lastOrder
  1851.      * @param Setup $setup
  1852.      * @return array
  1853.      */
  1854.     public function getPreUpsellOfferData(Cart $lastOrderSetup $setup): array
  1855.     {
  1856.         $template 'offer_pre_upsale.html.twig';
  1857.         $packForCart null;
  1858.         $createCart false;
  1859.         $preUpsellPack null;
  1860.         $packTemplate null;
  1861.         if ($lastOrder->getSite()->hasPreUpsells()) {
  1862.             /** @var Pack $pack */
  1863.             $pack $lastOrder->getPacks()[0];
  1864.             /** @var PackTemplate $linked */
  1865.             $linked $pack->getPackTemplate()->getLinkedPreUpsellPackTemplates();
  1866.             $sitePackTemplates = [];
  1867.             /** @var PackTemplate $pack */
  1868.             foreach ($linked as $pack) {
  1869.                 $sitePackTemplates[] = $pack->getSitePackTemplates()[0];
  1870.             }
  1871.             $personalisedPack $this->ABTestManager->getPacksForTracking($setup->getTracking(), $sitePackTemplates);
  1872.             if (count($personalisedPack) && $personalisedPack[0] instanceof SitePackTemplate) {
  1873.                 /** @var SitePackTemplate $pack */
  1874.                 foreach ($personalisedPack as $pack) {
  1875.                     $packTemplateOptions $pack->getPackTemplate()->getPackTemplateOptions();
  1876.                     if (isset($packTemplateOptions['pack_type']) && 'offer' == $packTemplateOptions['pack_type']) {
  1877.                         $packTemplate $pack->getPackTemplate();
  1878.                     }
  1879.                 }
  1880.             }
  1881.             if ($packTemplate instanceof PackTemplate) {
  1882.                 $preUpsellPack $this->packFactory->getPreUpsellPack($packTemplate);
  1883.                 $packForCart $packTemplate;
  1884.                 $createCart true;
  1885.             }
  1886.         }
  1887.         return [
  1888.             'template' => $template,
  1889.             'packForCart' => $packForCart,
  1890.             'createCart' => $createCart,
  1891.             'templateData' => [
  1892.                 "preUpsellPack" => $preUpsellPack
  1893.             ]
  1894.         ];
  1895.     }
  1896.     /**
  1897.      * @param Cart $offerCart
  1898.      * @param Request $request
  1899.      */
  1900.     public function updateOfferCartProductQuantity(Cart $offerCartRequest $request)
  1901.     {
  1902.         $selectedQuantity = (int) $request->request->get('quantity');
  1903.         if ($selectedQuantity UpsellCart::MAX_SINGLE_ITEM) {
  1904.             $selectedQuantity 10;
  1905.         }
  1906.         if ($selectedQuantity 0) {
  1907.             $selectedQuantity 1;
  1908.         }
  1909.         /** @var Pack $pack */
  1910.         $pack $offerCart->getPacks()[0];
  1911.         /** @var PackProduct $packProduct */
  1912.         foreach ($pack->getPackProducts() as $packProduct) {
  1913.             $packProduct->setProductQuantity($selectedQuantity);
  1914.         }
  1915.         $this->refreshCartPrices($offerCart);
  1916.         $this->em->flush();
  1917.     }
  1918.     /**
  1919.      * @param Cart $cart
  1920.      * @return SitePackTemplate|mixed
  1921.      */
  1922.     public function getSitePackTemplateForCart(Cart $cart)
  1923.     {
  1924.         /** @var Pack $cartPack */
  1925.         $cartPack $cart->getPacks()[0];
  1926.         /** @var PackTemplate $packTempalte */
  1927.         $packTemplate $cartPack->getPackTemplate();
  1928.         $sitePackTemplates $packTemplate->getSitePackTemplates();
  1929.         /** @var Site $cartSite */
  1930.         $cartSite $cart->getSite();
  1931.         $sitePackTemplatesArray = [];
  1932.         /** @var SitePackTemplate $sitePackTemplate */
  1933.         foreach ($sitePackTemplates as $sitePackTemplate) {
  1934.             if ($cartSite !== $sitePackTemplate->getSite() || !$sitePackTemplate->isActive()) {
  1935.                 continue;
  1936.             }
  1937.             $sitePackTemplatesArray[] = $sitePackTemplate;
  1938.         }
  1939.         if (count($sitePackTemplatesArray) > 1) {
  1940.             $this->logger->critical('We found more than one Site Pack Templates! Check that: ' print_r($sitePackTemplatesArraytrue));
  1941.         }
  1942.         return $sitePackTemplatesArray[0];
  1943.     }
  1944.     // PRIVATE FUNCTIONS
  1945.     /**
  1946.      * Tell whether the current cart shippable state should be updated
  1947.      *
  1948.      * @param bool $state last shippable state
  1949.      * @param Transaction $transaction current transaction state
  1950.      *
  1951.      * @return bool
  1952.      */
  1953.     private function updateShippableState(bool $stateTransaction $transaction): bool
  1954.     {
  1955.         if (
  1956.             !$state
  1957.             && $transaction->getTransactionType() == Transaction::BILL
  1958.             && $transaction->getTransactionStatus() == Transaction::OK
  1959.             && $transaction->getTransactionAquirerReason() != 'Dummy OK 111'
  1960.             && $transaction->getBillingAccount()->getBillingAccountName() != 'MERKAV@DUMMY'
  1961.         ) {
  1962.             return true;
  1963.         }
  1964.         if ($state
  1965.             && $transaction->getTransactionStatus() == Transaction::OK
  1966.             && in_array($transaction->getTransactionType(), [Transaction::REFUNDTransaction::CHARGEBACK], true)
  1967.         ) {
  1968.             return false;
  1969.         }
  1970.         return $state;
  1971.     }
  1972.     /**
  1973.      * @param array $defaultProposedPacks
  1974.      * @param Cart $currentCart
  1975.      * @return array|void
  1976.      */
  1977.     private function getPossibleBiggerPacks(array $defaultProposedPacksCart $currentCart)
  1978.     {
  1979.         usort($defaultProposedPacks, function ($item1$item2) {
  1980.             return $item2->getSitePackTemplateOrder() <=> $item2->getSitePackTemplateOrder();
  1981.         });
  1982.         $possibleBiggerPacks = [];
  1983.         /** @var SitePackTemplate $defaultProposedPack */
  1984.         foreach ($defaultProposedPacks as $defaultProposedPack) {
  1985.             $packTemplate $defaultProposedPack->getPackTemplate();
  1986.             $packTemplateOptions $defaultProposedPack->getPackTemplate()->getPackTemplateOptions();
  1987.             $packTemplateOptionsMonth $packTemplateOptions['month'];
  1988.             if ($packTemplate->getPackTemplateType() == PackTemplate::CUSTOM_PACK) {
  1989.                 continue;
  1990.             }
  1991.             /** @var Pack $pack */
  1992.             foreach ($currentCart->getPacks() as $pack) {
  1993.                 if ($pack->getPackType() == Pack::CUSTOM_PACK) {
  1994.                     return;
  1995.                 }
  1996.                 $currentPackOptionMonth $pack->getPackOptions()['month'];
  1997.                 if ($currentPackOptionMonth $packTemplateOptionsMonth) {
  1998.                     $possibleBiggerPacks[] = $defaultProposedPack;
  1999.                 }
  2000.             }
  2001.         }
  2002.         if (empty($possibleBiggerPacks)) {
  2003.             return;
  2004.         }
  2005.         return $possibleBiggerPacks;
  2006.     }
  2007.     /**
  2008.      * @param array $defaultProposedPacks
  2009.      * @param Cart $currentCart
  2010.      * @return array|mixed|void|null
  2011.      */
  2012.     private function getPushPack(array $defaultProposedPacksCart $currentCart)
  2013.     {
  2014.         if ($currentCart->isPurchased()) {
  2015.             return;
  2016.         }
  2017.         $pack $currentCart->getPacks()[0];
  2018.         if (Pack::CUSTOM_PACK !== $pack->getPackType()) {
  2019.             return $this->getPossibleBiggerPacks($defaultProposedPacks$currentCart);
  2020.         }
  2021.         // we never reach that, because we dont show switch page on Custom Packs.
  2022.         // see cartSwitchAction
  2023.         // However, I refactored it to be compatible
  2024.         $maxQuantity 0;
  2025.         foreach ($pack->getPackProducts() as $packProduct) {
  2026.             if ($packProduct->getProductQuantity() > $maxQuantity) {
  2027.                 $maxQuantity $packProduct->getProductQuantity();
  2028.             }
  2029.         }
  2030.         if ($maxQuantity || $maxQuantity 1) {
  2031.             return;
  2032.         }
  2033.         /** @var SitePackTemplate $dPP */
  2034.         foreach ($defaultProposedPacks as $dPP) {
  2035.             $packTemplate $dPP->getPackTemplate();
  2036.             $packTemplateOptions $packTemplate->getPackTemplateOptions();
  2037.             if ($maxQuantity === $packTemplateOptions['month']) {
  2038.                 return $dPP;
  2039.             }
  2040.         }
  2041.         return null;
  2042.     }
  2043.     private function extractExternalQuantity(
  2044.         array $items,
  2045.         PackTemplateProduct $packTemplateProduct
  2046.     ): int {
  2047.         foreach ($items as $k => $v) {
  2048.             if (!preg_match('/^([0-9]+)_(.*)$/'$k$m)) {
  2049.                 continue;
  2050.             }
  2051.             if (!is_numeric($m[1])) {
  2052.                 continue;
  2053.             }
  2054.             if (!(is_numeric($v) && $v >= 0)) {
  2055.                 continue;
  2056.             }
  2057.             if ($m[1] == $packTemplateProduct->getPackTemplateProductId()) {
  2058.                 return (int)abs($v);
  2059.             }
  2060.         }
  2061.         return -1;
  2062.     }
  2063.     private function refreshPrices(CartInterface $cart)
  2064.     {
  2065.         /** @var PackInterface $pack */
  2066.         foreach ($cart->getPacks() as $pack) {
  2067.             $this->packFactory->makePackDiscounts($pack$cart->getUser(), $cart->getCartShippingCountry(), $cart->getShippingRule());
  2068.             $this->packFactory->makeProductTypeGroups($pack);
  2069.         }
  2070.     }
  2071.     private function refreshCartPrices(Cart $cart)
  2072.     {
  2073.         $bill $this->getBill($cart);
  2074.         $cart->setCartTotalAmount($bill->getReducedAmount());
  2075.         $cart->setCartTotalShippingAmount($bill->getShippingAmount());
  2076.     }
  2077.     /**
  2078.      * @param Cart $cart
  2079.      * @return Bill
  2080.      */
  2081.     private function getCartBill(CartInterface $cart): Bill
  2082.     {
  2083.         $price 0;
  2084.         $priceReduced 0;
  2085.         $priceShipping 0;
  2086.         $promoAmount 0;
  2087.         $creditNoteAmount 0;
  2088.         $payedAfterCN 0;
  2089.         $originalShippingAmount 0;
  2090.         $bills = [];
  2091.         $this->refreshPrices($cart);
  2092.         /** @var Pack $pack */
  2093.         foreach ($cart->getPacks() as $pack) {
  2094.             $bills[] = $this->getPackBill($pack);
  2095.         }
  2096.         foreach ($bills as $loopBill) {
  2097.             $price += $loopBill->getBaseAmount();
  2098.             $priceReduced += $loopBill->getReducedAmount();
  2099.             $priceShipping += $loopBill->getShippingAmount();
  2100.             $promoAmount += $loopBill->getPromoAmount();
  2101.             $creditNoteAmount += $loopBill->getCreditNoteAmount();
  2102.             $payedAfterCN += $loopBill->getPayedAfterCNAmount();
  2103.             $originalShippingAmount += $loopBill->getOriginalShippingAmount();
  2104.         }
  2105.         $bill = new Bill(Bill::TYPE_PACK1$price$priceReduced$promoAmount$priceShippingfalse$creditNoteAmount$payedAfterCN$originalShippingAmount);
  2106.         $billToReturn $bill->withPromoAmount($cart$this->cartRepository);
  2107.         // Determine wich tracking to use for AB tests
  2108.         if ('cli' !== php_sapi_name() && 'test' !== $this->environment && !$cart->getTracking() instanceof Tracking) {
  2109.             $tracking $this->trackingFactory->getCurrentTracking();
  2110.         } else {
  2111.             $tracking $cart->getTracking();
  2112.         }
  2113. //        if ($tracking instanceof Tracking && $this->ABTestManager->hasTest($tracking, SiteABTest::FREE_SHIPPING_ON)) {
  2114. //            $billWithShipping = $billWithPromo->withShippingAmount(0, $cart->getDiscountCode());
  2115. //        } else {
  2116.         $billToReturn $billToReturn->withShippingAmount($priceShipping$cart->getDiscountCode());
  2117. //        }
  2118.         $billToReturn $billToReturn->withCreditNote($cart);
  2119.         return $billToReturn;
  2120.     }
  2121.     private function getPackBill(PackInterface $pack): Bill
  2122.     {
  2123.         $cart $pack->getCart();
  2124.         $this->refreshPrices($cart);
  2125.         $affectedByPromocode true;
  2126.         if ($pack->getPackTemplate() instanceof PackTemplate && $pack->getPackTemplate()->isWithoutPromoCode()) {
  2127.             $affectedByPromocode false;
  2128.         }
  2129.         $bill = new Bill(Bill::TYPE_PACK1$pack->getPackPrice($pack), $pack->getPackPriceReduced(), 00$affectedByPromocode);
  2130.         $billWithPromo $bill->withPromoAmount($cart$this->cartRepository);
  2131.         $billWithShipping $billWithPromo->withShippingAmount($pack->getPackShippingPrice(), $cart->getDiscountCode());
  2132.         $billWithCreditNote $billWithShipping->withCreditNote($cart);
  2133.         return $billWithCreditNote;
  2134.     }
  2135.     private function getPackProductBill(PackProductInterface $packProduct): Bill
  2136.     {
  2137.         $cart $packProduct->getPack()->getCart();
  2138.         $this->refreshPrices($cart);
  2139.         $quantity $packProduct->getProductQuantity();
  2140.         $affectedByPromocode true;
  2141.         if ($packProduct->getPack()->getPackTemplate() instanceof PackTemplate && $packProduct->getPack()->getPackTemplate()->isWithoutPromoCode()) {
  2142.             $affectedByPromocode false;
  2143.         }
  2144.         $bill = new Bill(
  2145.             Bill::TYPE_PRODUCT,
  2146.             $quantity,
  2147.             $packProduct->getProduct()->getProductPrice() * $quantity,
  2148.             $packProduct->getPackProductPriceReduced() * $quantity,
  2149.             0,
  2150.             0,
  2151.             $affectedByPromocode
  2152.         );
  2153.         $billWithPromo $bill->withPromoAmount($cart$this->cartRepository);
  2154.         return $billWithPromo;
  2155.     }
  2156.     private function getPackProductTypeBill(PackInterface $packint $product_type): Bill
  2157.     {
  2158.         $cart $pack->getCart();
  2159.         $this->refreshPrices($cart);
  2160.         $price 0;
  2161.         $priceReduced 0;
  2162.         foreach ($pack->getPackProducts() as $packProduct) {
  2163.             if ($product_type !== $packProduct->getProduct()->getProductType()) {
  2164.                 continue;
  2165.             }
  2166.             $price += $packProduct->getProduct()->getProductPrice() * $packProduct->getProductQuantity();
  2167.             $priceReduced += $packProduct->getPackProductPriceReduced() * $packProduct->getProductQuantity();
  2168.         }
  2169.         $affectedByPromocode true;
  2170.         if ($pack->getPackTemplate() instanceof PackTemplate && $pack->getPackTemplate()->isWithoutPromoCode()) {
  2171.             $affectedByPromocode false;
  2172.         }
  2173.         $bill = new Bill(Bill::TYPE_PACK1$price$priceReduced00$affectedByPromocode);
  2174.         $billWithPromo $bill->withPromoAmount($cart$this->cartRepository);
  2175.         return $billWithPromo;
  2176.     }
  2177.     /**
  2178.      * Add a specific billing Account to all bills
  2179.      *
  2180.      * @param array $bills
  2181.      * @param BillingAccount $billingAccount
  2182.      *
  2183.      * @return array a collection of bill array
  2184.      */
  2185.     private function addBillingAccount(array $billsBillingAccount $billingAccount): array
  2186.     {
  2187.         foreach ($bills as &$bill) {
  2188.             $bill['billingAccount'] = $billingAccount;
  2189.         }
  2190.         unset($bill);
  2191.         return $bills;
  2192.     }
  2193.     /**
  2194.      * Add smart routed billing accounts to all bills
  2195.      *
  2196.      * @param array $bills
  2197.      * @param STSClient $stsClient
  2198.      * @param string $token Merkav Token returned from ADDCustomData
  2199.      *
  2200.      * @return array a collection of bill array
  2201.      */
  2202.     private function addSmartRoutingBillingAccount(array $billsSTSClient $stsClientstring $token): array
  2203.     {
  2204.         $cache = [];
  2205.         foreach ($bills as &$bill) {
  2206.             $bill $this->applySmartBillingAccount($bill$stsClient$token$cache);
  2207.         }
  2208.         unset($bill);
  2209.         return $bills;
  2210.     }
  2211.     /**
  2212.      * Add a Smart routing billing account to a single bill
  2213.      *
  2214.      * @param array $bill
  2215.      * @param STSClient $client
  2216.      * @param string $token Merkav Token returned from ADDCustomData
  2217.      * @param array &$cache Memory cache containing already resolved Smart Routing billing Account
  2218.      *
  2219.      * @return array
  2220.      */
  2221.     private function applySmartBillingAccount(array $billSTSClient $clientstring $token, array &$cache = []): array
  2222.     {
  2223.         //is smart routing enabled ?
  2224.         $smartBillingActivated $this->siteManager->getYamlConfigParameter('billing.smart_routing_activated');
  2225.         if (!$smartBillingActivated) {
  2226.             return $bill;
  2227.         }
  2228.         $currentBillingAccount $bill['billingAccount'] ?? null;
  2229.         // if there is no current BA return.
  2230.         if (!$currentBillingAccount instanceof BillingAccount) {
  2231.             return $bill;
  2232.         }
  2233.         // if this account doesn't support sr, ignore it
  2234.         if (!= $currentBillingAccount->getBillingAccountSmartRouting()) {
  2235.             return $bill;
  2236.         }
  2237.         if ($currentBillingAccount->getBillingAccountType() == BillingAccount::BILLING_ACCOUNT_TYPE_DUMMY && 'test' != $this->environment) {
  2238.             return $bill;
  2239.         }
  2240.         // don't use smart routing in case of free purchase (not for electronic products)
  2241.         if (Product::ELECTRONIC_PRODUCT != $bill["productType"] && === $bill['totalAmount']) {
  2242.             return $bill;
  2243.         }
  2244.         //electroning product factu is diff from shippied product facturation
  2245.         switch ($bill['productType']) {
  2246.             case Product::ELECTRONIC_PRODUCT:
  2247.                 $facturationName $this->siteManager->getCurrentSite()->getProperties()['smart-routing']['coaching'] ?? $this->siteManager->getYamlConfigParameter('billing.smart_routing_coaching_name');;
  2248.                 break;
  2249.             case Product::SHIPPIED_PRODUCT:
  2250.                 $facturationName $this->siteManager->getCurrentSite()->getProperties()['smart-routing']['products'] ?? $this->siteManager->getYamlConfigParameter('billing.smart_routing_pills_name');;
  2251.                 break;
  2252.             default:
  2253.                 $this->logger->info(sprintf('No facturation name was found for the product type : %s'$bill['productType']));
  2254.                 return $bill;
  2255.         }
  2256.         // Do we already have the cache ? If yes return the data.
  2257.         if (isset($cache[$facturationName]) && isset($cache['3ds'][$facturationName])) {
  2258.             $bill['billingAccount'] = $cache[$facturationName];
  2259.             $bill['billingAccount3ds'] = $cache['3ds'][$facturationName];
  2260.             return $bill;
  2261.         }
  2262.         $response $client->findMerkavBillingAccount($token$facturationName'BILL'$bill['totalAmount']);
  2263.         // did we get the smart routing info ?
  2264.         if ($response->isError()) {
  2265.             $this->logger->info(sprintf(
  2266.                 'Smart Billing Account not fetched for %s: %s %s %s',
  2267.                 $bill['billingAccount']->getBillingAccountName(),
  2268.                 $response->getData()['type'],
  2269.                 json_encode($response->getData()['request'], JSON_PRETTY_PRINT),
  2270.                 $response->getData()['response']
  2271.             ));
  2272.             $this->logger->critical("Smart Billing Error : Request => " print_r($response->getData()['request'], true) . ", Response : " print_r($response->getData()['response'], true));
  2273.             return $bill;
  2274.         }
  2275.         $data $response->getData();
  2276.         if ($response->isFail() || !isset($data['merkav_mid_id']) || !isset($data['merkav_mid_id3ds'])) {
  2277.             $this->logger->error(sprintf(
  2278.                 'Smart Billing Account not found for %s, found %s',
  2279.                 $bill['billingAccount']->getBillingAccountName(),
  2280.                 json_encode($dataJSON_PRETTY_PRINT)
  2281.             ));
  2282.             return $bill;
  2283.         }
  2284.         $smartBillingAccount $this->em->getRepository(BillingAccount::class)
  2285.             ->findOneBy(['billingAccountMerkavId' => $data['merkav_mid_id']]);
  2286.         if (null === $smartBillingAccount) {
  2287.             $this->logger->info(sprintf(
  2288.                 'Smart Billing Account not present in DB for %s: %s',
  2289.                 $bill['billingAccount']->getBillingAccountName(),
  2290.                 $data['merkav_mid_id']
  2291.             ));
  2292.             return $bill;
  2293.         }
  2294.         $smartBillingAccount3ds $this->em->getRepository(BillingAccount::class)
  2295.             ->findOneBy(['billingAccountMerkavId' => $data['merkav_mid_id3ds']]);
  2296.         if (!$smartBillingAccount3ds instanceof BillingAccount) {
  2297.             $this->logger->info(sprintf(
  2298.                 'Smart Billing Account 3DS not present in DB for %s: %s',
  2299.                 $bill['billingAccount']->getBillingAccountName(),
  2300.                 $data['merkav_mid_id3ds']
  2301.             ));
  2302.             return $bill;
  2303.         }
  2304.         $this->logger->info(sprintf(
  2305.             'Smart Billing Account used for %s is %s and 3ds is %s',
  2306.             $bill['billingAccount']->getBillingAccountName(),
  2307.             $smartBillingAccount->getBillingAccountName(),
  2308.             $smartBillingAccount3ds->getBillingAccountName()
  2309.         ));
  2310.         $cache[$facturationName] = $smartBillingAccount;
  2311.         $cache['3ds'][$facturationName] = $smartBillingAccount3ds;
  2312.         $bill['billingAccount'] = $smartBillingAccount;
  2313.         $bill['billingAccount3ds'] = $smartBillingAccount3ds;
  2314.         return $bill;
  2315.     }
  2316. }