<?php declare(strict_types=1);
namespace App\Subscriber;
use App\Entity\CreditHistory;
use App\Entity\PaymentVxsOrder;
use App\Entity\User;
use App\Event\CreditsEvent;
use App\Event\Recurring\MembershipPaymentCancelledEvent;
use App\Event\Recurring\MembershipPaymentEventInterface;
use App\Event\Recurring\MembershipPaymentReceivedEvent;
use App\Model\ChangeCard;
use Doctrine\DBAL\Connection;
use Doctrine\Persistence\ManagerRegistry;
use DomainException;
use LogicException;
use RuntimeException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ManageCreditsHistorySubscriber implements EventSubscriberInterface
{
private ManagerRegistry $doctrine;
public function __construct(ManagerRegistry $doctrine)
{
$this->doctrine = $doctrine;
}
public function onCredits(CreditsEvent $event): void
{
/** @var Connection $connection */
$connection = $this->doctrine->getConnection();
if (!$connection->isTransactionActive()) {
throw new RuntimeException('You must start DB transaction in order to use this event');
}
if (!($event->getUser() instanceof User)) {
throw new RuntimeException('The event must have a user.');
}
$creditsRelated = $event->getRelatedEntity();
$creditHistory = new CreditHistory();
$creditHistory->setUser($event->getUser());
$creditHistory->setRelatedEntity($creditsRelated, $event->getAction());
if (null !== $event->getBillAt()) {
$creditHistory->setLastModified(clone $event->getBillAt());
}
$credits = $event->getCredits();
if (in_array($event->getAction(), [CreditsEvent::ACTION_CHARGEBACK, CreditsEvent::ACTION_REFUND], true)) {
$credits *= -1;
if (!($creditsRelated instanceof PaymentVxsOrder)) {
throw new DomainException('Other related entities refund is not implemented.');
}
$refundedCreditsHistory = $this->doctrine->getRepository(CreditHistory::class)
->findOneBy(['vxsOrder' => $creditsRelated]);
if (null === $refundedCreditsHistory) {
throw new LogicException('Can not find CreditsHistory to refund.');
}
$refundedCreditsHistory->setRefunded(true);
}
$creditHistory->setCredits($credits);
if (0 === $credits
&& $creditsRelated instanceof PaymentVxsOrder
&& $event->getAction() === CreditsEvent::ACTION_CREDITS
&& $creditsRelated->getAmountFloat() === ChangeCard::CREDIT_CHANGE_AMOUNT
) {
$creditHistory->setRelatedEntity($creditsRelated, $event->getAction() . '-change-card');
}
$this->doctrine->getManager()->persist($creditHistory);
$this->doctrine->getManager()->flush();
}
public function onPaymentReceived(MembershipPaymentReceivedEvent $event): void
{
if (!($event->getUser() instanceof User)) {
throw new RuntimeException('The event must have a user.');
}
$action = MembershipPaymentEventInterface::ACTION_MEMBERSHIP_NEW;
if (count($event->getCreditsMembership()->getVxsOrders()) > 1) {
$action = MembershipPaymentEventInterface::ACTION_MEMBERSHIP_REBILL;
}
$this->writeMembershipCreditsHistory($event->getUser(), $event->getVxsOrder(), $action);
}
public function onPaymentCancelled(MembershipPaymentCancelledEvent $event): void
{
if (!($event->getUser() instanceof User)) {
throw new RuntimeException('The event must have a user.');
}
$action = MembershipPaymentEventInterface::ACTION_MEMBERSHIP_REFUND;
if ($event->isChargeback()) {
$action = MembershipPaymentEventInterface::ACTION_MEMBERSHIP_CHARGEBACK;
}
$this->writeMembershipCreditsHistory($event->getUser(), $event->getVxsOrder(), $action);
}
private function writeMembershipCreditsHistory(User $user, PaymentVxsOrder $vxsOrder, string $action): void
{
/** @var Connection $connection */
$connection = $this->doctrine->getConnection();
if (!$connection->isTransactionActive()) {
throw new RuntimeException('You must start DB transaction in order to use this event');
}
$creditHistory = new CreditHistory();
$creditHistory->setUser($user);
$creditHistory->setRelatedEntity($vxsOrder, $action);
$creditHistory->setCredits(0);
$this->doctrine->getManager()->persist($creditHistory);
$this->doctrine->getManager()->flush();
}
/**
* @return array<string, array<int|string, array<int|string, int|string>|int|string>|string>
*/
public static function getSubscribedEvents(): array
{
return [
CreditsEvent::class => 'onCredits',
MembershipPaymentReceivedEvent::class => 'onPaymentReceived',
MembershipPaymentCancelledEvent::class => 'onPaymentCancelled',
];
}
}