<?php
namespace Boldr\Shop\ShopBundle\EventSubscriber;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Boldr\Shop\ShopBundle\Event\{ PaymentStatusUpdatedEvent, InvoiceProFormaUpdatedEvent, InvoiceStatusUpdatedEvent, PaymentCreatedEvent, InvoiceCreatedEvent };
use Boldr\Shop\ShopBundle\Entity\{ Payment, Invoice };
use Boldr\Shop\ShopBundle\OrderFlow\{ OrderFlowManagerInterface, OrderStateChangeProcessor };
use Boldr\Shop\ShopBundle\Invoice\InvoiceNumberGeneratorInterface;
use Boldr\Shop\ShopBundle\EmailFactory;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Mailer\MailerInterface;
use DateTimeImmutable;
class InvoiceEventSubscriber implements EventSubscriberInterface
{
private OrderFlowManagerInterface $orderFlowManager;
private InvoiceNumberGeneratorInterface $invoiceNumberGenerator;
private EventDispatcherInterface $eventDispatcher;
private EntityManagerInterface $em;
private OrderStateChangeProcessor $orderStateChangeProcessor;
private EmailFactory $emailFactory;
private MailerInterface $mailer;
public function __construct(
OrderFlowManagerInterface $orderFlowManager,
InvoiceNumberGeneratorInterface $invoiceNumberGenerator,
EventDispatcherInterface $eventDispatcher,
OrderStateChangeProcessor $orderStateChangeProcessor,
EntityManagerInterface $em,
EmailFactory $emailFactory,
MailerInterface $mailer
)
{
$this->orderFlowManager = $orderFlowManager;
$this->invoiceNumberGenerator = $invoiceNumberGenerator;
$this->eventDispatcher = $eventDispatcher;
$this->orderStateChangeProcessor = $orderStateChangeProcessor;
$this->em = $em;
$this->emailFactory = $emailFactory;
$this->mailer = $mailer;
}
public static function getSubscribedEvents()
{
return [
PaymentCreatedEvent::class => [
['onPaymentCreated', 10]
],
PaymentStatusUpdatedEvent::class => [
['onPaymentStatusUpdated', 10]
],
InvoiceCreatedEvent::class => [
['onInvoiceCreated', 10]
],
InvoiceProFormaUpdatedEvent::class => [
['onInvoiceProFormaUpdated', 10]
],
InvoiceStatusUpdatedEvent::class => [
['onInvoiceStatusUpdated', 10]
]
];
}
private function updateInvoiceStatus(Invoice $invoice, $newStatus)
{
if ($invoice->getStatus() !== $newStatus)
{
$event = new InvoiceStatusUpdatedEvent($invoice, $invoice->getStatus(), $newStatus);
$invoice->setStatus($newStatus);
$this->em->flush();
$this->eventDispatcher->dispatch($event);
}
}
private function handleInvoiceChange(Invoice $invoice)
{
// Generate invoice number if invoice is finished and agreed
if ($invoice->getNumber() === null)
{
if ($invoice->isCreditInvoice() || $this->orderFlowManager->canGenerateInvoiceNumber($invoice))
{
$date = new DateTimeImmutable;
$number = $invoice->isProForma() ? null : $this->invoiceNumberGenerator->generateInvoiceNumber($invoice->getCustomer(), $date);
$invoice->finalize($number, $date);
}
}
// Mark as paid if no amount left outstanding
$newStatus = abs($invoice->getAmountOutstanding()) < 0.000001 ? Invoice::STATUS_PAID : ($invoice->isDraft() ? Invoice::STATUS_DRAFT : Invoice::STATUS_UNPAID);
$this->updateInvoiceStatus($invoice, $newStatus);
$this->em->flush();
}
public function onInvoiceProFormaUpdated(InvoiceProFormaUpdatedEvent $event)
{
$invoice = $event->getInvoice();
$this->handleInvoiceChange($invoice);
}
public function onPaymentCreated(PaymentCreatedEvent $event)
{
$invoice = $event->getPayment()->getInvoice();
$this->handleInvoiceChange($invoice);
}
public function onPaymentStatusUpdated(PaymentStatusUpdatedEvent $event)
{
$payment = $event->getPayment();
if ($event->getNewStatus() === Payment::STATUS_FAILED)
{
$invoice = $payment->getInvoice();
if ($invoice->getStatus() !== Invoice::STATUS_PAID)
{
$retryUrl = null;
try
{
$email = $this->emailFactory->createPaymentFailedEmail($event->getPayment(), $retryUrl);
$this->mailer->send($email);
}
catch (\Exception $ex)
{
// fail silently
}
}
}
else
{
$invoice = $event->getPayment()->getInvoice();
$this->handleInvoiceChange($invoice);
}
}
public function onInvoiceCreated(InvoiceCreatedEvent $event)
{
$invoice = $event->getInvoice();
$this->handleInvoiceChange($invoice);
}
public function onInvoiceStatusUpdated(InvoiceStatusUpdatedEvent $event)
{
$invoice = $event->getInvoice();
$orders = [];
foreach ($invoice->getItems() as $invoiceItem)
{
$orders[] = $invoiceItem->getOrder();
}
$doneOrders = [];
foreach ($orders as $order)
{
if (!in_array($order, $doneOrders))
{
$doneOrders[] = $order;
$nextStates = $this->orderFlowManager->getNextStates($order);
foreach ($nextStates as $nextState)
{
$changeStateSteps = $this->orderFlowManager->getChangeStateSteps($order, $nextState);
if ($changeStateSteps->getAutomatic())
{
$doComplete = true;
foreach ($changeStateSteps->getSteps() as $step)
{
if (!$step->isCompleted($order))
{
$doComplete = false;
break;
}
}
if ($doComplete)
{
$this->orderStateChangeProcessor->changeState($order, $nextState);
break;
}
}
}
}
}
$this->em->flush();
}
}