<?php
/**
* @copyright Copyright (c) 2020, Net Inventors GmbH
* @category Shopware
* @author mpeters
*/
declare(strict_types=1);
namespace NetInventors\NetiNextEasyCoupon\Subscriber;
use NetInventors\NetiNextEasyCoupon\Core\Content\EasyCoupon\EasyCouponEntity;
use NetInventors\NetiNextEasyCoupon\Core\Framework\Exception\InvalidTypeException;
use NetInventors\NetiNextEasyCoupon\Service\OrderVoucherService;
use NetInventors\NetiNextEasyCoupon\Service\PluginConfigFactory;
use NetInventors\NetiNextEasyCoupon\Service\RedemptionAndChargebackTransactionsService;
use NetInventors\NetiNextEasyCoupon\Service\Repository\VoucherRepository;
use NetInventors\NetiNextEasyCoupon\Service\RequestedDelivery\RequestedDeliveryMailer;
use NetInventors\NetiNextEasyCoupon\Service\RequestedDelivery\RequestedDeliveryProcessor;
use NetInventors\NetiNextEasyCoupon\Struct\PluginConfigStruct;
use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Api\Context\SalesChannelApiSource;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Shopware\Core\System\SalesChannel\Context\AbstractSalesChannelContextFactory;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\System\SalesChannel\Event\SalesChannelContextSwitchEvent;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\StateMachine\Event\StateMachineStateChangeEvent;
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class OrderSubscriber implements EventSubscriberInterface
{
protected PluginConfigStruct $pluginConfig;
protected OrderVoucherService $orderVoucherService;
private RequestStack $requestStack;
private SalesChannelContextServiceInterface $salesChannelContextService;
private AbstractSalesChannelContextFactory $salesChannelContextFactory;
private EntityRepositoryInterface $orderRepository;
protected RedemptionAndChargebackTransactionsService $redemptionAndChargebackTransactionsService;
private VoucherRepository $voucherRepository;
private RequestedDeliveryProcessor $deliveryProcessor;
private RequestedDeliveryMailer $deliveryMailer;
private PluginConfigFactory $pluginConfigFactory;
private string $swVersion;
public function __construct(
PluginConfigStruct $pluginConfig,
OrderVoucherService $orderVoucherService,
RequestStack $requestStack,
SalesChannelContextServiceInterface $salesChannelContextService,
AbstractSalesChannelContextFactory $salesChannelContextFactory,
EntityRepositoryInterface $orderRepository,
RedemptionAndChargebackTransactionsService $redemptionAndChargebackTransactionsService,
VoucherRepository $voucherRepository,
RequestedDeliveryProcessor $deliveryProcessor,
RequestedDeliveryMailer $deliveryMailer,
PluginConfigFactory $pluginConfigFactory,
?string $swVersion
) {
$this->pluginConfig = $pluginConfig;
$this->orderVoucherService = $orderVoucherService;
$this->requestStack = $requestStack;
$this->salesChannelContextService = $salesChannelContextService;
$this->orderRepository = $orderRepository;
$this->salesChannelContextFactory = $salesChannelContextFactory;
$this->redemptionAndChargebackTransactionsService = $redemptionAndChargebackTransactionsService;
$this->voucherRepository = $voucherRepository;
$this->deliveryProcessor = $deliveryProcessor;
$this->deliveryMailer = $deliveryMailer;
$this->pluginConfigFactory = $pluginConfigFactory;
$this->swVersion = (string) $swVersion;
}
public static function getSubscribedEvents(): array
{
return [
CheckoutOrderPlacedEvent::class => [
[ 'createVoucherByLineItems', 0 ],
[ 'createTransactions', 5000 ],
],
'state_machine.order_transaction.state_changed' => [
'sendVoucherActivateMailByOrderTransaction',
],
// @TODO Also remove redemption voucher on context switch
SalesChannelContextSwitchEvent::class => [
'removePurchaseVoucherFromCart',
],
CheckoutFinishPageLoadedEvent::class => [
[ 'displayPurchaseVouchers', 0 ],
[ 'displayVoucherRestValue', 5000 ],
[ 'removeVoucherCodesFromSession', 9000 ],
],
'order_line_item.written' => 'onOrderLineItemWritten',
'state_machine.order.state_changed' => 'onOrderStateChange',
];
}
/**
* @param StateMachineStateChangeEvent $event
*
* @return void
* @throws InvalidTypeException
*/
public function onOrderStateChange(StateMachineStateChangeEvent $event): void
{
$order = $this->getOrder($event->getTransition()->getEntityId(), $event->getContext());
$pluginConfigOfOrder = $this->pluginConfigFactory->create($order->getSalesChannelId());
if (!$pluginConfigOfOrder->isActive()) {
return;
}
$this->redemptionAndChargebackTransactionsService->chargebackTransaction(
$order,
$event->getContext(),
\in_array(
$event->getNextState()->getId(),
$pluginConfigOfOrder->getVoucherCancelOrderStatus(),
true
)
);
}
protected function getOrder(string $orderId, Context $context): ?OrderEntity
{
$criteria = new Criteria([ $orderId ]);
$criteria->addAssociation('lineItems');
return $this->orderRepository->search($criteria, $context)->first();
}
public function onOrderLineItemWritten(EntityWrittenEvent $event): void
{
// if the live version of the order_line_item is updated, the associated transaction should be updated, too.
foreach ($event->getWriteResults() as $writeResult) {
$existence = $writeResult->getExistence();
if (null === $existence || !$existence->exists()) {
continue; // we don't care about deleted items here
}
$payload = $writeResult->getPayload();
if ($payload['versionId'] !== Defaults::LIVE_VERSION) {
continue; // we only update the transaction if the live version order_line_item is updated
}
$this->orderVoucherService->updateTransactionValueFromOrderLineItem(
$writeResult->getPrimaryKey(),
$event->getContext()
);
}
}
public function createVoucherByLineItems(CheckoutOrderPlacedEvent $event): void
{
if (!$this->pluginConfig->isActive()) {
return;
}
if (!$event->getOrder()->getLineItems() instanceof OrderLineItemCollection) {
return;
}
if (!$this->isProperCheckoutOrderPlacedEvent($event)) {
return;
}
$filteredItems = $this->orderVoucherService->filterLineItemsByEnteredValued($event->getOrder()->getLineItems());
if ([] === $filteredItems->getElements()) {
return;
}
$salesChannelContext = $this->requestStack->getCurrentRequest()->get('sw-sales-channel-context');
if (!$salesChannelContext instanceof SalesChannelContext) {
// We are presumably in an administration context
$salesChannelId = $this->requestStack->getCurrentRequest()->get('salesChannelId');
$contextToken = $this->requestStack->getCurrentRequest()->headers->get('sw-context-token');
if ($salesChannelId && $contextToken) {
$parameters = new SalesChannelContextServiceParameters($salesChannelId, $contextToken);
$salesChannelContext = $this->salesChannelContextService->get($parameters);
}
}
if (!$salesChannelContext instanceof SalesChannelContext) {
// No Sales Channel provided, so we have to create it on our own
$salesChannelContext = $this->salesChannelContextFactory->create(
'',
$event->getOrder()->getSalesChannelId(),
[
SalesChannelContextService::LANGUAGE_ID => $event->getOrder()->getLanguageId(),
SalesChannelContextService::CURRENCY_ID => $event->getOrder()->getCurrencyId(),
]
);
}
$this->orderVoucherService->createVoucherByLineItems(
$event->getOrder()->getLineItems(),
$event->getContext(),
$salesChannelContext
);
}
public function displayPurchaseVouchers(CheckoutFinishPageLoadedEvent $event): void
{
if (!$this->pluginConfig->isActive() || !$this->pluginConfig->isVouchersInfoAtCheckoutActive()) {
return;
}
$this->orderVoucherService->addPurchaseVouchersToCheckoutConfirmPage($event->getPage(), $event->getContext());
}
public function displayVoucherRestValue(CheckoutFinishPageLoadedEvent $event): void
{
if (!$this->pluginConfig->isActive() || !$this->pluginConfig->isVouchersInfoAtCheckoutActive()) {
return;
}
$this->orderVoucherService->displayVoucherRestValueAtCheckoutConfirmPage(
$event->getPage(),
$event->getContext(),
$event->getSalesChannelContext()
);
}
public function createTransactions(CheckoutOrderPlacedEvent $event): void
{
if (!$this->pluginConfig->isActive()) {
return;
}
if (!$this->isProperCheckoutOrderPlacedEvent($event)) {
return;
}
$this->orderVoucherService->createTransactionsForRedeemedVouchers($event->getOrder(), $event->getContext());
}
public function sendVoucherActivateMailByOrderTransaction(StateMachineStateChangeEvent $event): void
{
if (
!$this->pluginConfig->isActive()
|| !\in_array($event->getNextState()->getId(), $this->pluginConfig->getVoucherActivatePaymentStatus(), true)
) {
return;
}
$context = $event->getContext();
/** @var array<string, EasyCouponEntity> $orderedVouchers */
$orderedVouchers = $this->voucherRepository->collectOrderedVouchersByTransactionId(
$event->getTransition()->getEntityId(),
$context
)->getElements();
if ([] === $orderedVouchers) {
return;
}
$order = $this->orderVoucherService->getOrderFromTransactionId($event->getTransition()->getEntityId(), $context);
if ($order instanceof OrderEntity) {
$orderPluginConfig = $this->pluginConfigFactory->create($order->getSalesChannelId());
} else {
$orderPluginConfig = $this->pluginConfig;
}
if ($orderPluginConfig->isActivationMailRequired()) {
// Send voucher activation mails
$this->orderVoucherService->sendVoucherActivateMailByOrderTransaction(
$orderedVouchers,
$context
);
}
// Send requested delivery mails
$easyCouponIds = array_map(static function(EasyCouponEntity $voucher) {
return $voucher->getId();
}, $orderedVouchers);
$deliveries = $this->deliveryProcessor->collectDeliveries($context, $easyCouponIds);
if (null === $deliveries) {
return;
}
$this->deliveryMailer->deliverVouchers($deliveries, $context);
}
public function removePurchaseVoucherFromCart(SalesChannelContextSwitchEvent $event): void
{
if (!$this->pluginConfig->isActive()) {
return;
}
$currencyId = $event->getRequestDataBag()->get('currencyId');
if (!(\is_string($currencyId) && '' !== $currencyId)) {
return;
}
$this->orderVoucherService->removePurchaseVoucherFromCart($event->getSalesChannelContext());
}
public function removeVoucherCodesFromSession(CheckoutFinishPageLoadedEvent $event): void
{
if (!$this->pluginConfig->isActive()) {
return;
}
$event->getRequest()->getSession()->remove('EasyCouponVoucherCodes');
}
private function isProperCheckoutOrderPlacedEvent(CheckoutOrderPlacedEvent $event): bool
{
$contextSource = $event->getContext()->getSource();
$contextStates = $event->getContext()->getStates();
return (
\version_compare($this->swVersion, '6.4.11.0', '<')
|| !$contextSource instanceof SalesChannelApiSource
|| (\in_array('checkout-order-route', $contextStates, true))
);
}
}