src/License/Service/SyliusOrderPaidSubscriber.php line 36

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\License\Service;
  4. use App\License\Enum\LicenseFeature;
  5. use App\License\Enum\LicenseStatus;
  6. use App\License\Repository\LicenseRepository;
  7. use Doctrine\ORM\EntityManagerInterface;
  8. use Psr\Log\LoggerInterface;
  9. use Sylius\Component\Core\Model\OrderInterface;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. final class SyliusOrderPaidSubscriber implements EventSubscriberInterface
  12. {
  13. public function __construct(
  14. private readonly LicenseRepository $licenseRepository,
  15. private readonly LicenseFactory $licenseFactory,
  16. private readonly LicenseAuditLogger $auditLogger,
  17. private readonly EntityManagerInterface $entityManager,
  18. private readonly LoggerInterface $logger,
  19. ) {
  20. }
  21. public static function getSubscribedEvents(): array
  22. {
  23. return [
  24. 'sylius.payment.post_complete' => 'onPaymentCompleted',
  25. ];
  26. }
  27. /**
  28. * @param mixed $event
  29. */
  30. public function onPaymentCompleted($event): void
  31. {
  32. if (!method_exists($event, 'getSubject')) {
  33. return;
  34. }
  35. $payment = $event->getSubject();
  36. if (!method_exists($payment, 'getOrder') || null === $payment->getOrder()) {
  37. return;
  38. }
  39. $order = $payment->getOrder();
  40. if (!$order instanceof OrderInterface) {
  41. return;
  42. }
  43. if (null === $order->getCustomer() || null === $order->getCustomer()->getEmail()) {
  44. return;
  45. }
  46. $userEmail = (string) $order->getCustomer()->getEmail();
  47. $userName = (string) ($order->getCustomer()->getFullName() ?: $userEmail);
  48. $features = $this->resolveFeaturesFromOrder($order);
  49. $months = $this->resolveSubscriptionMonths($order);
  50. $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
  51. $start = $order->getCheckoutCompletedAt() ? \DateTimeImmutable::createFromInterface($order->getCheckoutCompletedAt()) : $now;
  52. $end = $start->modify(sprintf('+%d months', max(1, $months)));
  53. $license = $this->licenseRepository->findOneBy(['userEmail' => $userEmail]);
  54. $oldValue = null;
  55. $action = 'created_by_order';
  56. if (null === $license) {
  57. $license = $this->licenseFactory->createLicense(
  58. $userName,
  59. $userEmail,
  60. $features,
  61. $start,
  62. $end,
  63. 1,
  64. false,
  65. 'sylius_order',
  66. null,
  67. LicenseStatus::ACTIVE
  68. );
  69. $this->entityManager->persist($license);
  70. } else {
  71. $oldValue = [
  72. 'subscriptionEnd' => $license->getSubscriptionEnd()->format(\DATE_ATOM),
  73. 'activeFeatures' => $license->getActiveFeatures(),
  74. 'status' => $license->getStatus(),
  75. ];
  76. $license->setUserName($userName);
  77. $license->setUserEmail($userEmail);
  78. $license->setActiveFeatures($features);
  79. $license->setSubscriptionStart($start);
  80. $license->setSubscriptionEnd($end);
  81. $license->setStatus(LicenseStatus::ACTIVE);
  82. $license->setOrigin('sylius_order');
  83. $license->setIsFreeGrant(false);
  84. $license->setAmountPaid($this->licenseFactory->calculateAnnualPrice($features));
  85. $license->touch();
  86. $action = 'updated_by_order';
  87. }
  88. $this->entityManager->persist($license);
  89. $this->auditLogger->log($license, null, $action, $oldValue, [
  90. 'subscriptionStart' => $license->getSubscriptionStart()->format(\DATE_ATOM),
  91. 'subscriptionEnd' => $license->getSubscriptionEnd()->format(\DATE_ATOM),
  92. 'activeFeatures' => $license->getActiveFeatures(),
  93. 'amountPaid' => $license->getAmountPaid(),
  94. ]);
  95. $this->entityManager->flush();
  96. $this->logger->info('License updated from paid Sylius order.', [
  97. 'licenseUuid' => $license->getUuid(),
  98. 'orderNumber' => method_exists($order, 'getNumber') ? $order->getNumber() : null,
  99. 'email' => $userEmail,
  100. ]);
  101. }
  102. /**
  103. * @return array<int, string>
  104. */
  105. private function resolveFeaturesFromOrder(OrderInterface $order): array
  106. {
  107. $features = [];
  108. foreach ($order->getItems() as $item) {
  109. $codes = [];
  110. if (method_exists($item, 'getVariant') && null !== $item->getVariant() && method_exists($item->getVariant(), 'getCode')) {
  111. $codes[] = strtoupper((string) $item->getVariant()->getCode());
  112. }
  113. if (method_exists($item, 'getProduct') && null !== $item->getProduct()) {
  114. $product = $item->getProduct();
  115. if (method_exists($product, 'getCode')) {
  116. $codes[] = strtoupper((string) $product->getCode());
  117. }
  118. if (method_exists($product, 'hasAttributeByCodeAndLocale')) {
  119. foreach (['audio', 'video', 'light', 'control'] as $featureCode) {
  120. if ($product->hasAttributeByCodeAndLocale($featureCode)) {
  121. $features[] = strtoupper($featureCode);
  122. }
  123. }
  124. }
  125. }
  126. foreach ($codes as $code) {
  127. if ('STUDIO' === $code) {
  128. $features = array_merge($features, LicenseFeature::allDefaults());
  129. continue;
  130. }
  131. $parts = explode('_', $code);
  132. foreach ($parts as $part) {
  133. if (in_array($part, LicenseFeature::allDefaults(), true)) {
  134. $features[] = $part;
  135. }
  136. }
  137. }
  138. }
  139. return array_values(array_unique($features));
  140. }
  141. private function resolveSubscriptionMonths(OrderInterface $order): int
  142. {
  143. $total = (int) $order->getTotal();
  144. if ($total <= 0) {
  145. return 12;
  146. }
  147. return max(1, (int) ceil($total / max(1, count($this->resolveFeaturesFromOrder($order)) * LicenseFeature::PRICE_PER_FEATURE)));
  148. }
  149. }