src/EventSubscribers/Main/PaypalSubscriber.php line 299

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscribers\Main;
  3. use App\Entity\Main\Cart;
  4. use App\Entity\Main\RebillManager;
  5. use App\Entity\Main\Transaction;
  6. use App\Events\Main\Cart\CartChargedBackEvent;
  7. use App\Events\Main\Cart\CartRefundedEvent;
  8. use App\Events\Main\Coaching\CoachingCancelledEvent;
  9. use App\Events\Main\Order\OrderPaidByClientEvent;
  10. use App\Events\Main\Paypal\PaypalCaptureCallbackReceivedEvent;
  11. use App\Events\Main\Paypal\PaypalRefundCallbackReceivedEvent;
  12. use App\Events\Main\Paypal\PaypalReverseCallbackReceivedEvent;
  13. use App\Events\Main\User\UserMadeChargebackEvent;
  14. use App\Events\Main\User\UserMadeRefundEvent;
  15. use App\Exceptions\Main\Paypal\PaypalCartMismatchException;
  16. use App\Exceptions\Main\Paypal\PaypalCartNotFoundException;
  17. use App\Exceptions\Main\Paypal\PaypalOrderNotCompletelyPaidException;
  18. use App\Exceptions\Main\Paypal\PaypalOrderNotFoundException;
  19. use App\Exceptions\Main\Paypal\PaypalTransactionNotFoundException;
  20. use App\Repository\Main\TransactionRepository;
  21. use App\Services\BillingManager;
  22. use App\Services\CartManager;
  23. use App\Services\EmailManager;
  24. use App\Services\PayPalManager;
  25. use App\Services\ReferralProgramManager;
  26. use App\Tools\Paypal\PaypalOrder;
  27. use App\Tools\ShortId;
  28. use Doctrine\ORM\EntityManagerInterface;
  29. use Doctrine\Persistence\ObjectRepository;
  30. use Psr\Log\LoggerInterface;
  31. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  32. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  33. /**
  34.  * Class PaypalSubscriber
  35.  * @package App\EventSubscribers\Main
  36.  */
  37. class PaypalSubscriber implements EventSubscriberInterface
  38. {
  39.     const
  40.         PAYPAL_REFUND_CALLBACK 'paypal.callback.refund',
  41.         PAYPAL_REVERSE_CALLBACK 'paypal.callback.reverse',
  42.         PAYPAL_CAPTURE_CALLBACK 'paypal.callback.capture'
  43.     ;
  44.     /**
  45.      * @var EntityManagerInterface
  46.      */
  47.     private $entityManager;
  48.     /**
  49.      * @var TransactionRepository|ObjectRepository
  50.      */
  51.     private $transactionRepository;
  52.     /**
  53.      * @var LoggerInterface
  54.      */
  55.     private $logger;
  56.     /**
  57.      * @var ShortId
  58.      */
  59.     private $shortId;
  60.     /**
  61.      * @var BillingManager
  62.      */
  63.     private $billingFactory;
  64.     /**
  65.      * @var CartManager
  66.      */
  67.     private $cartFactory;
  68.     /**
  69.      * @var PayPalManager
  70.      */
  71.     private $payPalManager;
  72.     /**
  73.      * @var EventDispatcherInterface
  74.      */
  75.     private $eventDispatcher;
  76.     /**
  77.      * @var EmailManager
  78.      */
  79.     private $emailFactory;
  80.     /**
  81.      * @var ReferralProgramManager
  82.      */
  83.     private $referralProgramManager;
  84.     private $transactionDescriptor;
  85.     /**
  86.      * PaypalSubscriber constructor.
  87.      * @param EntityManagerInterface $entityManager
  88.      * @param LoggerInterface $logger
  89.      * @param ShortId $shortId
  90.      * @param CartManager $cartFactory
  91.      * @param BillingManager $billingFactory
  92.      * @param PayPalManager $payPalManager
  93.      * @param EventDispatcherInterface $eventDispatcher
  94.      * @param EmailManager $emailFactory
  95.      * @param $transactionDescriptor
  96.      */
  97.     public function __construct(
  98.         EntityManagerInterface $entityManager,
  99.         LoggerInterface $logger,
  100.         ShortId $shortId,
  101.         CartManager $cartFactory,
  102.         BillingManager $billingFactory,
  103.         PayPalManager $payPalManager,
  104.         EventDispatcherInterface $eventDispatcher,
  105.         EmailManager $emailFactory,
  106.         $transactionDescriptor
  107.     ) {
  108.         $this->entityManager $entityManager;
  109.         $this->transactionRepository $entityManager->getRepository(Transaction::class);
  110.         $this->logger $logger;
  111.         $this->shortId $shortId;
  112.         $this->cartFactory $cartFactory;
  113.         $this->billingFactory $billingFactory;
  114.         $this->payPalManager $payPalManager;
  115.         $this->eventDispatcher $eventDispatcher;
  116.         $this->emailFactory $emailFactory;
  117.         $this->transactionDescriptor $transactionDescriptor;
  118.     }
  119.     /**
  120.      * @return array
  121.      */
  122.     public static function getSubscribedEvents()
  123.     {
  124.         return array(
  125.             self::PAYPAL_CAPTURE_CALLBACK => "paypalCallbackCapture",
  126.             self::PAYPAL_REFUND_CALLBACK => "paypalCallbackRefund",
  127.             self::PAYPAL_REVERSE_CALLBACK => "paypalCallbackReverse"
  128.         );
  129.     }
  130.     /**
  131.      * @param PaypalCaptureCallbackReceivedEvent $event
  132.      * @throws \Exception
  133.      */
  134.     public function paypalCallbackCapture(PaypalCaptureCallbackReceivedEvent $event)
  135.     {
  136.         // check instantly if transaction exists without to query paypal api.
  137.         $transaction $this->transactionRepository->getTransactionForPaypalId($event->getRawEventData()['resource']['id']);
  138.         // if transaction is here and with OK status, then we can ignore this callBack and give paypal the response.
  139.         if ($transaction instanceof Transaction && $transaction->isSuccessful()) {
  140.             return;
  141.         }
  142.         // Ok the transaction doesn't exists, validate it with their api and load all data.
  143.         $paypalCapture $this->payPalManager->getPaypalCapture($event->getRawEventData()['resource']['id']);
  144.         $paypalOrder $paypalCapture->getOrder();
  145.         // If we don't have an order for this event, we can't handle it
  146.         if (!$paypalOrder instanceof PaypalOrder || !$paypalOrder->isInitialised() || $paypalOrder->getApiError()) {
  147.             // failed to get the order, we can't do anything with this notification.
  148.             $this->logger->critical("Receiving paypal callback that can't be exploited (no corresponding order. Data : " print_r($event->getRawEventData(), true));
  149.             throw new PaypalOrderNotFoundException("Couldn't find paypal order for capture. Data : {$paypalCapture->getId()}");
  150.         }
  151.         // avoid concurrency problems with customer checkout if transaction is not found
  152.         sleep(2);
  153.         // After waiting we recheck
  154.         $transaction $this->transactionRepository->getTransactionForPaypalId($paypalCapture->getId());
  155.         // if transaction is here and with OK status, then we can ignore this callBack
  156.         if ($transaction instanceof Transaction && $transaction->isSuccessful()) {
  157.             return;
  158.         }
  159.         // get the cart from the order
  160.         $cartId $this->shortId->decode($paypalOrder->getCustomOrderId());
  161.         $cart $this->entityManager->getRepository(Cart::class)->findOneByCartId($cartId);
  162.         if (!$cart instanceof Cart) {
  163.             // We have a transaction that we don't know to which cart it applies
  164.             $this->logger->critical("Receiving paypal callback that can't be exploited (no corresponding cart. Data : " print_r($event->getRawEventData(), true));
  165.             throw new PaypalCartNotFoundException("Couldn't find the cart for a paypal order. Data : {$paypalCapture->getId()}");
  166.         }
  167.         // Transaction doesn't exist create it.
  168.         if (!$transaction instanceof Transaction) {
  169.             $this->billingFactory->setCartFactory($this->cartFactory);
  170.             $this->cartFactory->transitState($cart'paymentReady');
  171.             $this->billingFactory->processPaypalPurchase($cartnull$paypalOrder);
  172.             return;
  173.         }
  174.         // transaction exists but is was marked as failed, we need to update that.
  175.         if ($paypalOrder->getEarnedAmount() != ($cart->getCartTotalAmount() / 100) ||
  176.             $paypalOrder->getOrderCurrency() != $cart->getCartCurrency()
  177.         ) {
  178.             $this->logger->critical("Error in Paypal transaction return - Amount paid is not correct or currency is wrong. Paypal Order : {$paypalOrder->getId()}, cartId : {$cart->getCartId()}, return result : " print_r($paypalOrder->getRawData() . " paypal currency : {$paypalOrder->getOrderCurrency()}, cart currency : {$cart->getCartCurrency()}"true));
  179.             throw new PaypalOrderNotCompletelyPaidException("Receiving callback info for a capture, but amount is not sufficient to pay the cart or currency is wrong. Data : {$paypalCapture->getId()}");
  180.         }
  181.         // Update the payment status and put the cart as paid.
  182.         $this->billingFactory->setCartFactory($this->cartFactory);
  183.         $this->cartFactory->transitState($cart'paymentReady');
  184.         $this->billingFactory->updateTransactionStatusToPaid($transaction" -> Paypal Callback confirming capture -> {$paypalCapture->getId()}");
  185.         // dispatch an order paid event
  186.         $event = new OrderPaidByClientEvent($cart);
  187.         $this->eventDispatcher->dispatch($eventOrderSubscriber::ORDER_PAID_BY_CLIENT);
  188.     }
  189.     /**
  190.      * @param PaypalRefundCallbackReceivedEvent $event
  191.      * @return bool
  192.      * @throws \Exception
  193.      */
  194.     public function paypalCallbackRefund(PaypalRefundCallbackReceivedEvent $event)
  195.     {
  196.         // check instantly if transaction exists without to query paypal api (this means double callback).
  197.         $refundTransaction $this->transactionRepository->getTransactionForPaypalId($event->getRawEventData()['resource']['id']);
  198.         // if transaction is here and with OK status, then we can ignore this callBack and give paypal the response.
  199.         if ($refundTransaction instanceof Transaction) {
  200.             return;
  201.         }
  202.         // Ok get the refund confirmation from payapal api and the paypal order info.
  203.         $paypalRefund $this->payPalManager->getPaypalRefund($event->getRawEventData()['resource']['id']);
  204.         $paypalOrder $paypalRefund->getOrder();
  205.         $cartId $this->shortId->decode($paypalOrder->getCustomOrderId());
  206.         // Now, get the original transaction
  207.         $originalTransaction $this->transactionRepository->getTransactionForPaypalId($paypalRefund->getCapture()->getId());
  208.         if (!$originalTransaction instanceof Transaction) {
  209.             $this->logger->critical("Unable to find original transaction for a refund : original paypal transaction {$paypalRefund->getCapture()->getId()}, refund id {$paypalRefund->getId()}, data : " print_r($event->getRawEventData(), true));
  210.             throw new PaypalTransactionNotFoundException("Unable to find original transaction for a refund : original paypal transaction {$paypalRefund->getCapture()->getId()}, refund id {$paypalRefund->getId()}");
  211.         }
  212.         if ($originalTransaction->getCart()->getCartId() != $cartId) {
  213.             $this->logger->critical("Receiving a refund for a non corresponding cart : original paypal transaction {$paypalRefund->getCapture()->getId()}, refund id {$paypalRefund->getId()}, cart : {$cartId}, data : " print_r($event->getRawEventData(), true));
  214.             throw new PaypalCartMismatchException("Receiving a refund for a non corresponding cart : original paypal transaction {$paypalRefund->getCapture()->getId()}, refund id {$paypalRefund->getId()}");
  215.         }
  216.         // Ok, create refund transaction
  217.         $t = new Transaction(
  218.             $originalTransaction->getCart(),
  219.             $originalTransaction->getBillingAccount(),
  220.             $paypalRefund->getTotalValue() * 100,
  221.             "Paypal Refund (callback) : {$paypalRefund->getId()}",
  222.             $originalTransaction->getUserPaymentToken(),
  223.             $paypalRefund->getCurrency(),
  224.             $originalTransaction->getTransactionIp(),
  225.             $originalTransaction->getTransactionUseragent()
  226.         );
  227.         $rebillManager $originalTransaction->getRebillManager();
  228.         if (!is_null($rebillManager)) {
  229.             $t->setRebillManager($rebillManager);
  230.         }
  231.         $t->setTransactionStamp($paypalRefund->getTransactionStamp());
  232.         $t->setTransactionType(Transaction::REFUND);
  233.         $t->setTransactionStatus(Transaction::OK);
  234.         $t->setTransactionReason('Transaction refunded by paypal callback');
  235.         $t->setTransactionMerkavId(0);
  236.         $t->setTransactionAquirerId($paypalRefund->getId());
  237.         $t->setTransactionAquirerReason("paypal order : {$paypalOrder->getId()}, paypal capture : {$paypalRefund->getCapture()->getId()}");
  238.         $t->setTransactionBankDescriptor($this->transactionDescriptor);
  239.         $t->setTransactionIp($originalTransaction->getTransactionIp());
  240.         $t->setTransactionUserAgent($originalTransaction->getTransactionUserAgent());
  241.         $t->setParent($originalTransaction);
  242.         // If the total order was refunded, this cart should be cancelled.
  243.         if ($t->getTransactionAmount() == $originalTransaction->getTransactionAmount()) {
  244.             $cart $originalTransaction->getCart();
  245.             $cart->setCartState(Cart::STATE_CANCELLED);
  246.             $this->emailFactory->sendRefundMail($originalTransaction);
  247.         } else {
  248.             $this->emailFactory->sendPartialRefundEmail($originalTransaction);
  249.         }
  250.         $this->entityManager->persist($t);
  251.         $this->entityManager->flush();
  252.         $event = new UserMadeRefundEvent($originalTransaction->getCart()->getUser());
  253.         $this->eventDispatcher->dispatch($eventUserSubscriber::USER_MAKE_REFUND);
  254.         $event = new CartRefundedEvent($originalTransaction->getCart());
  255.         $this->eventDispatcher->dispatch($eventCartSubscriber::CART_REFUND);
  256.         return true;
  257.     }
  258.     /**
  259.      * @param PaypalReverseCallbackReceivedEvent $event
  260.      * @throws \Exception
  261.      */
  262.     public function paypalCallbackReverse(PaypalReverseCallbackReceivedEvent $event)
  263.     {
  264.         // check instantly if transaction exists without to query paypal api (this means double callback).
  265.         $chargebackedTransaction $this->transactionRepository->getTransactionForPaypalId($event->getRawEventData()['resource']['id']);
  266.         // if transaction is here and with OK status, then we can ignore this callBack and give paypal the response.
  267.         if ($chargebackedTransaction instanceof Transaction) {
  268.             return;
  269.         }
  270.         // Ok get the refund confirmation from payapal api and the paypal order info.
  271.         $paypalChargeback $this->payPalManager->getPaypalRefund($event->getRawEventData()['resource']['id']);
  272.         $paypalOrder $paypalChargeback->getOrder();
  273.         $cartId $this->shortId->decode($paypalOrder->getCustomOrderId());
  274.         // Now, get the original transaction
  275.         $originalTransaction $this->transactionRepository->getTransactionForPaypalId($paypalChargeback->getCapture()->getId());
  276.         if (!$originalTransaction instanceof Transaction) {
  277.             $this->logger->critical("Unable to find original transaction for a refund : original paypal transaction {$paypalChargeback->getCapture()->getId()}, refund id {$paypalChargeback->getId()}, data : " print_r($event->getRawEventData(), true));
  278.             throw new PaypalTransactionNotFoundException("Unable to find original transaction for a refund : original paypal transaction {$paypalChargeback->getCapture()->getId()}, refund id {$paypalChargeback->getId()}");
  279.         }
  280.         if ($originalTransaction->getCart()->getCartId() != $cartId) {
  281.             $this->logger->critical("Receiving a refund for a non corresponding cart : original paypal transaction {$paypalChargeback->getCapture()->getId()}, refund id {$paypalChargeback->getId()}, cart : {$cartId}, data : " print_r($event->getRawEventData(), true));
  282.             throw new PaypalCartMismatchException("Receiving a refund for a non corresponding cart : original paypal transaction {$paypalChargeback->getCapture()->getId()}, refund id {$paypalChargeback->getId()}");
  283.         }
  284.         $chargebackTransaction = clone $originalTransaction;
  285.         $chargebackTransaction->setTransactionId(null);
  286.         $chargebackTransaction->setTransactionType(Transaction::CHARGEBACK);
  287.         $chargebackTransaction->setTransactionStamp($paypalChargeback->getTransactionStamp());
  288.         $chargebackTransaction->setTransactionMerkavId($paypalChargeback->getId());
  289.         $chargebackTransaction->setParent($originalTransaction);
  290.         $chargebackTransaction->setTransactionDescription("Paypal Chargeback (callback) : {$paypalChargeback->getId()}");
  291.         $this->entityManager->persist($chargebackTransaction);
  292.         $this->entityManager->flush();
  293.         // Cut the rebill if there is one:
  294.         /** @var Cart $cart */
  295.         $cart $originalTransaction->getCart();
  296.         $rebillManagerList $this->entityManager->getRepository(RebillManager::class)
  297.             ->findBy(['cart' => $cart]);
  298.         foreach ($rebillManagerList as $rebillManager) {
  299.             $this->billingFactory->stopRebillManager($rebillManagerRebillManager::CANCEL_BY_CHARGEBACK);
  300.         }
  301.         $event = new CoachingCancelledEvent($cart->getUser(), $cart$originalTransaction);
  302.         $this->eventDispatcher->dispatch($eventCoachingSubscriber::COACHING_CANCELLED);
  303.         $event = new UserMadeChargebackEvent($originalTransaction->getCart()->getUser());
  304.         $this->eventDispatcher->dispatch($eventUserSubscriber::USER_MAKE_CHARGEBACK);
  305.         $event = new CartChargedBackEvent($originalTransaction->getCart());
  306.         $this->eventDispatcher->dispatch($eventCartSubscriber::CART_CHARGEBACK);
  307.     }
  308. }