<?php
namespace App\Services;
use App\Entity\Main\Affiliate;
use App\Entity\Main\AlternativeBilling;
use App\Entity\Main\Bill;
use App\Entity\Main\BillingAccount;
use App\Entity\Main\CardPayment;
use App\Entity\Main\Cart;
use App\Entity\Main\CartAction;
use App\Entity\Main\CartAddress;
use App\Entity\Main\CartCreditNote;
use App\Entity\Main\CartHasABTest;
use App\Entity\Main\CartInterface;
use App\Entity\Main\CartSupportRefund;
use App\Entity\Main\DiscountCode;
use App\Entity\Main\Pack;
use App\Entity\Main\PackInterface;
use App\Entity\Main\PackProduct;
use App\Entity\Main\PackProductInterface;
use App\Entity\Main\PackTemplate;
use App\Entity\Main\PackTemplateProduct;
use App\Entity\Main\PaymentToken;
use App\Entity\Main\Product;
use App\Entity\Main\Purchase;
use App\Entity\Main\Site;
use App\Entity\Main\SitePackTemplate;
use App\Entity\Main\Tracking;
use App\Entity\Main\TrackingHasABTest;
use App\Entity\Main\Transaction;
use App\Entity\Main\UpsellCart;
use App\Entity\Main\User;
use App\Entity\Main\UserAddress;
use App\Entity\Main\UserPaymentToken;
use App\Events\Main\Cart\CartCreatedEvent;
use App\Events\Main\Cart\CartItemAddedEvent;
use App\Events\Main\Cart\CartItemQuantityUpdatedEvent;
use App\Events\Main\DiscountCode\DiscountCodeErrorEvent;
use App\Events\Main\Order\OrderFailedCheckedEvent;
use App\EventSubscribers\Main\CartSubscriber;
use App\EventSubscribers\Main\DiscountCodeSubscriber;
use App\EventSubscribers\Main\OrderSubscriber;
use App\Exceptions\Main\Cart\CartAlreadyPaidException;
use App\Exceptions\Main\DiscountCode\DiscountCodeAlreadyUsedException;
use App\Exceptions\Main\DiscountCode\DiscountCodeException;
use App\Exceptions\Main\DiscountCode\DiscountCodeExpiredException;
use App\Exceptions\Main\DiscountCode\DiscountCodeNotApplicableException;
use App\Exceptions\Main\DiscountCode\DiscountCodeNotValidException;
use App\Exceptions\Main\DiscountCode\DiscountCodeNotValidRegistrationException;
use App\Exceptions\Main\DiscountCode\DiscountCodeWrongUserException;
use App\Services\PackManager;
use App\Services\Setup;
use App\Services\TrackingManager;
use App\Psp\STSClient;
use App\Repository\Main\CartActionRepository;
use App\Services\ABTestManager;
use App\Services\BillingAccountManager;
use App\Services\DiscountCodeManager;
use App\Services\GeoIPManager;
use App\Services\SiteManager;
use App\Services\TranslationManager;
use App\Services\UserAddressManager;
use App\Services\UserManager;
use App\Tools\ConditionalPacks\FailedPackCondition;
use App\Tools\ReviewsSystem\AvisVerifies;
use App\Tools\ShortId;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Exception;
use League\Period\Period;
use Predis\Client;
use Psr\Log\LoggerInterface;
use Symfony\Component\Asset\Packages;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class CartManager
{
const
DISCOUNT_CODE_KEY = 'dcode',
NO_COACHING_SESSION_KEY = 'coaching_refused',
COACHING_CHOICE_SESSION_KEY = 'coaching_choice_made',
CUSTOM_PACK_MAX_SINGLE_ITEMS = 6,
CHECK_FAILED_PURCHASE_KEY = 'check-failed-purchase',
OFFER_PREUPSELL = 'offer-preupsell',
OFFER_UPSELL = 'offer-upsell'
;
private $is_active_smart_routing_billing = false;
public static array $possibleOffers = [self::OFFER_PREUPSELL, self::OFFER_UPSELL];
private EntityRepository $cartRepository;
private EntityRepository $cartActionRepository;
private mixed $invoiceSiteId;
private mixed $brandName;
private mixed $siteName;
private mixed $webSiteHost;
private ?Request $request;
/**
* @param EntityManagerInterface $em
* @param LoggerInterface $logger
* @param PackManager $packFactory
* @param TrackingManager $trackingFactory
* @param RequestStack $requestStack
* @param Client $redisCache
* @param EventDispatcherInterface $eventDispatcher
* @param UserManager $userManager
* @param WorkflowInterface $cartStateMachine
* @param $environment
* @param TokenStorageInterface $token_storage
* @param ABTestManager $ABTestManager
* @param DiscountCodeManager $discountCodeManager
* @param SiteManager $siteManager
* @param ShortId $shortIdGenerator
* @param UserAddressManager $userAddressManager
* @param TranslatorInterface $translator
* @param BillingAccountManager $billingAccountManager
* @param \App\Services\GeoIPManager $geoIPManager
*/
public function __construct(
private EntityManagerInterface $em,
private LoggerInterface $logger,
private PackManager $packFactory,
public TrackingManager $trackingFactory,
private RequestStack $requestStack,
private Client $redisCache,
private EventDispatcherInterface $eventDispatcher,
private UserManager $userManager,
private WorkflowInterface $cartStateMachine,
private $environment,
public TokenStorageInterface $token_storage,
private ABTestManager $ABTestManager,
private DiscountCodeManager $discountCodeManager,
public SiteManager $siteManager,
private ShortId $shortIdGenerator,
private UserAddressManager $userAddressManager,
private TranslatorInterface $translator,
private BillingAccountManager $billingAccountManager,
private GeoIPManager $geoIPManager
) {
$this->request = $this->requestStack->getCurrentRequest();
$this->cartRepository = $em->getRepository(Cart::class);
$this->cartActionRepository = $em->getRepository(CartAction::class);
$this->invoiceSiteId = $this->siteManager->getYamlConfigParameter('invoiceSiteId');
$this->brandName = $siteManager->getYamlConfigParameter('brandName');
$this->siteName = $siteManager->getYamlConfigParameter('websiteName');
$this->webSiteHost = $siteManager->getYamlConfigParameter('websiteHost');
}
/**
* @return mixed
*/
public function getInvoiceSiteId()
{
return $this->invoiceSiteId;
}
/**
* Tells whether to use Smart Routing on billings
*
* @param bool $state
*/
public function useSmartRoutingBilling(bool $state)
{
$this->is_active_smart_routing_billing = $state;
}
/**
* Tells whether to Smart Routing on billings is active
*/
public function smartRoutingBillingIsActive(): bool
{
return $this->is_active_smart_routing_billing;
}
/**
* Tell whether the cart global transaction state is valid
*
* @param Cart $cart
*
* @return bool
*/
public function isShippable(Cart $cart): bool
{
if (Cart::STATE_PENDING_SHIPPING !== $cart->getCartState()) {
$this->logger->notice(sprintf('The cart %s is not shippable because of its current state %s', $cart->getCartId(), $cart->getCartState()));
return false;
}
$user = $cart->getUser();
if (!$user instanceof User) {
$this->logger->notice(sprintf('The cart %s is not shippable because no user was attached to it', $cart->getCartId()));
return false;
}
$cartAddress = $cart->getCartAddress();
if (!$cartAddress instanceof CartAddress) {
$this->logger->notice(sprintf('The cart %s is not shippable because no user address was attached to its user %s', $cart->getCartId(), $user->getUsername()));
return false;
}
$state = false;
$paymentTransaction = $cart->getPaymentTransaction();
if ('prod' === $this->environment && $paymentTransaction->getBillingAccount()->isDummyAccount()) {
$this->logger->notice(sprintf(
'The cart %s is not shippable because it uses a dummy billing account : %s',
$cart->getCartId(),
$paymentTransaction->getBillingAccount()->getBillingAccountName()
));
return $state;
}
$tStatus = $paymentTransaction->getTransactionStatus();
$tType = $paymentTransaction->getTransactionType();
if (Transaction::OK !== $tStatus) {
return $state;
}
if (in_array($tType, [Transaction::CHARGEBACK], true)) {
$this->logger->notice(sprintf(
'The cart %s is not shippable because a %s transaction is attached to it',
$cart->getCartId(),
'CHARGEBACK'
));
return $state;
}
$state = Transaction::BILL === $tType;
return $state;
}
public function cleanUpCurrentCart(Cart $currentCart)
{
// 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)
$this->removeCartFromSession();
$currentCart->setCartAddress(null);
$this->transitState($currentCart, 'cleaning');
$this->em->flush();
// ok we don't delete anymore the carts, we will do some cleanup with a cron from now on.
return false;
// if ($currentCart instanceof Cart && !$currentCart->isPurchased() && !count($currentCart->getTransactions())) {
// try {
// $this->removeCartById($currentCart->getCartId());
// return true;
// } catch (\Exception $e) {
// $this->logger->critical("message: {$e->getMessage()}, cart : {$currentCart->getCartId()}");
// }
// }
//
// return false;
}
public function removeCartFromSession()
{
$session = $this->request->getSession();
$currentCartId = $session->get('currentCartId');
if (isset($currentCartId)) {
$session->remove('currentCartId');
}
}
public function removeCartById($cartId)
{
$cart = $this->em
->getRepository('CastalisPlsBrulafineCoreBundle:Cart')
->findOneBy(['cartId' => $cartId]);
if ($cart instanceof Cart) {
$this->em->remove($cart);
$this->em->flush();
}
$session = $this->request->getSession();
$session->remove('currentCartId');
}
/**
* @param Cart $cart
* @param string $transitionName
* @return bool
*/
public function transitState(Cart $cart, string $transitionName): bool
{
if (!$this->cartStateMachine->can($cart, $transitionName)) {
return false;
}
return null !== $this->cartStateMachine->apply($cart, $transitionName);
}
/**
* @param PackTemplate $packTemplate
* @param Site $site
* @param Tracking $tracking
* @param Affiliate $affiliate
* @param array $items
* @param null $user
* @param CartAddress|null $cartAddress
* @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.)
* @return Cart
*/
public function makeCartFromPackTemplate(
PackTemplate $packTemplate,
Site $site,
Tracking $tracking,
Affiliate $affiliate,
array $items = [],
$user = null,
CartAddress $cartAddress = null,
$forceCoaching = false
): Cart {
$cart = new Cart($affiliate, $site);
$cart->setCartCurrency('EUR'); // todo ajouter la currency dans le template
$pack = new Pack(
$packTemplate->getPackTemplateName(),
$packTemplate->getPackTemplateType(),
$packTemplate->getPackTemplateDiscount(),
$packTemplate->getPackTemplateCurrency(),
$packTemplate->getPackTemplateOptions(),
$packTemplate
);
if ($user instanceof User) {
$cart->setUser($user);
}
$this->em->persist($pack);
$this->em->flush();
$this->updateTrackingInfos($cart, $tracking);
$this->em->persist($cart);
$this->em->flush();
// Create a unique cart Shipping ID.
$cart->setCartShippingId(sprintf("CT{$this->invoiceSiteId}%'.09d", $cart->getCartId()));
// check if he refused coaching :
if (!$forceCoaching && !$packTemplate->isCoachingForced() && ($this->request->get('cnr') == 1 || $this->request->getSession()->get(CartManager::NO_COACHING_SESSION_KEY) == 1 || $this->siteManager->coachingIsPreventedByDefault($tracking))) {
$coachingExcluded = true;
} else {
$coachingExcluded = false;
}
/** @var PackTemplateProduct $packTemplateProduct */
foreach ($packTemplate->getPackTemplateProducts() as $packTemplateProduct) {
$externalQuantity = $this->extractExternalQuantity($items, $packTemplateProduct);
$productQuantity = $packTemplateProduct->getProductQuantity();
if ($externalQuantity > -1) {
$productQuantity = $externalQuantity;
}
// If we exclude coaching, we add it but with 0 as quantity.
if ($coachingExcluded && $packTemplateProduct->getProduct()->isCoaching()) {
$productQuantity = 0;
}
if ($productQuantity > -1) {
$packProduct = new PackProduct(
$pack,
$packTemplateProduct->getProduct(),
$productQuantity,
$packTemplateProduct->getPackTemplateProductOrder()
);
$packProduct->setDisplayed($packTemplateProduct->isDisplayed());
$this->em->persist($packProduct);
$this->em->flush();
$pack->addPackProduct($packProduct);
$event = new CartItemAddedEvent($cart, $packProduct);
$this->eventDispatcher->dispatch($event, CartSubscriber::CART_ITEM_ADDED);
}
}
$cart->addPack($pack);
$this->refreshCartPrices($cart);
$this->em->flush();
$event = new CartCreatedEvent($cart, $user);
$this->eventDispatcher->dispatch($event, CartSubscriber::CART_CREATED);
$this->refreshCartPrices($cart);
$this->em->flush();
// Create a unique cart Shipping ID.
$cart->setCartShippingId(sprintf("CT{$this->invoiceSiteId}%'.09d", $cart->getCartId()));
if ($cartAddress instanceof CartAddress) {
$cart->setCartAddress($cartAddress);
$cartAddress->setCart($cart);
}
$this->em->flush();
return $cart;
}
public function makeTemporaryCart(
PackTemplate $packTemplate,
Site $site,
Tracking $tracking,
Affiliate $affiliate,
array $items = [],
bool $has_coaching = null
): Cart {
$cart = new Cart($affiliate, $site);
$cart->setCartCurrency('EUR'); // todo ajouter la currency dans le template
$pack = new Pack(
$packTemplate->getPackTemplateName(),
$packTemplate->getPackTemplateType(),
$packTemplate->getPackTemplateDiscount(),
$packTemplate->getPackTemplateCurrency(),
$packTemplate->getPackTemplateOptions(),
$packTemplate
);
/** @var PackTemplateProduct $packTemplateProduct */
foreach ($packTemplate->getPackTemplateProducts() as $packTemplateProduct) {
$externalQuantity = $this->extractExternalQuantity($items, $packTemplateProduct);
$productQuantity = $packTemplateProduct->getProductQuantity();
if ($externalQuantity > -1) {
$productQuantity = $externalQuantity;
}
if ($productQuantity > -1) {
if (false === $has_coaching && $packTemplateProduct->getProduct()->getProductName() === 'Coaching') {
$productQuantity = 0;
}
$packProduct = new PackProduct(
$pack,
$packTemplateProduct->getProduct(),
$productQuantity,
$packTemplateProduct->getPackTemplateProductOrder()
);
$packProduct->setDisplayed($packTemplateProduct->isDisplayed());
$pack->addPackProduct($packProduct);
}
}
$cart->addPack($pack);
$this->updateTrackingInfos($cart, $tracking);
$this->refreshCartPrices($cart);
return $cart;
}
/**
* Replaced with a listener.
* @param Request $request
* @return bool
*/
public function saveDiscountCodeName(Request $request): bool
{
$discountCodeName = $request->query->get(self::DISCOUNT_CODE_KEY) ?? null;
if (null === $discountCodeName) {
return false;
}
$discountCodeName = trim($discountCodeName);
if ('' === $discountCodeName) {
return false;
}
$discountCode = $this->em->getRepository(DiscountCode::class)
->findOneBy([
'discountCodeName' => $discountCodeName,
'discountCodeStatus' => DiscountCode::ACTIVE_STATUS,
]);
if ($discountCode instanceof DiscountCode) {
$request->getSession()->set(self::DISCOUNT_CODE_KEY, $discountCodeName);
return true;
}
return false;
}
/**
* Get the current discount code from request or session
*
* @param Cart|null $cart
* @return |null
*/
public function getActualDiscountCode(Cart $cart = null)
{
if ($this->request->request->get('discountCode')) {
return trim($this->request->request->get('discountCode'));
}
if ($cart instanceof Cart && $cart->getDiscountCode() instanceof DiscountCode) {
return $cart->getDiscountCode()->getDiscountCodeName();
}
if ($this->requestStack->getSession()->has(self::DISCOUNT_CODE_KEY)) {
$discountCodeName = trim((string)$this->requestStack->getSession()->get(self::DISCOUNT_CODE_KEY));
if ('' === $discountCodeName) {
$this->requestStack->getSession()->remove(self::DISCOUNT_CODE_KEY);
return null;
}
return $discountCodeName;
}
return null;
}
/**
* Apply discountCode
*
* @param Cart $cart
* @param $discountCodeName
* @return array
* @throws Exception
*/
public function addDiscountCode(Cart $cart, $discountCodeName): array
{
$res = ['status' => 'failed', 'message' => "flashMessages.site.promoCodeNotExists"];
if (!$cart->allowDiscountCode()) {
$res['message'] = "flashMessages.site.promoCodeNotForThisPack";
$event = new DiscountCodeErrorEvent($discountCodeName, $res['message']);
$this->eventDispatcher->dispatch($event, DiscountCodeSubscriber::DISCOUNT_CODE_APPLY_ERROR);
return $res;
}
if (null === $discountCodeName || '' === $discountCodeName) {
return $res;
}
$discount = $this->em->getRepository(DiscountCode::class)->getActiveDiscountCodeByName($discountCodeName);
if (!$discount instanceof DiscountCode) {
$res['message'] = "flashMessages.site.promoCodeNotExists";
$event = new DiscountCodeErrorEvent($discountCodeName, $res['message']);
$this->eventDispatcher->dispatch($event, DiscountCodeSubscriber::DISCOUNT_CODE_APPLY_ERROR);
return $res;
}
try {
$this->checkCanApplyCodeToCart($discount, $cart);
} catch (DiscountCodeException $e) {
$event = new DiscountCodeErrorEvent($discount->getDiscountCodeName(), $e->getMessage(), $e);
$this->eventDispatcher->dispatch($event, DiscountCodeSubscriber::DISCOUNT_CODE_APPLY_ERROR);
$res['message'] = $e->getMessage();
return $res;
}
$cart->setDiscountCode($discount);
$this->em->persist($cart);
$this->em->flush();
$res['status'] = 'success';
$res['message'] = 'flashMessages.site.promoCodeAddedInAccount';
// save discount code for future carts
$this->requestStack->getSession()->set(self::DISCOUNT_CODE_KEY, $discount->getDiscountCodeName());
return $res;
}
/**
* @param DiscountCode $discount
* @param Cart $cart
* @return mixed
* @throws Exception
* @throws DiscountCodeNotValidException
* @throws DiscountCodeExpiredException
* @throws DiscountCodeWrongUserException
* @throws DiscountCodeAlreadyUsedException
*/
public function checkCanApplyCodeToCart(DiscountCode $discount, Cart $cart)
{
if (!$cart->getUser() instanceof User) {
throw new DiscountCodeNotValidException();
}
if (!$cart->allowDiscountCode()) {
throw new DiscountCodeNotApplicableException();
}
if ($this->discountCodeManager->isDiscountCodeExpired($discount)) {
throw new DiscountCodeExpiredException();
}
// checking discount code aff.
$user = $cart->getUser();
$discountAffiliate = $discount->getAffiliate();
if ($discountAffiliate instanceof Affiliate) {
$discountCodeHasSameAff = $this->discountCodeManager->discountCodeHasSameAff($discountAffiliate, $cart, $user);
if (!$discountCodeHasSameAff) {
throw new DiscountCodeNotValidException();
}
}
// checking if Promo code is attached to the scenario that user already received an email for.
if (count($discount->getMailingScenarios())) {
$userReceivedScenarioWithPromoCode = $this->discountCodeManager->discountCodeHasSameMailingScenario($discount, $user);
if (!$userReceivedScenarioWithPromoCode) {
throw new DiscountCodeNotValidException("flashMessages.site.promoCodeNotForThisCart");
}
}
// checking if its SINGLE_USAGE
if (!$this->discountCodeManager->canApplySingleUsageCode($discount)) {
throw new DiscountCodeAlreadyUsedException();
}
// checking SINGLE_USER_USAGE
if ($cart->getUser() instanceof User) {
try {
$this->discountCodeManager->canCodeBeUsedByUser($cart->getUser(), $discount);
} catch (DiscountCodeAlreadyUsedException $e) {
throw new DiscountCodeAlreadyUsedException($e->getMessage());
} catch (DiscountCodeNotValidRegistrationException $e) {
throw new DiscountCodeNotValidRegistrationException($this->translator->trans('flashMessages.site.promoCodeNotValidReg', ['{{ date }}' => $discount->getRegisteredUserDateLimit()->format('d-m-Y')], TranslationManager::TRANSLATION_DOMAIN_FLASH));
}
}
//checking SINGLE_USER_USAGE and if discount code has user and its for that specific user.
if ($cart->getUser() instanceof User && $discount->getUser() instanceof User) {
try {
$this->discountCodeManager->canCodeBeUsedByUser($cart->getUser(), $discount, true);
} catch (DiscountCodeAlreadyUsedException $e) {
throw new DiscountCodeAlreadyUsedException($e->getMessage());
} catch (DiscountCodeWrongUserException $e) {
throw new DiscountCodeWrongUserException($e->getMessage());
} catch (DiscountCodeNotValidRegistrationException $e) {
throw new DiscountCodeNotValidRegistrationException($this->translator->trans('flashMessages.site.promoCodeNotValidReg', ['{{ date }}' => $discount->getRegisteredUserDateLimit()->format('d-m-Y')], TranslationManager::TRANSLATION_DOMAIN_FLASH));
}
}
// checking if its MULTIPLE_USAGE
if (DiscountCode::MULTIPLE_USAGE === $discount->getDiscountCodeUsage()) {
return true;
}
return true;
}
/**
* @param User $user
* @param Cart $cart
* @return array
* @throws Exception
*/
public function discountCodeApplying(User $user, Cart $cart)
{
$redirectRoute = 'brulafine_shipping';
if (!$cart) {
$redirectRoute = 'brulafine_show_packs';
}
$routeParams = [];
if (!$user instanceof User) {
$redirectRoute = 'brulafine_cart';
$routeParams = ['packTemplateId' => 123];
}
$discountCodeName = $this->getActualDiscountCode($cart);
$res = $this->addDiscountCode($cart, $discountCodeName);
$status = 'error';
$message = $res['message'];
$cartTotal = $this->computeCartTotal($cart);
if ('success' === $res['status']) {
$status = 'info';
$message = "flashMessages.site.discountCodeIsApplied";
}
return $result = [
'redirectRoute' => $redirectRoute,
'routeParams' => $routeParams,
'status' => $status,
'message' => $message,
'discountCodeName' => $discountCodeName,
'cartTotal' => $cartTotal,
'cart' => $cart,
];
}
/**
* @param Cart $cart
* @return bool
*/
public function cartIsEligibleForFreePurchase(Cart $cart)
{
$cartTotal = $this->computeCartTotal($cart);
$discountCode = $cart->getDiscountCode();
$cartCreditNote = $cart->hasCreditNoteAssigned(true, true);
if (0 == $cartTotal['packGrandTotal']) {
if ($discountCode instanceof DiscountCode && DiscountCode::FREE_PURCHASE === $discountCode->getDiscountCodeType()) {
return true;
}
if ($cartCreditNote instanceof CartCreditNote) {
return true;
}
}
return false;
}
/**
* @param User $user
* @param Cart $cart
* @return array
* @throws Exception
*/
public function creditNoteApplying(Cart $cart)
{
$status = 'info';
$message = "The credit note is applied!";
$cartTotal = [];
try {
$cartTotal = $this->computeCartTotal($cart);
$this->refreshPrices($cart);
} catch (Exception $e) {
$status = 'error';
$message = 'We could not applied the CN because: '. $e->getMessage();
$this->logger->critical($message);
}
return $result = [
'status' => $status,
'message' => $message,
'cartTotal' => $cartTotal,
'cart' => $cart,
];
}
/**
* @param $item
* @param int|null $product_type
* @return Bill
*/
public function getBill($item, int $product_type = null): Bill
{
if ($item instanceof CartInterface) {
$cartBill = $this->getCartBill($item);
return $cartBill;
}
if ($item instanceof PackProductInterface) {
$productBill = $this->getPackProductBill($item);
return $productBill;
}
if (!$item instanceof PackInterface) {
throw new TypeError('Unknown item');
}
if (null === $product_type) {
return $this->getPackBill($item);
}
foreach ($item->getPackProducts() as $packProduct) {
if ($product_type === $packProduct->getProduct()->getProductType()) {
return $this->getPackProductTypeBill($item, $product_type);
}
}
throw new InvalidArgumentException('Unknown product type');
}
/**
* @param Cart|null $cart
* @return array
*/
public function computeCartTotal(CartInterface $cart = null): array
{
if (null === $cart) {
return [
'packPrice' => 0,
'packPriceReduced' => 0,
'packPriceDiscountRate' => 0,
'packPriceDiscount' => 0,
'packPromoDiscount' => 0,
'packPriceShipping' => 0,
'packGrandTotal' => 0,
'pack' => [],
];
}
$bill = $this->getBill($cart);
$total = [
'packPrice' => $bill->getBaseAmount(),
'packPriceReduced' => $bill->getReducedAmount(),
'packPriceDiscountRate' => $bill->getDiscountRate(),
'packPriceDiscount' => $bill->getDiscountAmount(),
'packPromoDiscount' => $bill->getPromoAmount(),
'packPriceShipping' => $bill->getShippingAmount(),
'packGrandTotal' => $bill->getTotalAmount(),
'creditNoteAmount' => $bill->getCreditNoteAmount(),
'payedAfterCN' => $bill->getPayedAfterCNAmount(),
'pack' => [],
];
foreach ($cart->getPacks() as $pack) {
foreach ($pack->getPackProducts() as $packProduct) {
$packProductBill = $this->getBill($packProduct);
$total['pack'][$pack->getPackId()][$packProduct->getProduct()->getProductId()] = [
'price' => $packProductBill->getBaseAmount(),
'priceReduced' => $packProductBill->getReducedAmount(),
'priceDiscount' => $packProductBill->getDiscountAmount(),
'priceEffective' => $packProductBill->getAmount(),
];
}
}
return $total;
}
/**
* Retuns the bills associated to a given Cart
*
* @param Cart|null $cart
* @param BillingAccount|null $billingAccount
* @param UserPaymentToken|null $userPaymentToken
* @param STSClient|null $stsClient
* @param false $disableSmartRouting
* @return array
*/
public function computeCartBills(
CartInterface $cart = null,
BillingAccount $billingAccount = null,
UserPaymentToken $userPaymentToken = null,
STSClient $stsClient = null,
$disableSmartRouting = false
): array {
$bills = [];
if (null === $cart) {
return $bills;
}
$totalShipping = $this->getBill($cart)->getShippingAmount();
// les ELECTRONIC_PRODUCT sont billes sur un mid à part tout le reste (y compris le shipping sur les autres)
// le Cart::Discount s'applique a la fin sur le montant total non ELECTRONIC
foreach ($cart->getPacks() as $pack) {
foreach ($pack->getPackProducts() as $packProduct) {
/** @var Product $product */
$product = $packProduct->getProduct();
// todo analyse limitation (only 2 pacounts 1 shipping one electronic ?? )
// 1 rebill plan par type also
if (!isset($bills[$product->getProductType()])) {
$bill = $this->getBill($pack, $product->getProductType());
if (Product::SHIPPIED_PRODUCT === $product->getProductType()) {
// $bill = $bill->withShippingAmount($totalShipping);
// i don`t know why we used that before, since getBill is applying everything needed
$bill = $this->getBill($cart);
}
// If no BA is provided, we take the one specified for this product.
$finalBillingAccount = $billingAccount ?? $product->getBillingAccount();
if (!$finalBillingAccount instanceof BillingAccount) {
throw new \LogicException("You must provide a BA !");
}
// there is a list with supported countries for Hipay.
// if the user`s Country Token is from diff country and the final BA is Hipay we select randomly another BA.
$userIpCountry = $this->geoIPManager->getCountryCode($cart->getUser()->getUserIp());
if ($userPaymentToken instanceof UserPaymentToken) {
$paymentCountry = $userPaymentToken->getCountryToken();
} else {
$paymentCountry = $userIpCountry;
}
if ($this->shouldApplyHipayRule($finalBillingAccount, $paymentCountry, $userIpCountry)) {
$finalBillingAccount = $this->applyHipayRule($finalBillingAccount);
}
if ($finalBillingAccount->getBillingAccountIs3ds()) {
$final3dsBillingAccount = $finalBillingAccount;
$finalBillingAccount = $final3dsBillingAccount->getLinkedNormalAccount();
} else {
$final3dsBillingAccount = $finalBillingAccount->getLinked3dsAccount();
}
// If the 3ds account doesn't exists, ALERT THE DEVS
if (!$final3dsBillingAccount instanceof BillingAccount) {
$final3dsBillingAccount = $finalBillingAccount;
$this->logger->critical("You must provide a 3DS BA ! => nothing linked to {$finalBillingAccount->getBillingAccountName()} found in 3ds accounts.");
}
$bills[$product->getProductType()] = [
'totalAmount' => $bill->getTotalAmount(),
'productType' => $product->getProductType(),
'billingCurrency' => $product->getProductCurrency(),
'billingAccount' => $finalBillingAccount,
'billingRebillRule' => $product->getBillingRebillRule(),
'billingPriority' => $product->getProductBillingPriority(),
'productId' => $product->getProductId(),
'billingAccount3ds' => $final3dsBillingAccount,
'billTrBillingAccount' => $billingAccount,
];
}
}
}
// sort bills Electronic first
usort($bills, function (array $item1, array $item2): int {
return $item2['billingPriority'] <=> $item1['billingPriority'];
});
// we are applying the billing type of the cart here to find the billing account.
if ($cart->getCartBillingType() === Cart::BILLING_TYPE_ALTERNATIVE || $cart->getCartBillingType() === Cart::BILLING_TYPE_FREE) {
if ($cart->getCartBillingType() === Cart::BILLING_TYPE_ALTERNATIVE) {
$typeTofind = AlternativeBilling::ALTERNATIVE_BILLING_TYPE_TRANSFER;
}
if ($cart->getCartBillingType() === Cart::BILLING_TYPE_FREE) {
$typeTofind = AlternativeBilling::ALTERNATIVE_BILLING_TYPE_FREE;
}
$billingAccountAlternate = $this->em->getRepository(AlternativeBilling::class)->findOneBy([
'alternativeBillingType' => $typeTofind,
]);
if ($billingAccountAlternate instanceof AlternativeBilling) {
$alternateBillingAccount = $this->em->getRepository(BillingAccount::class)->findOneBy([
'billingAccountId' => $billingAccountAlternate->getBillingAccount(),
]);
} else {
$alternateBillingAccount = null;
}
if (null !== $alternateBillingAccount) {
return $this->addBillingAccount($bills, $alternateBillingAccount);
}
}
if (false === $disableSmartRouting && $this->is_active_smart_routing_billing && null !== $userPaymentToken && null !== ($token = $userPaymentToken->getPaymentToken()->getToken())) {
return $this->addSmartRoutingBillingAccount($bills, $stsClient ?? new STSClient(), $token);
}
return $bills;
}
/**
* @param $finalBillingAccount
* @param $paymentCountry
* @return bool
*/
public function shouldApplyHipayRule($finalBillingAccount, $paymentCountry, $userIPCountry): bool
{
$allowedHipayCountries = ["FR", "MC", "LU", "CH", "BE"];
$allHipayBillingAccounts = $this->billingAccountManager->getAllBillingAccountByName('HIPAY@');
$isHiPay = false;
foreach ($allHipayBillingAccounts as $account) {
if ($account['billingAccountId'] == $finalBillingAccount->getBillingAccountId()) {
$isHiPay = true;
}
}
// if card country or user Ip country is diff than the allowed ones - apply the rule
if ($isHiPay && (!in_array($paymentCountry, $allowedHipayCountries) || !in_array($userIPCountry, $allowedHipayCountries))) {
return true;
}
return false;
}
/**
* @param $finalBillingAccount
* @return BillingAccount
*/
public function applyHipayRule($finalBillingAccount)
{
$allOtherBas = $this->billingAccountManager->getAllBillingAccounts('HIPAY@', $finalBillingAccount->getBillingAccountType());
$key = array_rand($allOtherBas);
return $allOtherBas[$key];
}
/**
* @return object|void
*/
public function resolveCurrentCart()
{
if ($this->requestStack) {
$currentCartId = $this->requestStack->getSession()->get('currentCartId');
}
if (!isset($currentCartId)) {
$this->logger->debug('Can not take cartId from session: ' . $currentCartId);
return;
}
/** @var Cart $currentCart */
$currentCart = $this->em
->getRepository(Cart::class)
->findOneBy(['cartId' => $currentCartId, 'site' => $this->siteManager->getCurrentSite()]);
if (!$currentCart instanceof Cart) {
$this->logger->debug('Can not find the cart with ID: ' . $currentCartId . ' Cart result: ' . serialize($currentCart));
return;
}
$this->refreshCartPrices($currentCart);
return $currentCart;
}
public function makePackTemplateDiscounts(PackTemplate $pack)
{
$totalPrice = 0;
$totalPriceReduced = 0;
$productCount = 0;
foreach ($pack->getPackTemplateProducts() as $pTP) {
$productCount = $pTP->getProductQuantity();
$productPrice = $pTP->getProduct()->getProductPrice();
$totalPrice += $productPrice;
$productDiscount = $pTP->getProduct()->getProductDiscount();
list($productPriceDiscountRate, $productPriceReduced) = $productDiscount->applyDiscount($productCount, $productPrice);
$totalPriceReduced += $productPriceReduced;
$pTP->setPackTemplateProductPriceReduced($productPriceReduced);
$pTP->getProduct()->setProductPriceDiscountRate($productPriceDiscountRate);
}
list($packPriceDiscountRate, $packPriceReduced, $shippingPrice) =
$this->packFactory->applyDiscount($pack->getPackTemplateDiscount(), $productCount, $totalPriceReduced);
$pack->setPackTemplatePriceDiscountRate($packPriceDiscountRate);
$pack->setPackTemplatePriceReduced($totalPriceReduced);
$pack->setPackTemplatePrice($totalPrice);
}
/**
* @param Setup $setup
* @param int $packTemplateId
* @param array $items
* @return mixed
*/
public function getCustomTemplatePack(Setup $setup, int $packTemplateId, array $items)
{
$items = array_filter($items, function ($value) {
return $value >= 0 && $value <= self::CUSTOM_PACK_MAX_SINGLE_ITEMS;
});
$defaultProposedPacks = $this->packFactory->getDefaultProposedPacks($setup->getSite());
$selectKey = null;
foreach ($defaultProposedPacks as $key => $packs) {
if ($packTemplateId == $packs->getPackTemplate()->getPackTemplateId()) {
$selectedKey = $key;
break;
}
}
if (null !== $selectedKey) {
return $this
->packFactory
->updateProposedPack($defaultProposedPacks, $selectedKey, $items)[$selectedKey];
}
}
/**
* @param Setup $setup
* @param $packIndex
* @param $packProductIndex
* @param $quantity
*/
public function updateCartProductQuantity(Setup $setup, $packIndex, $packProductIndex, $quantity)
{
$quantity = filter_var(
$quantity,
FILTER_VALIDATE_INT,
['options' => ['min_range' => 0, 'max_range' => 6]]
);
if (false === $quantity) {
throw new OutOfRangeException('invalid product quantity');
}
$currentCart = $setup->getCart();
if (!$currentCart instanceof Cart) {
throw new InvalidArgumentException('no cart found');
}
$packs = $currentCart->getPacks();
if (!isset($packs[$packIndex])) {
throw new OutOfRangeException('invalid pack index');
}
$packProducts = $packs[$packIndex]->getPackProducts();
if (!isset($packProducts[$packProductIndex])) {
throw new OutOfRangeException('invalid pack product index');
}
/** @var PackProduct $product */
$product = $packProducts[$packProductIndex];
$oldQuantity = $product->getProductQuantity();
$product->setProductQuantity($quantity);
$newQuantity = $product->getProductQuantity();
// Fire the event for eventual processing
$event = new CartItemQuantityUpdatedEvent($currentCart, $product, $oldQuantity, $newQuantity);
$this->eventDispatcher->dispatch($event, CartSubscriber::CART_ITEM_QTY_UPDATED);
$this->refreshCartPrices($currentCart);
$this->em->flush();
}
/**
* @param Cart $cart
* @return mixed
*/
public function getCurrentPack(Cart $cart)
{
$defaultProposedPacks = $this->packFactory->getDefaultProposedPacks($cart->getSite());
$packName = $cart->getPacks()[0]->getPackName();
foreach ($defaultProposedPacks as $sitePackTemplate) {
if ($packName == $sitePackTemplate->getPackTemplate()->getPackTemplateName()) {
return $sitePackTemplate->getPackTemplate();
}
}
}
/**
* @param Setup $setup
* @return array
*/
public function getNextPackTemplate(Cart $cart)
{
$packs = $this->packFactory->getDefaultProposedPacks($cart->getSite());
// filter the pack to exclude the abtest ones.
$defaultProposedPacks = $this->ABTestManager->getPacksForTracking($this->trackingFactory->getCurrentTracking(), $packs);
$packTemplates = $this->getPushPack($defaultProposedPacks, $cart);
return [
'packTemplates' => $packTemplates,
'hasCoaching' => (int)$this->hasCoaching($cart),
];
}
/**
* @param array $result
* @param Cart $cart
* @param Setup $setup
* @return array
*/
public function generatePossibleNextPacksData(array $result, Cart $cart, Setup $setup)
{
$data = [];
foreach ($result['packTemplates'] as $packTemplate) {
$nextCart = $this->makeTemporaryCart(
$packTemplate->getPackTemplate(),
$setup->getSite(),
$setup->getTracking(),
$setup->getAffiliate(),
[],
$result['hasCoaching']
);
$nextCart->setUser($cart->getUser());
try {
$nextCart->setTracking($cart->getTracking());
} catch (Exception $e) {
$nextCart->setTracking(null);
$this->logger->critical("{$this->environment} - {$this->siteManager->getCurrentSite()->getSiteName()} - could not set tracking in cart with exception : {$e->getMessage()}");
}
$nextCart->setCartShippingCountry($cart->getCartShippingCountry());
$nextCart->setDiscountCode($cart->getDiscountCode());
$nextCart->setShippingRule($cart->getShippingRule());
$nextCart->setPickUpPoint($cart->getPickUpPoint());
$nextCartTotal = $this->computeCartTotal($nextCart);
$this->packFactory->makePackTemplateDiscounts($packTemplate->getPackTemplate());
$data[] = [
'nextPack' => $packTemplate->getPackTemplate(),
'hasCoaching' => $result['hasCoaching'],
'nextCart' => $nextCart,
'nextCartTotal' => $nextCartTotal,
'nextCartBill' => $this->getCartBill($nextCart)
];
}
return $data;
}
public function getCartFromId(int $cartId = null, User $user = null, string $state = '')
{
//no cartId nothing to do here
if (null === $cartId) {
throw new \RuntimeException('Undefined cart Id');
}
$params = ['cartId' => $cartId];
if ('' != $state) {
$params['cartState'] = $state;
}
$cart = $this->cartRepository->findOneBy($params);
//no cart found nothing to do here
if (!$cart) {
throw new \RuntimeException(sprintf('No cart found for the given parameters %s', json_encode($params)));
}
if (!$user instanceof User) {
return $cart;
}
if ($cart->getUser() != $user) {
throw new \RuntimeException('The cart does not belong to the submitted user');
}
$this->refreshCartPrices($cart);
return $cart;
}
/**
* @param Setup $setup
* @param int $packTemplateId
* @param int $hasCoaching
* @param UserAddressManager $userAddressManager
* @return Cart
*/
public function getPaymentCart(Setup $setup, int $packTemplateId, int $hasCoaching, UserAddressManager $userAddressManager)
{
$cart = $setup->getCart();
$checkFailedPurchase = $this->request->getSession()->get(CartManager::CHECK_FAILED_PURCHASE_KEY);
if ($checkFailedPurchase) {
$user = $setup->getUser();
if (!$user instanceof User && $this->requestStack->getSession()->has(User::GUEST_USER_SESSION_KEY)) {
$user = $this->em->getRepository(User::class)->findOneBy(['id' => $this->request->getSession()->get(User::GUEST_USER_SESSION_KEY)]);
}
$lastFailedPaidCart = $user->getLastCart(true);
if ($lastFailedPaidCart instanceof Cart) {
$cart = $lastFailedPaidCart;
$hasDcodeParam = $this->request->getSession()->get(CartManager::DISCOUNT_CODE_KEY);
if ($hasDcodeParam && !$cart->getDiscountCode() instanceof DiscountCode) {
try {
$this->addDiscountCode($cart, $hasDcodeParam);
} catch (Exception $e) {
$this->logger->critical('We could not add promo code: ' . $hasDcodeParam . ' to the cart: ' . $cart->getCartId() . ' Because: ' . $e->getMessage());
}
}
$orderFailedCheckedEvent = new OrderFailedCheckedEvent($cart, $this->request);
$this->eventDispatcher->dispatch($orderFailedCheckedEvent, OrderSubscriber::CHECKED_FOR_FAILED_ORDER);
}
}
if (!$cart instanceof Cart) {
throw new \RuntimeException('No cart was found');
}
$user = $setup->getUser();
if (!$user instanceof User) {
$user = $cart->getUser();
}
if (!$user instanceof User) {
throw new \RuntimeException('No user found');
}
$state = $cart->getCartState();
if (Cart::STATE_ADDRESS_ASSIGNED !== $state) {
if ($cart->isPaid()) {
throw new CartAlreadyPaidException("Cart is already paid but shouldn't, returning exception {$cart->getCartId()}");
} elseif (Cart::STATE_FREEZE_PENDING_PAYMENT == $state) {
$this->transitState($cart, 'paymentAlternateFailed');
} else {
throw new \RuntimeException(sprintf('Expected cart to be in %s state but cart is in %s state', Cart::STATE_ADDRESS_ASSIGNED, $state));
}
}
if ($cart->getUser() != $user) {
throw new \RuntimeException('User does not belong to current cart');
}
if ($packTemplateId < 1) {
if (!$this->checkUserIsEligibleForCoaching($user) || 0 == $hasCoaching) {
$this->em->persist($cart);
foreach ($cart->getPacks()[0]->getPackProducts() as $packProduct) {
if ($packProduct->getProduct()->getProductName() === 'Coaching') {
$packProduct->setProductQuantity(0);
break;
}
}
$this->em->flush();
}
return $cart;
}
$items = [];
$packTemplate = $this->em
->getRepository(PackTemplate::class)
->findOneBy(['packTemplateId' => $packTemplateId]);
if (!$packTemplate) {
return $cart;
}
$discount = $cart->getDiscountCode();
$oldCartAddress = $cart->getCartAddress();
$oldCartAddress->setCart(null);
$this->cleanUpCurrentCart($cart);
// I am not sure why we redo completely the cart here ...?
$newCart = $this->makeCartFromPackTemplate(
$packTemplate,
$setup->getSite(),
$setup->getTracking(),
$setup->getAffiliate(),
$items,
$user,
$oldCartAddress,
$cart->hasCoaching()
);
$tracking = $user->getTracking();
$this->em->persist($tracking);
$newCart->setUser($user);
$newCart->setTracking($tracking);
$newCart->setDiscountCode($discount);
$newCart->setShippingRule($cart->getShippingRule());
$newCart->setPickUpPoint($cart->getPickUpPoint());
// add Unique CartShippingId
if (null == $newCart->getCartShippingId()) {
$newCart->setCartShippingId(sprintf("CT{$this->invoiceSiteId}%'.09d", $newCart->getCartId()));
}
if (!$this->checkUserIsEligibleForCoaching($user) || 0 == $hasCoaching) {
foreach ($newCart->getPacks()[0]->getPackProducts() as $packProduct) {
if ($packProduct->getProduct()->getProductName() === 'Coaching') {
$packProduct->setProductQuantity(0);
break;
}
}
}
// If the cart has no address here, we need to create one.
if (!$newCart->getCartAddress() instanceof CartAddress) {
$cartAddress = $userAddressManager->getAddressForCart($user->getAddress());
$cartAddress->setCart($newCart);
$newCart->setCartAddress($cartAddress);
} elseif (!$userAddressManager->checkAddressIsSameForCart($user->getAddress(), $newCart->getCartAddress())) {
$userAddressManager->updateCartAddress($newCart->getCartAddress(), $user->getAddress());
}
$this->transitState($newCart, 'clientIdentification');
$this->transitState($newCart, 'clientAddressFilling');
$this->transitState($newCart, 'payment');
$this->em->flush();
return $newCart;
}
/**
* @param Cart $cart
* @return bool
*/
public function hasCoaching(Cart $cart): bool
{
$packProducts = $cart->getPacks()[0]->getPackProducts();
foreach ($packProducts as $packProduct) {
if ($packProduct->getProduct()->getProductName() === 'Coaching' && $packProduct->getProductQuantity() > 0) {
return true;
}
}
return false;
}
/**
* @param Cart $cart
* @return bool
*/
public function removeCoaching(Cart $cart)
{
if ($cart->getPacks()[0] instanceof Pack) {
/** @var Pack $pack */
$pack = $cart->getPacks()[0];
// can't remove coaching from a forced coaching pack.
if ($pack->getPackTemplate() instanceof PackTemplate && $pack->getPackTemplate()->isCoachingForced()) {
return;
}
$packProducts = $cart->getPacks()[0]->getPackProducts();
/** @var PackProduct $packProduct */
foreach ($packProducts as $packProduct) {
if ($packProduct->getProduct()->getProductName() === 'Coaching' && $packProduct->getProductQuantity() > 0) {
$packProduct->setProductQuantity(0);
}
}
}
}
/**
* @param User $user
* @param bool $shipped
* @return mixed
*/
public function getLastPaidCart(User $user, $shipped = false)
{
$lastPaidCart = null;
$method = $shipped ? 'isShipped' : 'isPaidButNotShipped';
/** @var Cart $cart */
foreach ($user->getCarts() as $cart) {
if (!$cart->$method()) {
continue;
}
if (null === $lastPaidCart) {
$lastPaidCart = $cart;
continue;
}
if ($cart->getCartCreateAt() > $lastPaidCart->getCartCreateAt()) {
$lastPaidCart = $cart;
}
}
return $lastPaidCart;
}
/**
* Check if the user already made an order recently (default 30 mins) that is not shipped.
*
* @param User $user
* @param \DateInterval $delayToCheck (default 30 mins)
* @param bool $shipped (default false)
* @return bool
* @throws Exception
*/
public function hasOrderInDelay(User $user, \DateInterval $delayToCheck = null, $shipped = false)
{
if (!$delayToCheck instanceof \DateInterval) {
$delayToCheck = new \DateInterval("PT30M");
}
$lastOrder = $this->getLastPaidCart($user, $shipped);
if (!$lastOrder instanceof Cart) {
return false;
}
$dateLimit = new \DateTime();
$dateLimit->sub($delayToCheck);
if ($lastOrder->getPaymentTransaction() instanceof Transaction && $lastOrder->getPaymentTransaction()->getTransactionStamp() >= $dateLimit) {
return true;
}
return false;
}
/**
* @param \DateInterval|null $interval (default 1 month)
* @param \DateTime $endDate
* @return array
* @throws \Doctrine\DBAL\DBALException
*/
public function getSoldProductsForPeriod(\DateInterval $interval = null, \DateTime $endDate = null)
{
if (!$interval instanceof \DateInterval) {
$interval = new \DateInterval("P1M");
}
if (!$interval instanceof \DateTime) {
$endDate = new \DateTime('now');
}
$period = Period::createFromDurationBeforeEnd($endDate, $interval);
$startPeriod = $period->getStartDate()->format('Y-m-d');
$endPeriod = $period->getEndDate()->format('Y-m-d');
// check if result exist in cache, if yes return it
if ('prod' == $this->environment && $this->redisCache->exists('stats_stock_'.$startPeriod.'_'.$endPeriod)) {
$sells = json_decode($this->redisCache->get('stats_stock_'.$startPeriod.'_'.$endPeriod), true);
} else {
// if not request it from db
$sells = $this->cartRepository
->getAllShippedProductsForPeriod($period);
// create the cache for 4 hours
$this->redisCache->setex('stats_stock_'.$startPeriod.'_'.$endPeriod, 14400, json_encode($sells));
}
return $sells;
}
/**
* @return array
* @throws Exception
*/
public function getStatsArray()
{
//get Products for periods: month, week, day
$soldProductsLastMonth = $this->getSoldProductsForPeriod();
$soldProductsLastWeek = $this->getSoldProductsForPeriod($interval = new \DateInterval('P1W'));
$soldProductsLastDay = $this->getSoldProductsForPeriod($interval = new \DateInterval('P1D'));
$stats = [];
if (!empty($soldProductsLastMonth)) {
foreach ($soldProductsLastMonth as $soldProducts) {
$stats[$soldProducts['productCode']] = [
"productName" => $soldProducts['productName'],
"productCode" => $soldProducts['productCode'],
"soldItemsLastMonth" => $soldProducts['totalSells'],
"averageSellsPerDay" => round($soldProducts['totalSells']/30, 2),
"averageSellsPerWeek" => round(($soldProducts['totalSells']/30)*7, 2),
];
}
}
if (!empty($soldProductsLastWeek)) {
foreach ($soldProductsLastWeek as $soldProducts) {
$stats[$soldProducts['productCode']]["soldItemsLastWeek"] = $soldProducts['totalSells'];
$stats[$soldProducts['productCode']]["estimatedSellsNextMonth"] = round(($soldProducts['totalSells'] / 7) * 30);
}
}
if (!empty($soldProductsLastDay)) {
foreach ($soldProductsLastDay as $soldProducts) {
$stats[$soldProducts['productCode']]["soldItemsLastDay"] = $soldProducts['totalSells'];
}
}
return $stats;
}
/**
* @param $shippingId
* @param $state
* @return Cart|object|null
*/
public function getCartByShippingId($shippingId, $state = null)
{
$params = ['cartShippingId' => $shippingId];
if (null !== $state) {
$params ['cartState'] = $state;
}
return $this->cartRepository->findOneBy($params);
}
/**
* @param Cart $cart
* @param Tracking $tracking
*/
public function updateTrackingInfos(Cart $cart, Tracking $tracking)
{
$cart->setTracking($tracking);
$cart->setAffiliate($tracking->getAff());
// clean the current cart AB tests.
if (count($cart->getAbTests())) {
/** @var CartHasABTest $abTest */
foreach ($cart->getAbTests() as $abTest) {
$abTest->setCart(null);
$this->em->remove($abTest);
}
$cart->setAbTests(new ArrayCollection());
}
// put the active AB tests of this new tracking.
if (count($tracking->getAbTests())) {
/** @var TrackingHasABTest $trackingABTest */
foreach ($tracking->getAbTests() as $trackingABTest) {
if ($trackingABTest->getSiteABTest()->isActive()) {
if (!$trackingABTest->getSiteABTest()->isForCartSwitch() || ($trackingABTest->getSiteABTest()->isForCartSwitch() && $this->ABTestManager->cartCanReceiveSwitchTest($cart, $cart->getSite(), $trackingABTest->getSiteABTest()))) {
$cartABTest = new CartHasABTest();
$cartABTest->setCart($cart);
$cartABTest->setVersion($trackingABTest->getVersion());
$cartABTest->setSiteABTest($trackingABTest->getSiteABTest());
$this->em->persist($cartABTest);
$cart->addABTest($cartABTest);
}
}
}
}
}
/**
* @param Cart $cart
* @param $status
*/
public function updateCartStatus(Cart $cart, $status)
{
$cart->setCartState($status);
$this->em->flush();
}
/**
* @param Cart $cart
* @return string
*/
public function getEncodedId(Cart $cart)
{
return $this->shortIdGenerator->encode($cart->getCartId());
}
/**
* @param $encodedId
* @return string
*/
public function getDecodedId($encodedId)
{
return $this->shortIdGenerator->decode($encodedId);
}
/**
* @param User $user
* @return bool
*/
public function checkUserIsEligibleForCoaching(User $user)
{
$dateTime = (new \DateTime('now'))->sub(new \DateInterval('P90D'));
if ($this->userManager->haveRunningRebillManager($user)
|| $this->userManager->hasSuccessfulTransactionForCoaching($user, $dateTime)
|| $this->userManager->hasRefundOrChargebackForCoaching($user)
) {
return false;
}
return true;
}
/**
* @param Cart $cart
* @param RouterInterface $router
* @return array
*/
public function getOrderInfoFromCartForAvisVerifie(Cart $cart, RouterInterface $router)
{
/** @var User $user */
$user = $cart->getUser();
/** @var UserAddress $address */
$userAddress = $user->getAddress();
$transactions = $cart->getTransactions();
if (!count($transactions)) {
return [];
}
/** @var Transaction $transaction */
$transaction = $transactions[0];
// if (BillingAccount::BILLING_ACCOUNT_TYPE_DUMMY == $transaction->getBillingAccount()->getBillingAccountType() || $transaction->isRealTest()) {
// return [];
// }
$productsArray = [
'products' => []
];
$context = $router->getContext();
$context->setHost($this->webSiteHost);
$packsUrl = $router->generate('brulafine_show_packs', [], UrlGeneratorInterface::ABSOLUTE_URL);
$homeUrl = $router->generate('brulafine_home', [], UrlGeneratorInterface::ABSOLUTE_URL);
/** @var Pack $pack */
foreach ($cart->getPacks() as $pack) {
if ($pack->getPackTemplate()->isUpsell()) {
continue;
}
if (Pack::AUTO_PACK == $pack->getPackType()) {
$packOptions = $pack->getPackOptions();
if (empty($packOptions)) {
return [];
}
if (!isset($packOptions['EAN']) || !isset($packOptions['avis_reference'])) {
$this->logger->critical("Pack:" . $pack->getPackId() . " and Pack template: " . $pack->getPackTemplate()->getPackTemplateId() .
" does not have one of the following options: 'avis_reference', 'EAN'!");
return [];
}
$urlProductImage = '';
switch ($this->siteName) {
case "brulafine":
$urlProductImage = $homeUrl . 'kits/produits/x' . $pack->getPackOptions()['month'] . '.png';
break;
case "slimalis":
case "nocticalm":
$urlProductImage = '';
break;
}
$productsArray['products'][] = [
'id_product' => $packOptions['avis_reference'],
'name_product' => $pack->getPackName(),
'url_product' => $packsUrl,
'url_product_image' => $urlProductImage,
'GTIN_EAN' => $packOptions['EAN'],
'brand_name' => $this->brandName
];
} elseif (Pack::CUSTOM_PACK == $pack->getPackType()) {
/** @var PackProduct $packProduct */
foreach ($pack->getPackProducts() as $packProduct) {
if (Product::ELECTRONIC_PRODUCT == $packProduct->getProduct()->getProductType()) {
continue;
}
$productOptions = $packProduct->getProduct()->getProductOptions();
if (empty($productOptions)) {
return [];
}
if (!isset($productOptions['EAN'])
|| !isset($productOptions['avis_reference'])
) {
$this->logger->critical("Product: " . $packProduct->getProduct()->getProductId() .
" does not have one of the following options: 'avis_reference', 'EAN'!");
return [];
}
if ($packProduct->getProductQuantity() > 0) {
$urlProductImage = '';
switch ($this->siteName) {
case "brulafine":
$urlProductImage = $homeUrl . 'kits/produits/' . strtolower($packProduct->getProduct()->getProductName()) . '.png';
break;
case "slimalis":
case "nocticalm":
$urlProductImage = '';
break;
}
$productsArray['products'][] = [
'id_product' => $productOptions['avis_reference'],
'name_product' => $packProduct->getProduct()->getProductName(),
'url_product' => $packsUrl,
'url_product_image' => $urlProductImage,
'GTIN_EAN' => $productOptions['EAN'],
// 'MPN' => $productOptions['avis_mpn'],
'brand_name' => $this->brandName,
];
}
}
}
}
return [
'order_ref' => $this->shortIdGenerator->encode($cart->getCartId()),
'email' => $user->getEmail(),
'lastname' => $userAddress->getLastName(),
'firstname' => $userAddress->getFirstName(),
'order_date' => $transaction->getTransactionStamp()->format('Y-m-d h:i:s'),
'delay_product' => AvisVerifies::DELAY_PRODUCT_VALUE,
'PRODUCTS' => $productsArray['products']
];
}
/**
* @param Cart $cart
* @return boolean
*/
public function fixCartMissingAddress(Cart $cart)
{
if ($cart->getCartAddress() instanceof CartAddress) {
return false;
}
$userAddress = null;
/** @var Cart $otherCart */
foreach ($cart->getUser()->getCarts() as $otherCart) {
if ($cart->getCartId() <= $otherCart->getCartId()) {
continue;
}
if ($otherCart->getCartAddress() instanceof CartAddress) {
$userAddress = $cart->getCartAddress();
}
}
if (null === $userAddress) {
$userAddress = $this->userAddressManager->getAddressForCart($cart->getUser()->getAddress());
}
$cart->setCartAddress($userAddress);
$this->em->flush();
}
/**
* This function is used to check that the pack in the cart is allowed to be purchased.
* This is now necessary due to the conditional packs.
*
* @param Cart $cart
* @return bool|FailedPackCondition
*/
public function isCartPurchasePossible(Cart $cart, $conditionType = PackTemplate::DISPLAY_CONDITION , UserPaymentToken $userPaymentToken = null)
{
$packs = $cart->getPacks();
/** @var Pack $pack */
foreach ($packs as $pack) {
$result = $this->packFactory->isConditionalPackTemplatePassingConditions($pack->getPackTemplate(), $conditionType, $cart, $userPaymentToken);
if ($result instanceof FailedPackCondition) {
return $result;
}
}
return true;
}
/**
* This function is used to identify potential rm that should have been created but that were not
* So it is finding carts with coaching, that should have rm (not same token) and list them.
* @param string $delay
*/
public function findCartWithoutCreatedRebillManagers($delay = "P30D")
{
return $this->cartRepository->findCartsWithoutRmButWithCoaching($delay);
}
/**
* @param Cart $cart
* @param string $action
*/
public function addNewAction(Cart $cart, string $action)
{
if (!$cart->hasEvent($action)) {
$cartAction = $this->cartActionRepository->createNewAction($action);
$cart->addAction($cartAction);
$this->em->flush();
}
}
/**
* @param Cart $cart
* @return bool
*/
public function cartIsEligibleForPreUpsell(Cart $cart): bool
{
$preUpsellPack = $this->getLinkedPreUpsellFromCart($cart);
if ($cart->getSite()->hasPreUpsells()
&& $preUpsellPack instanceof SitePackTemplate
&& !$cart->hasPreUpsellTransactions()
&& !$cart->hasAction(CartAction::REFUSED_PREUPSELL)
) {
return true;
}
return false;
}
/**
* @param Cart $cart
* @return null|SitePackTemplate
*/
public function getLinkedPreUpsellFromCart(Cart $cart): ?SitePackTemplate
{
$preUpsellPack = null;
$pack = $cart->getPacks()[0];
/** @var PackTemplate $linked */
$linked = $pack->getPackTemplate()->getLinkedPreUpsellPackTemplates();
$sitePackTemplates = [];
/** @var PackTemplate $pack */
foreach ($linked as $packTemplate) {
if (isset($packTemplate->getPackTemplateOptions()['pack_type']) && $packTemplate->getPackTemplateOptions()['pack_type'] == 'offer') {
continue;
}
/** @var SitePackTemplate $sitePackTemplate */
$sitePackTemplate = $packTemplate->getSitePackTemplates()[0];
if (!$sitePackTemplate->isActive()) {
continue;
}
$sitePackTemplates[] = $sitePackTemplate;
}
$personalisedPack = $sitePackTemplates;
if (count($personalisedPack) && $personalisedPack[0] instanceof SitePackTemplate) {
$linked = $personalisedPack[0]->getPackTemplate();
}
if ($linked instanceof PackTemplate) {
$preUpsellPack = $this->packFactory->getPreUpsellPack($linked);
}
return $preUpsellPack;
}
/**
* @param Cart $cart
* @param string $format
* $format is a random string (you can choose abbr),
* for now we only cover the format "qXn" - means quantityXname => 1xBrulafine, 2xC-Konjac, 3xBrulafine1xC-Konjac etc.
* @return array|string
*/
public function extractProductsFromCart(Cart $cart, string $format = 'qXn')
{
$cartPacks = $cart->getPacks();
$tempArray = [];
/** @var Pack $pack */
foreach ($cartPacks as $pack) {
$packProducts = $pack->getPackProducts();
/** @var PackProduct $packProduct */
foreach ($packProducts as $packProduct) {
$product = $packProduct->getProduct();
if ($product->getProductType() == Product::ELECTRONIC_PRODUCT || 0 == $packProduct->getProductQuantity()) {
continue;
}
$productQuantity = $packProduct->getProductQuantity();
$productName = $packProduct->getProduct()->getProductName();
if (!key_exists($productName, $tempArray)) {
$tempArray[$productName] = $productQuantity;
} else {
$tempArray[$productName] = $tempArray[$productName] + $productQuantity;
}
}
}
// we can extend this, by given format to have diff return result
if ('qXn' == $format) {
$string = '';
foreach ($tempArray as $product => $quantity) {
$string .= $quantity . 'x' . $product;
}
return $string;
}
// by default, we return the array.
return $tempArray;
}
/**
* @param User $user
* @param PackTemplate $packTemplate
* @return array
*/
public function createFreeCartForUserByPackTemplate(User $user, PackTemplate $packTemplate)
{
$tracking = $user->getTracking();
/** @var Cart $lastCart */
$lastCart = $user->getLastOrder();
$cartAddress = $lastCart->getCartAddress();
$duplicateCartAddress = clone $cartAddress;
$aff = $lastCart->getAffiliate();
$site = $user->getRegisteredFrom();
$packTemplate = $this->em
->getRepository(PackTemplate::class)
->findOneBy(['packTemplateId' => $packTemplate->getPackTemplateId()]);
$cart = $this->makeCartFromPackTemplate($packTemplate, $site, $tracking, $aff, [], $user, $duplicateCartAddress);
$this->removeCoaching($cart);
$cart->setCartState(Cart::STATE_ADDRESS_ASSIGNED);
$purchase = new Purchase();
$purchase->setPaymentMethod(Purchase::PAYMENT_FREE);
$cart->setCartBillingType(Cart::BILLING_TYPE_FREE);
$this->em->flush();
$userPayment = new CardPayment($user->getUserIp(), $user->getUserAgent());
return [
"cart" => $cart,
"site" => $site,
"userPayment" => $userPayment,
"purchase" => $purchase,
];
}
/**
* @param User $user
* @param $promoAmount
* @return bool
*/
public function createPromoCodeForUser(User $user, $promoAmount, $generationType = DiscountCodeManager::DISCOUNT_CODE_NAME_GENERATION_TYPE_CREDIT_NOTE)
{
try {
$this->discountCodeManager->addNewDiscountCodeFromSupportToUser($user, $promoAmount, $generationType);
$created = true;
} catch (Exception $exception) {
$this->logger->critical("Failed to create a promo code from the support for user: " . $user->getId() . " and exception: " . $exception->getMessage());
$created = false;
}
return $created;
}
/**
* @param Cart $cart
* @param Cart $refundedCart
* @param User $supportUser
*/
public function addCartSupportRefund(
Cart $cart,
Cart $refundedCart,
User $supportUser
) {
$cartRefundedBySupport = new CartSupportRefund();
$cartRefundedBySupport->setCartForRefund($cart);
$cartRefundedBySupport->setRefundedCart($refundedCart);
$cartRefundedBySupport->setSupportBy($supportUser);
$cartRefundedBySupport->setCreatedDate(new \DateTime('now'));
$this->em->persist($cartRefundedBySupport);
$this->em->flush();
}
/**
* @param Cart $refundedCart
* @return bool
*/
public function cartAlreadyRefundedBySupport(Cart $refundedCart)
{
$found = $this->em->getRepository(CartSupportRefund::class)->findOneBy(["refundedCart" => $refundedCart]);
if ($found instanceof CartSupportRefund) {
return true;
}
return false;
}
/**
* @param Cart $lastOrder
* @param Setup $setup
* @param Packages $package
* @param Request $request
* @return array
*/
public function getUpsellOfferData(Cart $lastOrder, Setup $setup, Packages $package, Request $request): array
{
$template = 'offer_upsell.html.twig';
$locale = $request->getLocale();
$packForCart = null;
$createCart = false;
$upsellProducts = [];
if ($lastOrder->getSite()->hasUpsells()) {
$upsellPacks = $this->packFactory->getUpsellPacks(true);
$upsellProducts = $this->ABTestManager->getPacksForTracking($setup->getTracking(), $upsellPacks);
}
$selectedProduct = null;
$selectedId = (int) $request->request->get('id');
if (!empty($upsellProducts) && null != $selectedId) {
foreach ($upsellProducts as $upsellProduct) {
if ($upsellProduct->getSitePackTemplateId() === $selectedId) {
$selectedProduct = $upsellProduct;
break;
}
}
}
if ($selectedProduct instanceof SitePackTemplate) {
$packForCart = $selectedProduct->getPackTemplate();
$createCart = true;
}
// we create a cart for upsell pack when:
// 1. Summary modal is displayed.
// OR
// 2. Quantity is changed
$selectedQuantity = (int) $request->request->get('quantity');
if ($request->request->get('action') == "showSummaryModal" || $selectedQuantity) {
$createCart = true;
}
$upsellProductsArrayForTwig = [];
$upsellProductsArrayForTwigWithKeys = [];
foreach ($upsellProducts as $upsellProduct) {
/** @var PackTemplate $packTemplate */
$packTemplate = $upsellProduct->getPackTemplate();
$options = $packTemplate->getPackTemplateOptions();
$title = $options['title'][$locale];
$product = [
'id' => $upsellProduct->getSitePackTemplateId(),
'name' => $packTemplate->getPackTemplateName(),
'title' => $title,
'desc_short' => $options['short_desc'][$locale],
'description_medium' => $options['medium_desc'][$locale],
'reduction_percent' => $options['reduc_percent'],
'price' => $options['normal_price'],
'reducted_price' => $options['reduc_price'],
'quantity' => 1,
'image' => $package->getUrl($options['image_link'][$locale]),
'image_large' => $package->getUrl($options['image_large_link'][$locale]),
'category' => $options['category'][$locale],
'category_color' => $options['category_color'],
];
if (isset($options['suggest_pack'])) {
$product['pack'] = $options['suggest_pack'];
$product['pack_modal_title'] = $options['modal_title'][$locale];
}
$upsellProductsArrayForTwig[] = $product;
$upsellProductsArrayForTwigWithKeys[$product['name']] = $product;
}
return [
'template' => $template,
'packForCart' => $packForCart,
'createCart' => $createCart,
'templateData' => [
'upsellProducts' => $upsellProducts,
'upsellProductsArray' => $upsellProductsArrayForTwigWithKeys,
'upsellProductsJs' => json_encode($upsellProductsArrayForTwig),
'lastFour' => $lastOrder->getPaymentTransaction()->getPaymentToken() instanceof PaymentToken ? $lastOrder->getPaymentTransaction()->getPaymentToken()->getLastFour() : PaymentToken::DEFAULT_EMPTY_LAST_FOUR,
]
];
}
/**
* @param Cart $lastOrder
* @param Setup $setup
* @return array
*/
public function getPreUpsellOfferData(Cart $lastOrder, Setup $setup): array
{
$template = 'offer_pre_upsale.html.twig';
$packForCart = null;
$createCart = false;
$preUpsellPack = null;
$packTemplate = null;
if ($lastOrder->getSite()->hasPreUpsells()) {
/** @var Pack $pack */
$pack = $lastOrder->getPacks()[0];
/** @var PackTemplate $linked */
$linked = $pack->getPackTemplate()->getLinkedPreUpsellPackTemplates();
$sitePackTemplates = [];
/** @var PackTemplate $pack */
foreach ($linked as $pack) {
$sitePackTemplates[] = $pack->getSitePackTemplates()[0];
}
$personalisedPack = $this->ABTestManager->getPacksForTracking($setup->getTracking(), $sitePackTemplates);
if (count($personalisedPack) && $personalisedPack[0] instanceof SitePackTemplate) {
/** @var SitePackTemplate $pack */
foreach ($personalisedPack as $pack) {
$packTemplateOptions = $pack->getPackTemplate()->getPackTemplateOptions();
if (isset($packTemplateOptions['pack_type']) && 'offer' == $packTemplateOptions['pack_type']) {
$packTemplate = $pack->getPackTemplate();
}
}
}
if ($packTemplate instanceof PackTemplate) {
$preUpsellPack = $this->packFactory->getPreUpsellPack($packTemplate);
$packForCart = $packTemplate;
$createCart = true;
}
}
return [
'template' => $template,
'packForCart' => $packForCart,
'createCart' => $createCart,
'templateData' => [
"preUpsellPack" => $preUpsellPack
]
];
}
/**
* @param Cart $offerCart
* @param Request $request
*/
public function updateOfferCartProductQuantity(Cart $offerCart, Request $request)
{
$selectedQuantity = (int) $request->request->get('quantity');
if ($selectedQuantity > UpsellCart::MAX_SINGLE_ITEM) {
$selectedQuantity = 10;
}
if ($selectedQuantity < 0) {
$selectedQuantity = 1;
}
/** @var Pack $pack */
$pack = $offerCart->getPacks()[0];
/** @var PackProduct $packProduct */
foreach ($pack->getPackProducts() as $packProduct) {
$packProduct->setProductQuantity($selectedQuantity);
}
$this->refreshCartPrices($offerCart);
$this->em->flush();
}
/**
* @param Cart $cart
* @return SitePackTemplate|mixed
*/
public function getSitePackTemplateForCart(Cart $cart)
{
/** @var Pack $cartPack */
$cartPack = $cart->getPacks()[0];
/** @var PackTemplate $packTempalte */
$packTemplate = $cartPack->getPackTemplate();
$sitePackTemplates = $packTemplate->getSitePackTemplates();
/** @var Site $cartSite */
$cartSite = $cart->getSite();
$sitePackTemplatesArray = [];
/** @var SitePackTemplate $sitePackTemplate */
foreach ($sitePackTemplates as $sitePackTemplate) {
if ($cartSite !== $sitePackTemplate->getSite() || !$sitePackTemplate->isActive()) {
continue;
}
$sitePackTemplatesArray[] = $sitePackTemplate;
}
if (count($sitePackTemplatesArray) > 1) {
$this->logger->critical('We found more than one Site Pack Templates! Check that: ' . print_r($sitePackTemplatesArray, true));
}
return $sitePackTemplatesArray[0];
}
// PRIVATE FUNCTIONS
/**
* Tell whether the current cart shippable state should be updated
*
* @param bool $state last shippable state
* @param Transaction $transaction current transaction state
*
* @return bool
*/
private function updateShippableState(bool $state, Transaction $transaction): bool
{
if (
!$state
&& $transaction->getTransactionType() == Transaction::BILL
&& $transaction->getTransactionStatus() == Transaction::OK
&& $transaction->getTransactionAquirerReason() != 'Dummy OK 111'
&& $transaction->getBillingAccount()->getBillingAccountName() != 'MERKAV@DUMMY'
) {
return true;
}
if ($state
&& $transaction->getTransactionStatus() == Transaction::OK
&& in_array($transaction->getTransactionType(), [Transaction::REFUND, Transaction::CHARGEBACK], true)
) {
return false;
}
return $state;
}
/**
* @param array $defaultProposedPacks
* @param Cart $currentCart
* @return array|void
*/
private function getPossibleBiggerPacks(array $defaultProposedPacks, Cart $currentCart)
{
usort($defaultProposedPacks, function ($item1, $item2) {
return $item2->getSitePackTemplateOrder() <=> $item2->getSitePackTemplateOrder();
});
$possibleBiggerPacks = [];
/** @var SitePackTemplate $defaultProposedPack */
foreach ($defaultProposedPacks as $defaultProposedPack) {
$packTemplate = $defaultProposedPack->getPackTemplate();
$packTemplateOptions = $defaultProposedPack->getPackTemplate()->getPackTemplateOptions();
$packTemplateOptionsMonth = $packTemplateOptions['month'];
if ($packTemplate->getPackTemplateType() == PackTemplate::CUSTOM_PACK) {
continue;
}
/** @var Pack $pack */
foreach ($currentCart->getPacks() as $pack) {
if ($pack->getPackType() == Pack::CUSTOM_PACK) {
return;
}
$currentPackOptionMonth = $pack->getPackOptions()['month'];
if ($currentPackOptionMonth < $packTemplateOptionsMonth) {
$possibleBiggerPacks[] = $defaultProposedPack;
}
}
}
if (empty($possibleBiggerPacks)) {
return;
}
return $possibleBiggerPacks;
}
/**
* @param array $defaultProposedPacks
* @param Cart $currentCart
* @return array|mixed|void|null
*/
private function getPushPack(array $defaultProposedPacks, Cart $currentCart)
{
if ($currentCart->isPurchased()) {
return;
}
$pack = $currentCart->getPacks()[0];
if (Pack::CUSTOM_PACK !== $pack->getPackType()) {
return $this->getPossibleBiggerPacks($defaultProposedPacks, $currentCart);
}
// we never reach that, because we dont show switch page on Custom Packs.
// see cartSwitchAction
// However, I refactored it to be compatible
$maxQuantity = 0;
foreach ($pack->getPackProducts() as $packProduct) {
if ($packProduct->getProductQuantity() > $maxQuantity) {
$maxQuantity = $packProduct->getProductQuantity();
}
}
if ($maxQuantity > 3 || $maxQuantity < 1) {
return;
}
/** @var SitePackTemplate $dPP */
foreach ($defaultProposedPacks as $dPP) {
$packTemplate = $dPP->getPackTemplate();
$packTemplateOptions = $packTemplate->getPackTemplateOptions();
if ($maxQuantity === $packTemplateOptions['month']) {
return $dPP;
}
}
return null;
}
private function extractExternalQuantity(
array $items,
PackTemplateProduct $packTemplateProduct
): int {
foreach ($items as $k => $v) {
if (!preg_match('/^([0-9]+)_(.*)$/', $k, $m)) {
continue;
}
if (!is_numeric($m[1])) {
continue;
}
if (!(is_numeric($v) && $v >= 0)) {
continue;
}
if ($m[1] == $packTemplateProduct->getPackTemplateProductId()) {
return (int)abs($v);
}
}
return -1;
}
private function refreshPrices(CartInterface $cart)
{
/** @var PackInterface $pack */
foreach ($cart->getPacks() as $pack) {
$this->packFactory->makePackDiscounts($pack, $cart->getUser(), $cart->getCartShippingCountry(), $cart->getShippingRule());
$this->packFactory->makeProductTypeGroups($pack);
}
}
private function refreshCartPrices(Cart $cart)
{
$bill = $this->getBill($cart);
$cart->setCartTotalAmount($bill->getReducedAmount());
$cart->setCartTotalShippingAmount($bill->getShippingAmount());
}
/**
* @param Cart $cart
* @return Bill
*/
private function getCartBill(CartInterface $cart): Bill
{
$price = 0;
$priceReduced = 0;
$priceShipping = 0;
$promoAmount = 0;
$creditNoteAmount = 0;
$payedAfterCN = 0;
$originalShippingAmount = 0;
$bills = [];
$this->refreshPrices($cart);
/** @var Pack $pack */
foreach ($cart->getPacks() as $pack) {
$bills[] = $this->getPackBill($pack);
}
foreach ($bills as $loopBill) {
$price += $loopBill->getBaseAmount();
$priceReduced += $loopBill->getReducedAmount();
$priceShipping += $loopBill->getShippingAmount();
$promoAmount += $loopBill->getPromoAmount();
$creditNoteAmount += $loopBill->getCreditNoteAmount();
$payedAfterCN += $loopBill->getPayedAfterCNAmount();
$originalShippingAmount += $loopBill->getOriginalShippingAmount();
}
$bill = new Bill(Bill::TYPE_PACK, 1, $price, $priceReduced, $promoAmount, $priceShipping, false, $creditNoteAmount, $payedAfterCN, $originalShippingAmount);
$billToReturn = $bill->withPromoAmount($cart, $this->cartRepository);
// Determine wich tracking to use for AB tests
if ('cli' !== php_sapi_name() && 'test' !== $this->environment && !$cart->getTracking() instanceof Tracking) {
$tracking = $this->trackingFactory->getCurrentTracking();
} else {
$tracking = $cart->getTracking();
}
// if ($tracking instanceof Tracking && $this->ABTestManager->hasTest($tracking, SiteABTest::FREE_SHIPPING_ON)) {
// $billWithShipping = $billWithPromo->withShippingAmount(0, $cart->getDiscountCode());
// } else {
$billToReturn = $billToReturn->withShippingAmount($priceShipping, $cart->getDiscountCode());
// }
$billToReturn = $billToReturn->withCreditNote($cart);
return $billToReturn;
}
private function getPackBill(PackInterface $pack): Bill
{
$cart = $pack->getCart();
$this->refreshPrices($cart);
$affectedByPromocode = true;
if ($pack->getPackTemplate() instanceof PackTemplate && $pack->getPackTemplate()->isWithoutPromoCode()) {
$affectedByPromocode = false;
}
$bill = new Bill(Bill::TYPE_PACK, 1, $pack->getPackPrice($pack), $pack->getPackPriceReduced(), 0, 0, $affectedByPromocode);
$billWithPromo = $bill->withPromoAmount($cart, $this->cartRepository);
$billWithShipping = $billWithPromo->withShippingAmount($pack->getPackShippingPrice(), $cart->getDiscountCode());
$billWithCreditNote = $billWithShipping->withCreditNote($cart);
return $billWithCreditNote;
}
private function getPackProductBill(PackProductInterface $packProduct): Bill
{
$cart = $packProduct->getPack()->getCart();
$this->refreshPrices($cart);
$quantity = $packProduct->getProductQuantity();
$affectedByPromocode = true;
if ($packProduct->getPack()->getPackTemplate() instanceof PackTemplate && $packProduct->getPack()->getPackTemplate()->isWithoutPromoCode()) {
$affectedByPromocode = false;
}
$bill = new Bill(
Bill::TYPE_PRODUCT,
$quantity,
$packProduct->getProduct()->getProductPrice() * $quantity,
$packProduct->getPackProductPriceReduced() * $quantity,
0,
0,
$affectedByPromocode
);
$billWithPromo = $bill->withPromoAmount($cart, $this->cartRepository);
return $billWithPromo;
}
private function getPackProductTypeBill(PackInterface $pack, int $product_type): Bill
{
$cart = $pack->getCart();
$this->refreshPrices($cart);
$price = 0;
$priceReduced = 0;
foreach ($pack->getPackProducts() as $packProduct) {
if ($product_type !== $packProduct->getProduct()->getProductType()) {
continue;
}
$price += $packProduct->getProduct()->getProductPrice() * $packProduct->getProductQuantity();
$priceReduced += $packProduct->getPackProductPriceReduced() * $packProduct->getProductQuantity();
}
$affectedByPromocode = true;
if ($pack->getPackTemplate() instanceof PackTemplate && $pack->getPackTemplate()->isWithoutPromoCode()) {
$affectedByPromocode = false;
}
$bill = new Bill(Bill::TYPE_PACK, 1, $price, $priceReduced, 0, 0, $affectedByPromocode);
$billWithPromo = $bill->withPromoAmount($cart, $this->cartRepository);
return $billWithPromo;
}
/**
* Add a specific billing Account to all bills
*
* @param array $bills
* @param BillingAccount $billingAccount
*
* @return array a collection of bill array
*/
private function addBillingAccount(array $bills, BillingAccount $billingAccount): array
{
foreach ($bills as &$bill) {
$bill['billingAccount'] = $billingAccount;
}
unset($bill);
return $bills;
}
/**
* Add smart routed billing accounts to all bills
*
* @param array $bills
* @param STSClient $stsClient
* @param string $token Merkav Token returned from ADDCustomData
*
* @return array a collection of bill array
*/
private function addSmartRoutingBillingAccount(array $bills, STSClient $stsClient, string $token): array
{
$cache = [];
foreach ($bills as &$bill) {
$bill = $this->applySmartBillingAccount($bill, $stsClient, $token, $cache);
}
unset($bill);
return $bills;
}
/**
* Add a Smart routing billing account to a single bill
*
* @param array $bill
* @param STSClient $client
* @param string $token Merkav Token returned from ADDCustomData
* @param array &$cache Memory cache containing already resolved Smart Routing billing Account
*
* @return array
*/
private function applySmartBillingAccount(array $bill, STSClient $client, string $token, array &$cache = []): array
{
//is smart routing enabled ?
$smartBillingActivated = $this->siteManager->getYamlConfigParameter('billing.smart_routing_activated');
if (!$smartBillingActivated) {
return $bill;
}
$currentBillingAccount = $bill['billingAccount'] ?? null;
// if there is no current BA return.
if (!$currentBillingAccount instanceof BillingAccount) {
return $bill;
}
// if this account doesn't support sr, ignore it
if (1 != $currentBillingAccount->getBillingAccountSmartRouting()) {
return $bill;
}
if ($currentBillingAccount->getBillingAccountType() == BillingAccount::BILLING_ACCOUNT_TYPE_DUMMY && 'test' != $this->environment) {
return $bill;
}
// don't use smart routing in case of free purchase (not for electronic products)
if (Product::ELECTRONIC_PRODUCT != $bill["productType"] && 0 === $bill['totalAmount']) {
return $bill;
}
//electroning product factu is diff from shippied product facturation
switch ($bill['productType']) {
case Product::ELECTRONIC_PRODUCT:
$facturationName = $this->siteManager->getCurrentSite()->getProperties()['smart-routing']['coaching'] ?? $this->siteManager->getYamlConfigParameter('billing.smart_routing_coaching_name');;
break;
case Product::SHIPPIED_PRODUCT:
$facturationName = $this->siteManager->getCurrentSite()->getProperties()['smart-routing']['products'] ?? $this->siteManager->getYamlConfigParameter('billing.smart_routing_pills_name');;
break;
default:
$this->logger->info(sprintf('No facturation name was found for the product type : %s', $bill['productType']));
return $bill;
}
// Do we already have the cache ? If yes return the data.
if (isset($cache[$facturationName]) && isset($cache['3ds'][$facturationName])) {
$bill['billingAccount'] = $cache[$facturationName];
$bill['billingAccount3ds'] = $cache['3ds'][$facturationName];
return $bill;
}
$response = $client->findMerkavBillingAccount($token, $facturationName, 'BILL', $bill['totalAmount']);
// did we get the smart routing info ?
if ($response->isError()) {
$this->logger->info(sprintf(
'Smart Billing Account not fetched for %s: %s %s %s',
$bill['billingAccount']->getBillingAccountName(),
$response->getData()['type'],
json_encode($response->getData()['request'], JSON_PRETTY_PRINT),
$response->getData()['response']
));
$this->logger->critical("Smart Billing Error : Request => " . print_r($response->getData()['request'], true) . ", Response : " . print_r($response->getData()['response'], true));
return $bill;
}
$data = $response->getData();
if ($response->isFail() || !isset($data['merkav_mid_id']) || !isset($data['merkav_mid_id3ds'])) {
$this->logger->error(sprintf(
'Smart Billing Account not found for %s, found %s',
$bill['billingAccount']->getBillingAccountName(),
json_encode($data, JSON_PRETTY_PRINT)
));
return $bill;
}
$smartBillingAccount = $this->em->getRepository(BillingAccount::class)
->findOneBy(['billingAccountMerkavId' => $data['merkav_mid_id']]);
if (null === $smartBillingAccount) {
$this->logger->info(sprintf(
'Smart Billing Account not present in DB for %s: %s',
$bill['billingAccount']->getBillingAccountName(),
$data['merkav_mid_id']
));
return $bill;
}
$smartBillingAccount3ds = $this->em->getRepository(BillingAccount::class)
->findOneBy(['billingAccountMerkavId' => $data['merkav_mid_id3ds']]);
if (!$smartBillingAccount3ds instanceof BillingAccount) {
$this->logger->info(sprintf(
'Smart Billing Account 3DS not present in DB for %s: %s',
$bill['billingAccount']->getBillingAccountName(),
$data['merkav_mid_id3ds']
));
return $bill;
}
$this->logger->info(sprintf(
'Smart Billing Account used for %s is %s and 3ds is %s',
$bill['billingAccount']->getBillingAccountName(),
$smartBillingAccount->getBillingAccountName(),
$smartBillingAccount3ds->getBillingAccountName()
));
$cache[$facturationName] = $smartBillingAccount;
$cache['3ds'][$facturationName] = $smartBillingAccount3ds;
$bill['billingAccount'] = $smartBillingAccount;
$bill['billingAccount3ds'] = $smartBillingAccount3ds;
return $bill;
}
}