src/Service/App/Merchant/Promotion/Listener/SubscriptionListener.php line 208

Open in your IDE?
  1. <?php
  2. namespace App\Service\App\Merchant\Promotion\Listener;
  3. use App\Core\Exception\UnexpectedException;
  4. use App\DTO\Common\Subscription\Response\PlanResponseDTO;
  5. use App\DTO\MerchantApi\Common\Request\LineDiscountRequestDTO;
  6. use App\Entity\Merchant\PromotionEntity;
  7. use App\Repository\Merchant\Contract\IOrderRepository;
  8. use App\Repository\Merchant\Contract\IPlanPriceRepository;
  9. use App\Repository\Merchant\Contract\IPlanRepository;
  10. use App\Repository\Merchant\Contract\IPlanSubscriptionRepository;
  11. use App\Repository\Merchant\Contract\IPromotionRepository;
  12. use App\Repository\Merchant\Contract\IRecurringProfileRepository;
  13. use App\Service\App\Merchant\CustomFeature\Implementation\PromotionCustomFeature;
  14. use App\Service\App\Merchant\Subscription\Event\BuildSubscriptionPaymentLineEvent;
  15. use App\Service\App\Merchant\Subscription\Event\CalculateSubscriptionTotalsEvent;
  16. use App\Service\App\Merchant\Subscription\Event\CreateSubscriptionWithPaymentEvent;
  17. use App\Service\App\Merchant\Subscription\Event\SubscriptionPlanListEvent;
  18. use App\Types\DiscountType;
  19. use App\Types\PromotionType;
  20. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  21. class SubscriptionListener implements EventSubscriberInterface
  22. {
  23.     /**
  24.      * Plan subscription repository
  25.      *
  26.      * @var IPlanSubscriptionRepository
  27.      */
  28.     private IPlanSubscriptionRepository $planSubscriptionRepository;
  29.     /**
  30.      * Promotion custom feature
  31.      *
  32.      * @var PromotionCustomFeature
  33.      */
  34.     private PromotionCustomFeature $promotionCustomFeature;
  35.     /**
  36.      * Order repository
  37.      *
  38.      * @var IOrderRepository
  39.      */
  40.     private IOrderRepository $orderRepository;
  41.     /**
  42.      * Recurring profile repository
  43.      *
  44.      * @var IRecurringProfileRepository
  45.      */
  46.     private IRecurringProfileRepository $recurringProfileRepository;
  47.     /**
  48.      * Promotion repository
  49.      *
  50.      * @var IPromotionRepository
  51.      */
  52.     private IPromotionRepository $promotionRepository;
  53.     /**
  54.      * Plan price repository
  55.      *
  56.      * @var IPlanPriceRepository
  57.      */
  58.     private IPlanPriceRepository $planPriceRepository;
  59.     /**
  60.      * Plan repository
  61.      *
  62.      * @var IPlanRepository
  63.      */
  64.     private IPlanRepository $planRepository;
  65.     /**
  66.      * Promotion entity
  67.      *
  68.      * @var PromotionEntity|null
  69.      */
  70.     private ?PromotionEntity $promotion null;
  71.     /**
  72.      * Constructor
  73.      *
  74.      * @param IPlanSubscriptionRepository $planSubscriptionRepository
  75.      * @param PromotionCustomFeature $promotionCustomFeature
  76.      * @param IOrderRepository $orderRepository
  77.      * @param IRecurringProfileRepository $recurringProfileRepository
  78.      * @param IPromotionRepository $promotionRepository
  79.      * @param IPlanPriceRepository $planPriceRepository
  80.      * @param IPlanRepository $planRepository
  81.      */
  82.     public function __construct(
  83.         IPlanSubscriptionRepository $planSubscriptionRepository,
  84.         PromotionCustomFeature $promotionCustomFeature,
  85.         IOrderRepository $orderRepository,
  86.         IRecurringProfileRepository $recurringProfileRepository,
  87.         IPromotionRepository $promotionRepository,
  88.         IPlanPriceRepository $planPriceRepository,
  89.         IPlanRepository $planRepository
  90.     )
  91.     {
  92.         $this->planSubscriptionRepository $planSubscriptionRepository;
  93.         $this->promotionCustomFeature $promotionCustomFeature;
  94.         $this->orderRepository $orderRepository;
  95.         $this->recurringProfileRepository $recurringProfileRepository;
  96.         $this->promotionRepository $promotionRepository;
  97.         $this->planPriceRepository $planPriceRepository;
  98.         $this->planRepository $planRepository;
  99.     }
  100.     /**
  101.      * Returns an array of event names this subscriber wants to listen to.
  102.      *
  103.      * The array keys are event names and the value can be:
  104.      *
  105.      *  * The method name to call (priority defaults to 0)
  106.      *  * An array composed of the method name to call and the priority
  107.      *  * An array of arrays composed of the method names to call and respective
  108.      *    priorities, or 0 if unset
  109.      *
  110.      * For instance:
  111.      *
  112.      *  * ['eventName' => 'methodName']
  113.      *  * ['eventName' => ['methodName', $priority]]
  114.      *  * ['eventName' => [['methodName1', $priority], ['methodName2']]]
  115.      *
  116.      * The code must not depend on runtime state as it will only be called at compile time.
  117.      * All logic depending on runtime state must be put into the individual methods handling the events.
  118.      *
  119.      * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  120.      */
  121.     public static function getSubscribedEvents(): array
  122.     {
  123.         return [
  124.             BuildSubscriptionPaymentLineEvent::NAME => 'onBuildSubscriptionPaymentLine',
  125.             CreateSubscriptionWithPaymentEvent::NAME => 'onCreateSubscriptionPayment',
  126.             CalculateSubscriptionTotalsEvent::NAME => 'onCalculateSubscriptionTotals',
  127.             SubscriptionPlanListEvent::NAME => 'onSubscriptionPlanList'
  128.         ];
  129.     }
  130.     /**
  131.      * On build subscription payment line
  132.      *
  133.      * @param BuildSubscriptionPaymentLineEvent $event
  134.      * @throws UnexpectedException
  135.      */
  136.     public function onBuildSubscriptionPaymentLine(BuildSubscriptionPaymentLineEvent $event): void
  137.     {
  138.         if (!$this->promotionCustomFeature->isEnabled()) {
  139.             return;
  140.         }
  141.         /** @var PromotionEntity|null $promotion */
  142.         $promotion null;
  143.         // If update subscription we use the same promo code and do not allow to change it
  144.         // To not allow users cancel in the middle of the subscription and then use this promo code for other payment
  145.         if ($event->getRequest()->getSubscriptionId()) {
  146.             // Try to get discount from parent subscription order
  147.             $subscription $this->planSubscriptionRepository->getSubscription(
  148.                 $event->getRequest()->getSubscriptionId()
  149.             );
  150.             $promotion $subscription->getRelatedPayment()?->getPromotion();
  151.         }
  152.         // #30258
  153.         // Find promotion by code
  154.         //if (!$event->getRequest()->getSubscriptionId() && !is_null($event->getRequest()->getPromoCode())) {
  155.         if (!is_null($event->getRequest()->getPromoCode())) {
  156.             // Try to find discount
  157.             $promotion $this->promotionRepository->getPromotionByCode(
  158.                 $event->getRequest()->getPromoCode()
  159.             );
  160.         }
  161.         if ($promotion) {
  162.             if ($this->promotion && $this->promotion->getId() != $promotion->getId()) {
  163.                 throw new UnexpectedException('Multiple promotions are not allowed in one subscription.');
  164.             }
  165.         }
  166.         // Get plan price
  167.         $planPrice $this->planPriceRepository->getPrice(
  168.             $event->getSubscriptionItem()->getPriceId()
  169.         );
  170.         // Apply discount
  171.         if ($promotion?->canBeAppliedToPlan($planPrice?->getPlan(), $event->isRecurring())) {
  172.             $discountValue min($promotion->getDiscount(), 1);
  173.             $event->getPaymentLine()->addDiscount(
  174.                 LineDiscountRequestDTO::build(
  175.                     $discountValueDiscountType::DISCOUNT_TYPE_PERCENTAGE
  176.                 )
  177.             );
  178.             $this->promotion $promotion;
  179.         }
  180.     }
  181.     /**
  182.      * On create subscription payment
  183.      *
  184.      * @param CreateSubscriptionWithPaymentEvent $event
  185.      * @return void
  186.      */
  187.     public function onCreateSubscriptionPayment(CreateSubscriptionWithPaymentEvent $event): void
  188.     {
  189.         if (!$this->promotionCustomFeature->isEnabled()) {
  190.             return;
  191.         }
  192.         // if promotion was used when lines were built
  193.         if ($this->promotion) {
  194.             // get order and add promotion
  195.             if ($event->getOrderId()) {
  196.                 $order $this->orderRepository->getOrder($event->getOrderId());
  197.                 if ($order) {
  198.                     $this->promotion->addOrder($order);
  199.                 }
  200.             }
  201.             // if applicable for recurring profile, add promotion to recurring profile
  202.             if ($this->promotion->getType() != PromotionType::TYPE_ONE_TIME_PAYMENT && $event->getRecurringProfileId()) {
  203.                 $profile $this->recurringProfileRepository->getRecurringProfile($event->getRecurringProfileId());
  204.                 if ($profile) {
  205.                     $this->promotion->addRecurringProfile($profile);
  206.                 }
  207.             }
  208.             // save promotion links
  209.             $this->promotionRepository->save($this->promotion);
  210.         }
  211.         // reset promotion
  212.         $this->promotion null;
  213.     }
  214.     /**
  215.      * On calculate subscription totals
  216.      *
  217.      * @param CalculateSubscriptionTotalsEvent $event
  218.      * @return void
  219.      */
  220.     public function onCalculateSubscriptionTotals(CalculateSubscriptionTotalsEvent $event): void
  221.     {
  222.         if (!$this->promotionCustomFeature->isEnabled()) {
  223.             return;
  224.         }
  225.         // if it is update subscription, we do not allow to use promo code.
  226.         if ($event->getRequest()->getSubscriptionId()) {
  227.             // #30258
  228.             // return;
  229.         }
  230.         if (count($event->getResponse()->getLines())) {
  231.             $allFree true;
  232.             foreach ($event->getResponse()->getLines() as $line) {
  233.                 $allFree &= $line->getPrice() == 0;
  234.             }
  235.             if ($allFree) {
  236.                 return;
  237.             }
  238.         }
  239.         // check if promotion is available
  240.         $event->getResponse()->setPromotionAvailable(
  241.             $this->promotionRepository->hasActivePromotions(
  242.                 $event->getRequest()->getCustomerReferenceId()
  243.             )
  244.         );
  245.     }
  246.     /**
  247.      * On subscription plan list
  248.      *
  249.      * @param SubscriptionPlanListEvent $event
  250.      * @return void
  251.      */
  252.     public function onSubscriptionPlanList(SubscriptionPlanListEvent $event): void
  253.     {
  254.         if (!$this->promotionCustomFeature->isEnabled()) {
  255.             return;
  256.         }
  257.         // if promo code is set, apply discount to plans
  258.         if ($event->getRequest()->getPromoCode()) {
  259.             // get promotion by code
  260.             $promotion $this->promotionRepository->getPromotionByCode(
  261.                 $event->getRequest()->getPromoCode()
  262.             );
  263.             // if not promotion or not active, return
  264.             if (!$promotion || !$promotion->isActive()) {
  265.                 return;
  266.             }
  267.             if ($event->getRequest()->getCustomerReferenceId()) {
  268.                 // check if customer can use promotion
  269.                 if (!$this->promotionRepository->canCustomerUsePromotion(
  270.                     $event->getRequest()->getCustomerReferenceId(),
  271.                     $event->getRequest()->getPromoCode()
  272.                 )) {
  273.                     return;
  274.                 }
  275.             }
  276.             // apply discount to plans
  277.             foreach ($event->getPlans() as $plan) {
  278.                 // get plan entity
  279.                 $planEntity $this->planRepository->getPlan($plan->getId());
  280.                 // if applicable, apply discount
  281.                 if ($promotion->canBeAppliedToPlan($planEntity)) {
  282.                     $plan->setDiscount($promotion->getDiscount());
  283.                 }
  284.                 // only plans have additions
  285.                 if (!($plan instanceof PlanResponseDTO)) {
  286.                     continue;
  287.                 }
  288.                 // apply discount to plan additions
  289.                 foreach ($plan->getAdditions() as $addition) {
  290.                     // get addition entity
  291.                     $additionEntity $this->planRepository->getPlan($addition->getId());
  292.                     // if applicable, apply discount
  293.                     if ($promotion->canBeAppliedToPlan($additionEntity)) {
  294.                         $addition->setDiscount($promotion->getDiscount());
  295.                     }
  296.                 }
  297.             }
  298.         }
  299.     }
  300. }