<?php
declare(strict_types=1);
namespace PolPaymentPayolutionSW6\EventListener;
use PolPaymentPayolutionSW6\Component\Configuration\ConfigurationServiceInterface;
use PolPaymentPayolutionSW6\Component\CustomFields\CustomFieldsInterface;
use PolPaymentPayolutionSW6\Component\DataHandler\OrderTransactionDataHandler\OrderTransactionDataHandlerInterface;
use PolPaymentPayolutionSW6\Component\DataHandler\OrderTransactionStatusHandler\OrderTransactionStatusHandlerInterface;
use PolPaymentPayolutionSW6\Component\Validator\PaymentMethodValidator\PaymentMethodValidatorInterface;
use PolPaymentPayolutionSW6\Component\Validator\PaymentRequestValidator\PaymentRequestValidatorInterface;
use PolPaymentPayolutionSW6\PayolutionApi\Client\Exception\InvalidXmlException;
use PolPaymentPayolutionSW6\PayolutionApi\Process\Exception\ProcessFailedException;
use PolPaymentPayolutionSW6\PayolutionApi\Request\RequestFactory\PaymentRequestFactoryInterface;
use PolPaymentPayolutionSW6\PayolutionApi\Request\RequestFactory\Transaction\TransactionRequestFactoryInterface;
use PolPaymentPayolutionSW6\PayolutionApi\Request\RequestProcessor\RequestProcessorInterface;
use Psr\Log\LoggerInterface;
use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryEntity;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\System\StateMachine\Event\StateMachineStateChangeEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class StateMachineEventListener implements EventSubscriberInterface
{
/**
* @var EntityRepositoryInterface
*/
private $orderTransactionRepository;
/**
* @var EntityRepositoryInterface
*/
private $orderDeliveryRepository;
/**
* @var OrderTransactionDataHandlerInterface
*/
private $orderTransactionDataHandler;
/**
* @var OrderTransactionStatusHandlerInterface
*/
private $orderTransactionStatusHandler;
/**
* @var TransactionRequestFactoryInterface
*/
private $transactionRequestFactory;
/**
* @var PaymentRequestFactoryInterface
*/
private $paymentRequestFactory;
/**
* @var RequestProcessorInterface
*/
private $captureRequestProcessor;
/**
* @var RequestProcessorInterface
*/
private $refundRequestProcessor;
/**
* @var RequestProcessorInterface
*/
private $reversalRequestProcessor;
/**
* @var ConfigurationServiceInterface
*/
private $configurationService;
/**
* @var PaymentMethodValidatorInterface
*/
private $paymentMethodValidator;
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(
EntityRepositoryInterface $orderTransactionRepository,
EntityRepositoryInterface $orderDeliveryRepository,
OrderTransactionDataHandlerInterface $orderTransactionDataHandler,
OrderTransactionStatusHandlerInterface $orderTransactionStatusHandler,
TransactionRequestFactoryInterface $transactionRequestFactory,
PaymentRequestFactoryInterface $paymentRequestFactory,
RequestProcessorInterface $captureRequestProcessor,
RequestProcessorInterface $refundRequestProcessor,
RequestProcessorInterface $reversalRequestProcessor,
ConfigurationServiceInterface $configurationService,
PaymentMethodValidatorInterface $paymentMethodValidator,
LoggerInterface $logger
) {
$this->orderTransactionRepository = $orderTransactionRepository;
$this->orderDeliveryRepository = $orderDeliveryRepository;
$this->orderTransactionDataHandler = $orderTransactionDataHandler;
$this->orderTransactionStatusHandler = $orderTransactionStatusHandler;
$this->transactionRequestFactory = $transactionRequestFactory;
$this->paymentRequestFactory = $paymentRequestFactory;
$this->captureRequestProcessor = $captureRequestProcessor;
$this->refundRequestProcessor = $refundRequestProcessor;
$this->reversalRequestProcessor = $reversalRequestProcessor;
$this->configurationService = $configurationService;
$this->paymentMethodValidator = $paymentMethodValidator;
$this->logger = $logger;
}
public static function getSubscribedEvents(): array
{
return [
'state_machine.order.state_changed' => 'onOrderStateChange',
'state_machine.order_delivery.state_changed' => 'onOrderDeliveryStateChange',
'state_machine.order_transaction.state_changed' => 'onOrderTransactionStateChange',
];
}
public function onOrderStateChange(StateMachineStateChangeEvent $event): void
{
$orderId = $event->getTransition()->getEntityId();
try {
$orderTransaction = $this->getOrderTransactionByOrderId($orderId, $event->getContext());
} catch (\RuntimeException $e) {
$this->logger->error($e->getMessage());
return;
}
$this->handleOrderStatusChange(
$orderTransaction,
$event,
ConfigurationServiceInterface::ORDER_STATUS_CAPTURE,
ConfigurationServiceInterface::ORDER_STATUS_REFUND,
ConfigurationServiceInterface::ORDER_STATUS_REVERSAL
);
}
public function onOrderDeliveryStateChange(StateMachineStateChangeEvent $event): void
{
$orderDeliveryId = $event->getTransition()->getEntityId();
try {
$orderTransaction = $this->getOrderTransactionByDeliveryId($orderDeliveryId, $event->getContext());
} catch (\RuntimeException $e) {
$this->logger->error($e->getMessage());
return;
}
$this->handleOrderStatusChange(
$orderTransaction,
$event,
ConfigurationServiceInterface::DELIVERY_STATUS_CAPTURE,
ConfigurationServiceInterface::DELIVERY_STATUS_REFUND,
ConfigurationServiceInterface::DELIVERY_STATUS_REVERSAL
);
}
public function onOrderTransactionStateChange(StateMachineStateChangeEvent $event): void
{
$orderTransactionId = $event->getTransition()->getEntityId();
try {
$orderTransaction = $this->getOrderTransactionById($orderTransactionId, $event->getContext());
} catch (\RuntimeException $e) {
$this->logger->error($e->getMessage());
return;
}
$this->handleOrderStatusChange(
$orderTransaction,
$event,
ConfigurationServiceInterface::PAYMENT_STATUS_CAPTURE,
ConfigurationServiceInterface::PAYMENT_STATUS_REFUND,
ConfigurationServiceInterface::PAYMENT_STATUS_REVERSAL
);
}
private function handleOrderStatusChange(
OrderTransactionEntity $orderTransaction,
StateMachineStateChangeEvent $event,
string $configurationNameStatusCapture,
string $configurationNameStatusRefund,
string $configurationNameStatusReversal
): void {
if (!$this->paymentMethodValidator->isPayolutionPaymentMethod($orderTransaction->getPaymentMethod())) {
return;
}
$salesChannelId = $orderTransaction->getOrder()->getSalesChannel()->getId();
$context = $event->getContext();
$statusCapture = $this->configurationService->getConfiguration($configurationNameStatusCapture, $salesChannelId);
if ($event->getTransition()->getTransitionName() === $statusCapture) {
$this->captureOrder($orderTransaction, $context);
$this->orderTransactionStatusHandler->setPaid($orderTransaction, $context);
}
$statusRefund = $this->configurationService->getConfiguration($configurationNameStatusRefund, $salesChannelId);
if ($event->getTransition()->getTransitionName() === $statusRefund) {
$this->refundOrder($orderTransaction, $context);
$this->orderTransactionStatusHandler->setRefund($orderTransaction, $context);
}
$statusReversal = $this->configurationService->getConfiguration($configurationNameStatusReversal, $salesChannelId);
if ($event->getTransition()->getTransitionName() === $statusReversal) {
$this->reversalOrder($orderTransaction, $context);
$this->orderTransactionStatusHandler->setCancelled($orderTransaction, $context);
}
}
private function captureOrder(OrderTransactionEntity $orderTransaction, Context $context): void
{
try {
if ((bool) $orderTransaction->getCustomFields()[CustomFieldsInterface::IS_PAYMENT_CAPTURED]) {
return;
}
$transaction = $this->transactionRequestFactory->createTransaction(
$orderTransaction,
new RequestDataBag(),
$orderTransaction->getAmount()->getTotalPrice(),
null,
[PaymentRequestValidatorInterface::IS_CAPTURE_REQUEST => true]
);
$captureRequest = $this->paymentRequestFactory->createRequest($orderTransaction, $transaction, $orderTransaction->getOrder()->getSalesChannel()->getId());
$captureRequest->setPreAuthorizationUniqueId($orderTransaction->getCustomFields()[CustomFieldsInterface::UNIQUE_ID]);
$captureResponse = $this->captureRequestProcessor->processRequest($captureRequest);
$this->orderTransactionDataHandler->updateOrderTransactionData($orderTransaction, $context, [
CustomFieldsInterface::IS_PAYMENT_CAPTURED => true,
CustomFieldsInterface::REMAINING_CAPTURE_AMOUNT => 0,
CustomFieldsInterface::REMAINING_REFUND_AMOUNT => $orderTransaction->getAmount()->getTotalPrice(),
]);
foreach ($orderTransaction->getOrder()->getLineItems() as $orderLineItem) {
$this->orderTransactionDataHandler->updateOrderLineItemData($orderLineItem, $context, [CustomFieldsInterface::IS_PAYMENT_CAPTURED => true]);
}
$this->logger->info('order successfully captured', $captureResponse->getResponseAsArray());
} catch (InvalidXmlException $xmlException) {
$this->logger->error('order could not captured invalid xml', ['message' => $xmlException->getMessage()]);
} catch (ProcessFailedException $processFailedException) {
$this->logger->error('order could not captured process failed', $processFailedException->getResponse()->getResponseAsArray());
} catch (\Throwable $e) {
$this->logger->error('order could not captured error occurred', ['message' => $e->getMessage()]);
}
}
private function refundOrder(OrderTransactionEntity $orderTransaction, Context $context): void
{
try {
if ((bool) $orderTransaction->getCustomFields()[CustomFieldsInterface::IS_PAYMENT_REFUNDED]) {
return;
}
$transaction = $this->transactionRequestFactory->createTransaction(
$orderTransaction,
new RequestDataBag(),
$orderTransaction->getAmount()->getTotalPrice(),
null,
[PaymentRequestValidatorInterface::IS_REFUND_REQUEST => true]
);
$refundRequest = $this->paymentRequestFactory->createRequest($orderTransaction, $transaction, $orderTransaction->getOrder()->getSalesChannel()->getId());
$refundRequest->setPreAuthorizationUniqueId($orderTransaction->getCustomFields()[CustomFieldsInterface::UNIQUE_ID]);
$refundResponse = $this->refundRequestProcessor->processRequest($refundRequest);
$this->orderTransactionDataHandler->updateOrderTransactionData($orderTransaction, $context, [
CustomFieldsInterface::IS_PAYMENT_REFUNDED => true,
CustomFieldsInterface::REMAINING_REFUND_AMOUNT => 0,
]);
foreach ($orderTransaction->getOrder()->getLineItems() as $orderLineItem) {
$this->orderTransactionDataHandler->updateOrderLineItemData($orderLineItem, $context, [CustomFieldsInterface::IS_PAYMENT_REFUNDED => true]);
}
$this->logger->info('order successfully refunded', $refundResponse->getResponseAsArray());
} catch (InvalidXmlException $xmlException) {
$this->logger->error('order could not refunded invalid xml', ['message' => $xmlException->getMessage()]);
} catch (ProcessFailedException $processFailedException) {
$this->logger->error('order could not refunded process failed', $processFailedException->getResponse()->getResponseAsArray());
} catch (\Throwable $e) {
$this->logger->error('order could not refunded error occurred', ['message' => $e->getMessage()]);
}
}
private function reversalOrder(OrderTransactionEntity $orderTransaction, Context $context): void
{
try {
if ((bool) $orderTransaction->getCustomFields()[CustomFieldsInterface::IS_PAYMENT_CANCELLED]) {
return;
}
$transaction = $this->transactionRequestFactory->createTransaction(
$orderTransaction,
new RequestDataBag(),
$orderTransaction->getAmount()->getTotalPrice(),
null,
[PaymentRequestValidatorInterface::IS_REVERSAL_REQUEST => true]
);
$reversalRequest = $this->paymentRequestFactory->createRequest($orderTransaction, $transaction, $orderTransaction->getOrder()->getSalesChannel()->getId());
$reversalRequest->setPreAuthorizationUniqueId($orderTransaction->getCustomFields()[CustomFieldsInterface::UNIQUE_ID]);
$reversalResponse = $this->reversalRequestProcessor->processRequest($reversalRequest);
$this->orderTransactionDataHandler->updateOrderTransactionData($orderTransaction, $context, [
CustomFieldsInterface::IS_PAYMENT_CANCELLED => true,
]);
$this->logger->info('order successfully cancelled', $reversalResponse->getResponseAsArray());
} catch (InvalidXmlException $xmlException) {
$this->logger->error('order could not cancelled invalid xml', ['message' => $xmlException->getMessage()]);
} catch (ProcessFailedException $processFailedException) {
$this->logger->error('order could not cancelled process failed', $processFailedException->getResponse()->getResponseAsArray());
} catch (\Throwable $e) {
$this->logger->error('order could not cancelled error occurred', ['message' => $e->getMessage()]);
}
}
private function getOrderTransactionById(string $id, Context $context): OrderTransactionEntity
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $id));
$criteria->addAssociations(['order.customer', 'order.currency', 'order.lineItems', 'order.deliveries', 'order.salesChannel', 'paymentMethod']);
/** @var OrderTransactionEntity $orderTransaction */
$orderTransaction = $this->orderTransactionRepository->search($criteria, $context)->first();
if ($orderTransaction === null) {
throw new \RuntimeException(sprintf('order transaction by id %s not found.', $id));
}
return $orderTransaction;
}
private function getOrderTransactionByOrderId(string $orderId, Context $context): OrderTransactionEntity
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('orderId', $orderId));
$criteria->addAssociations(['order.customer', 'order.currency', 'order.lineItems', 'order.deliveries', 'order.salesChannel', 'paymentMethod']);
/** @var OrderTransactionEntity $orderTransaction */
$orderTransaction = $this->orderTransactionRepository->search($criteria, $context)->first();
if ($orderTransaction === null) {
throw new \RuntimeException(sprintf('order transaction by order id %s not found.', $orderId));
}
return $orderTransaction;
}
private function getOrderTransactionByDeliveryId(string $deliveryId, Context $context): OrderTransactionEntity
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $deliveryId));
$criteria->addAssociations(['order', 'order.transactions']);
/** @var OrderDeliveryEntity $orderDeliveryEntity */
$orderDeliveryEntity = $this->orderDeliveryRepository->search($criteria, $context)->first();
if ($orderDeliveryEntity === null) {
throw new \RuntimeException(sprintf('order delivery by delivery id %s not found.', $deliveryId));
}
/** @var OrderTransactionEntity $orderTransaction */
$orderTransaction = $orderDeliveryEntity->getOrder()->getTransactions()->first();
if (!$orderTransaction instanceof OrderTransactionEntity) {
throw new \RuntimeException(sprintf('order transaction by delivery id %s not found.', $deliveryId));
}
return $this->getOrderTransactionByOrderId($orderTransaction->getOrderId(), $context);
}
}