<?php declare(strict_types=1);
namespace VRPayment\VRPay\Subscriber;
use http\Exception\UnexpectedValueException;
use Psr\Container\ContainerInterface;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentFinalizeException;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use VRPayment\VRPay\Core\Checkout\Order\TransactionDefinition;
use VRPayment\VRPay\Core\Checkout\Order\TransactionEntity;
use VRPayment\VRPay\Service\Payment\Interfaces\VRPaymentInterface;
use VRPayment\VRPay\Service\VRPay\Client;
/**
* Class TransactionSubscriber
* Reacts on changes in our transaction entities to react to state changes and apply those changes through the payment
* gateway
*
* @package VRPayment\VRPay\Subscriber
*/
class TransactionSubscriber implements EventSubscriberInterface
{
/**
* @var EntityRepository
*/
private $transactionRepository;
/**
* @var Client
*/
private $client;
/**
* @var SalesChannelContextFactory
*/
private $salesChannelContextFactory;
/**
* @var ContainerInterface
*/
private $container;
public static function getSubscribedEvents()
{
return [
EntityWrittenContainerEvent::class => 'onEntityWritten',
];
}
/**
* TransactionSubscriber constructor.
* @param EntityRepository $transactionRepository
* @param Client $client
* @param SalesChannelContextFactory $salesChannelContextFactory
*/
public function __construct(EntityRepository $transactionRepository, Client $client, $salesChannelContextFactory)
{
$this->transactionRepository = $transactionRepository;
$this->client = $client;
$this->salesChannelContextFactory = $salesChannelContextFactory;
}
/**
* @internal
* @required
*/
public function setContainer(ContainerInterface $container): ?ContainerInterface
{
$previous = $this->container;
$this->container = $container;
return $previous;
}
/**
* Called when any entity within Shopware was written to the db. Only reacts to writes of our transactions.
* If refundedAt was set, perform a refund with the payment gateway.
*
* @param EntityWrittenContainerEvent $event
*
* @throws \Exception
*/
public function onEntityWritten(EntityWrittenContainerEvent $event)
{
foreach($event->getEvents() as $writtenEvent)
{
/** @var EntityWrittenEvent $writtenEvent */
if($writtenEvent->getEntityName() !== TransactionDefinition::ENTITY_NAME)
{
continue;
}
foreach($writtenEvent->getPayloads() as $payload)
{
if(!isset($payload['refundedAt']) || empty($payload['refundedAt']))
{
continue;
}
$this->refundPayment($payload['id']);
}
}
}
/**
* refunds a payment at the payment provider. If something goes wrong, our transaction is updated to have
* refundedAt back at null to reflect the correct state of the transaction.
*
* @param string $transactionId
*
* @throws \Exception
*/
private function refundPayment(string $transactionId)
{
$defaultContext = \Shopware\Core\Framework\Context::createDefaultContext();
// search the vr pay transaction by the given vr pay transaction id and fetch the relations we need for refunding
$vrpayTransactions = $this->transactionRepository->search(
(new Criteria([$transactionId]))
->addAssociation('orderTransaction.paymentMethod')
->addAssociation('orderTransaction.order.salesChannel.currency'),
$defaultContext
);
if($vrpayTransactions->getTotal() !== 1)
{
throw new \OutOfBoundsException('unkown vr pay transaction given for refund');
}
/** @var TransactionEntity $vrpayTransaction */
$vrpayTransaction = $vrpayTransactions->getEntities()->first();
try
{
// gsther required information and call the api to refund
$shopwareTransaction = $vrpayTransaction->getOrderTransaction();
$paymentHandlerClass = $shopwareTransaction->getPaymentMethod()->getHandlerIdentifier();
/** @var VRPaymentInterface $paymentHandler */
$paymentHandler = $this->container->get($paymentHandlerClass);
$this->client->refundTransaction(
$paymentHandler,
$this->salesChannelContextFactory->create(uniqid(), $shopwareTransaction->getOrder()->getSalesChannelId()),
$vrpayTransaction->getTransactionId(),
$shopwareTransaction->getAmount()->getTotalPrice(),
$shopwareTransaction->getOrder()->getSalesChannel()->getCurrency()->getIsoCode()
);
}
catch(\Exception $e)
{
// if anything goes wrong, update the transaction to NULL the refundedAt field
$this->transactionRepository->update([['id' => $vrpayTransaction->getId(), 'refundedAt' => null]], $defaultContext);
throw $e;
}
}
}