<?php declare(strict_types=1);
namespace Avency\Shopware\Core\Core\Subscriber;
use Avency\Shopware\Core\Core\Content\Event\AvencyNoCalculationByTypesEvent;
use Avency\Shopware\Core\Core\Content\Product\Cart\MinimumOrderAmountError;
use Avency\Shopware\Core\Core\Service\CalculationService;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\System\Currency\CurrencyFormatter;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedEvent;
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
use Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPageLoadedEvent;
use Shopware\Storefront\Page\PageLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class CheckoutConfirmPageSubscriber implements EventSubscriberInterface
{
const MESSAGE_SIMPLE = 'simple';
const MESSAGE_NO_VALUE = 'no-value';
const MESSAGE_MISSING_SUM = 'missing-sum';
const MESSAGES_TYPES = [
'simple' => self::MESSAGE_SIMPLE,
'noValue' => self::MESSAGE_NO_VALUE,
'missingSum' => self::MESSAGE_MISSING_SUM,
];
/**
* @var SystemConfigService
*/
protected $systemConfigService;
/**
* @var CurrencyFormatter
*/
private $currencyFormatter;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var CalculationService
*/
private $calculationService;
public function __construct(
SystemConfigService $systemConfigService,
CurrencyFormatter $currencyFormatter,
CalculationService $calculationService,
EventDispatcherInterface $eventDispatcher
)
{
$this->systemConfigService = $systemConfigService;
$this->currencyFormatter = $currencyFormatter;
$this->eventDispatcher = $eventDispatcher;
$this->calculationService = $calculationService;
}
public static function getSubscribedEvents(): array
{
return [
CheckoutConfirmPageLoadedEvent::class => 'extendCheckoutConfirm',
CheckoutCartPageLoadedEvent::class => 'extendCheckoutCart',
OffcanvasCartPageLoadedEvent::class => 'extendCheckoutCart',
];
}
/**
* @param PageLoadedEvent $event
*/
public function extendCheckoutCart(PageLoadedEvent $event)
{
if (!$event instanceof CheckoutCartPageLoadedEvent && !$event instanceof OffcanvasCartPageLoadedEvent) {
return;
}
$config = $this->systemConfigService->get('AvencyShopwareCore.config', $event->getSalesChannelContext()->getSalesChannelId());
if (!$config['avencyMinimumOrderShowInCart']) {
return;
}
$this->extendCheckout($event, $config);
}
/**
* @param CheckoutConfirmPageLoadedEvent $event
*/
public function extendCheckoutConfirm(CheckoutConfirmPageLoadedEvent $event)
{
$config = $this->systemConfigService->get('AvencyShopwareCore.config', $event->getSalesChannelContext()->getSalesChannelId());
$this->extendCheckout($event, $config);
}
/**
* @param PageLoadedEvent $event
* @param array $config
*/
private function extendCheckout(PageLoadedEvent $event, array $config)
{
$cart = $event->getPage()->getCart();
$calculatedPositionPrice = $this->calculationService->getCalculatedPositionPrice($cart, $event->getSalesChannelContext(), $config);
if (!$this->errorShouldBeSet($config, $calculatedPositionPrice, $cart, $event)) {
return;
}
$messageType = $this->getMessageType($config);
if ($messageType === self::MESSAGE_MISSING_SUM) {
$missingSum = $config['avencyMinimumOrderAmount'] - $calculatedPositionPrice;
}
if ($event instanceof OffcanvasCartPageLoadedEvent) {
$cart->addExtension('avencyMinimumOrderAmount', new ArrayStruct([
'messageType' => $messageType,
'minimumOrderAmount' => $this->formatPrice($event, $config['avencyMinimumOrderAmount']),
'missingSum' => isset($missingSum) ? $this->formatPrice($event, $missingSum) : null,
]));
} else {
$cart->addErrors(new MinimumOrderAmountError(
$messageType,
[
'minimumOrderAmount' => $this->formatPrice($event, $config['avencyMinimumOrderAmount']),
'missingSum' => isset($missingSum) ? $this->formatPrice($event, $missingSum) : null,
]
));
}
}
/**
* @param PageLoadedEvent $event
* @param float $price
* @return string
*/
private function formatPrice(PageLoadedEvent $event, float $price): string
{
return str_replace(",000", "", str_replace(".000", "", $this->currencyFormatter->formatCurrencyByLanguage(
$price,
$event->getSalesChannelContext()->getCurrency()->getIsoCode(),
$event->getSalesChannelContext()->getSalesChannel()->getLanguageId(),
$event->getSalesChannelContext()->getContext()
)));
}
/**
* Validate if error has to be set (or message in ajax cart)
*
* @param array $config
* @param float $calculatedPositionPrice
* @param Cart $cart
* @param PageLoadedEvent $event
* @return bool
*/
private function errorShouldBeSet(array $config, float $calculatedPositionPrice, Cart $cart, PageLoadedEvent $event): bool
{
$setError = false;
if ($calculatedPositionPrice < $config['avencyMinimumOrderAmount']) {
$setError = true;
}
$evaluateTypes = array_filter(array_map('trim', explode(',', $config['avencyMinimumOrderNoCalculationTypes'] ?? '')));
$evaluateTypes = ($this->eventDispatcher->dispatch(new AvencyNoCalculationByTypesEvent($evaluateTypes, $event->getSalesChannelContext())))->getTypes();
if (!empty($evaluateTypes) && $setError) {
$shouldMatchAtLeastOne = $config['avencyMinimumOrderNoCalculationMatch'] ?? false;
if (!$shouldMatchAtLeastOne && !empty(array_intersect($evaluateTypes, $cart->getLineItems()->getTypes()))) {
// If at least one match is found, set no error
$setError = false;
}
if ($shouldMatchAtLeastOne) {
$lineItems = $cart->getLineItems()->filter(
function (LineItem $lineItem) use ($evaluateTypes) {
return !in_array($lineItem->getType(), $evaluateTypes);
}
);
// If all line items matches the types, set no error
$setError = !($lineItems->count() === 0);
}
return $setError;
}
if (isset($config['avencyMinimumOrderRuleId'])) {
/*
* Validate rule configuration only if
* 1. `$setError` is not relevant (minimum order value is validated by rule)
* 2. `$setError` has to be set to false (there is no minimum order quantity for some customers)
*/
if (
$config['avencyMinimumOrderExtendedAmount'] ||
(!$config['avencyMinimumOrderExtendedAmount'] && $setError)
) {
return (\in_array($config['avencyMinimumOrderRuleId'], $event->getContext()->getRuleIds())) === $config['avencyMinimumOrderRuleValid'];
}
}
return $setError;
}
/**
* Get the message type for snippet
*
* @param array $config
* @return string
*/
private function getMessageType(array $config): string
{
$messageType = self::MESSAGES_TYPES[$config['avencyMinimumOrderMessage']] ?? self::MESSAGE_SIMPLE;
// If minimum order amount is validated by rule we can only set a simple message
if (isset($config['avencyMinimumOrderRuleId']) && $config['avencyMinimumOrderExtendedAmount']) {
$messageType = self::MESSAGE_NO_VALUE;
}
return $messageType;
}
}