* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ if (!defined('_PS_VERSION_')) { exit; } use PsCheckout\Api\ValueObject\PayPalOrderResponse; use PsCheckout\Core\Exception\PsCheckoutException; use PsCheckout\Core\Order\Action\CreateOrderAction; use PsCheckout\Core\PayPal\Card3DSecure\Card3DSecureConfiguration; use PsCheckout\Core\PayPal\Card3DSecure\Card3DSecureValidator; use PsCheckout\Core\PayPal\Order\Action\CapturePayPalOrderAction; use PsCheckout\Core\PayPal\Order\Configuration\PayPalOrderStatus; use PsCheckout\Core\PayPal\Order\Entity\PayPalOrder; use PsCheckout\Core\PayPal\Order\Exception\PayPalOrderException; use PsCheckout\Core\PayPal\Order\Provider\PayPalOrderProvider; use PsCheckout\Core\Settings\Configuration\PayPalConfiguration; use PsCheckout\Infrastructure\Adapter\Tools; use PsCheckout\Infrastructure\Controller\AbstractFrontController; use PsCheckout\Infrastructure\Repository\OrderRepository; use PsCheckout\Infrastructure\Repository\PaymentTokenRepository; use PsCheckout\Infrastructure\Repository\PayPalOrderRepository; use PsCheckout\Module\Presentation\Translator; use Psr\Log\LoggerInterface; class Ps_CheckoutPaymentModuleFrontController extends AbstractFrontController { public $ssl = true; public $controller_type = 'front'; public $display_footer = false; public $display_header = true; private $orderPageUrl; private $paypalOrderId; /** * @return bool */ public function checkAccess(): bool { return $this->context->customer && $this->context->cart; } /** * @return void * * @throws PrestaShopException */ public function initContent() { parent::initContent(); $this->setTemplate('module:' . $this->module->name . '/views/templates/front/payment.tpl'); $this->context->smarty->assign([ 'css_url' => $this->module->getPathUri() . 'views/css/payment.css', 'order_url' => $this->orderPageUrl, ]); } /** * @return bool|void */ public function setMedia() { $this->registerStylesheet('ps_checkout_payment', '/modules/' . $this->module->name . '/views/css/payment.css'); parent::setMedia(); } /** * @return void */ public function postProcess() { $this->orderPageUrl = $this->context->link->getPageLink('order'); /** @var Tools $tools */ $tools = $this->module->getService(Tools::class); /** @var LoggerInterface $logger */ $logger = $this->module->getService(LoggerInterface::class); $orderId = $tools->getValue('orderID'); if (!$orderId) { $logger->error('No PayPal Order ID found.'); $tools->redirect($this->orderPageUrl); } $this->paypalOrderId = $orderId; $logger->info('Processing PayPal Order ID: ' . $this->paypalOrderId); try { /** @var PayPalOrderRepository $payPalOrderRepository */ $payPalOrderRepository = $this->module->getService(PayPalOrderRepository::class); /** @var PayPalOrderProvider $payPalOrderProvider */ $payPalOrderProvider = $this->module->getService(PayPalOrderProvider::class); /** @var OrderRepository $orderRepository */ $orderRepository = $this->module->getService(OrderRepository::class); $payPalOrder = $payPalOrderRepository->getOneBy(['id' => $this->paypalOrderId]); if (!$payPalOrder) { $logger->error('PayPal Order not found: ' . $this->paypalOrderId); $tools->redirect($this->orderPageUrl); } $orders = $orderRepository->getAllBy(['id_cart' => $payPalOrder->getIdCart()]); if (!empty($orders)) { $this->handleExistingOrder($payPalOrder->getIdCart()); } if ($payPalOrder->getIdCart() !== $this->context->cart->id) { $logger->error('Cart Id mismatch for PayPal Order: ' . $this->paypalOrderId); $tools->redirect($this->orderPageUrl); } $payPalOrderResponse = $payPalOrderProvider->getById($this->paypalOrderId); $this->handleOrderStatus($payPalOrderResponse, $payPalOrder); } catch (Throwable $exception) { $logger->error( sprintf( 'PaymentController - Exception %s : %s', $exception->getCode(), $exception->getMessage() ), ['exception' => $exception] ); /** @var Translator $translator **/ $translator = $this->module->getService(Translator::class); $this->context->smarty->assign('error', $translator->trans('There was an error during the payment. Please try again or contact the support.')); } } /** * @param int $cartId * * @return void */ private function handleExistingOrder(int $cartId) { /** @var Tools $tools */ $tools = $this->module->getService(Tools::class); /** @var PayPalOrderProvider $payPalOrderProvider */ $payPalOrderProvider = $this->module->getService(PayPalOrderProvider::class); if ($this->context->customer->isLogged()) { $tools->redirect($this->context->link->getPageLink('history')); } else { $payPalOrderResponse = $payPalOrderProvider->getById($this->paypalOrderId); $this->redirectToOrderConfirmationPage( $cartId, $payPalOrderResponse->getCapture()['id'] ?? null, $payPalOrderResponse->getStatus() ); } } /** * @param PayPalOrderResponse $payPalOrderResponse * @param PayPalOrder $payPalOrder * * @return void * * @throws PayPalOrderException * @throws PsCheckoutException */ private function handleOrderStatus( PayPalOrderResponse $payPalOrderResponse, PayPalOrder $payPalOrder ) { /** @var LoggerInterface $logger */ $logger = $this->module->getService(LoggerInterface::class); /** @var CapturePayPalOrderAction $capturePayPalOrderAction */ $capturePayPalOrderAction = $this->module->getService(CapturePayPalOrderAction::class); /** @var PayPalOrderProvider $payPalOrderProvider */ $payPalOrderProvider = $this->module->getService(PayPalOrderProvider::class); switch ($payPalOrderResponse->getStatus()) { case PayPalOrderStatus::COMPLETED: $logger->info('PayPal Order Completed: ' . $this->paypalOrderId); $this->createOrder($payPalOrderResponse, $payPalOrder); break; case PayPalOrderStatus::PAYER_ACTION_REQUIRED: $logger->info('3DS Verification Required for PayPal Order: ' . $this->paypalOrderId); $this->redirectTo3DSVerification($payPalOrderResponse); break; case PayPalOrderStatus::CREATED: $this->handle3DSecureDecision($payPalOrderResponse, $payPalOrder); break; case PayPalOrderStatus::APPROVED: $logger->info('Capturing PayPal Order: ' . $this->paypalOrderId); $capturePayPalOrderAction->execute($payPalOrderResponse); $this->createOrder($payPalOrderProvider->getById($this->paypalOrderId), $payPalOrder); break; } } /** * @param PayPalOrderResponse $payPalOrderResponse * @param PayPalOrder $payPalOrder * * @return void * * @throws PsCheckoutException * @throws PayPalOrderException */ private function handle3DSecureDecision( PayPalOrderResponse $payPalOrderResponse, PayPalOrder $payPalOrder ) { /** @var Card3DSecureValidator $card3DSecure */ $card3DSecure = $this->module->getService(Card3DSecureValidator::class); /** @var CapturePayPalOrderAction $capturePayPalOrderAction */ $capturePayPalOrderAction = $this->module->getService(CapturePayPalOrderAction::class); /** @var PayPalOrderProvider $payPalOrderProvider */ $payPalOrderProvider = $this->module->getService(PayPalOrderProvider::class); /** @var PayPalConfiguration $payPalConfiguration */ $payPalConfiguration = $this->module->getService(PayPalConfiguration::class); switch ($card3DSecure->getAuthorizationDecision($payPalOrderResponse)) { case Card3DSecureConfiguration::DECISION_RETRY: $this->redirectTo3DSVerification($payPalOrderResponse); break; case Card3DSecureConfiguration::DECISION_PROCEED: $capturePayPalOrderAction->execute($payPalOrderResponse); $this->createOrder($payPalOrderProvider->getById($this->paypalOrderId), $payPalOrder); break; case Card3DSecureConfiguration::DECISION_NO_DECISION: if ($payPalConfiguration->getCardFieldsContingencies() !== 'SCA_ALWAYS') { $capturePayPalOrderAction->execute($payPalOrderResponse); $this->createOrder($payPalOrderProvider->getById($this->paypalOrderId), $payPalOrder); } break; } } /** * @param PayPalOrderResponse $payPalOrderResponse * @param PayPalOrder $payPalOrder * * @return void * * @throws PsCheckoutException */ private function createOrder(PayPalOrderResponse $payPalOrderResponse, PayPalOrder $payPalOrder) { /** @var CreateOrderAction $createOrderAction */ $createOrderAction = $this->module->getService(CreateOrderAction::class); $createOrderAction->execute($payPalOrderResponse); if ($payPalOrder->getPaymentTokenId() && $payPalOrder->checkCustomerIntent(PayPalConfiguration::PS_CHECKOUT_CUSTOMER_INTENT_FAVORITE)) { /** @var PaymentTokenRepository $paymentTokenRepository */ $paymentTokenRepository = $this->module->getService(PaymentTokenRepository::class); $paymentTokenRepository->setTokenFavorite($payPalOrder->getPaymentTokenId(), $payPalOrderResponse->getCustomerId()); } $this->redirectToOrderConfirmationPage( $payPalOrder->getIdCart(), $payPalOrderResponse->getCapture()['id'] ?? null, $payPalOrderResponse->getStatus() ); } /** * @param PayPalOrderResponse $paypalOrder * * @return void */ private function redirectTo3DSVerification(PayPalOrderResponse $payPalOrderResponse) { /** @var Tools $tools */ $tools = $this->module->getService(Tools::class); $payerActionLinks = array_filter($payPalOrderResponse->getLinks(), function ($link) { return $link['rel'] === 'payer-action'; }); if (!empty($payerActionLinks)) { $redirectUrl = reset($payerActionLinks)['href'] . '&redirect_uri=' . urlencode( $this->context->link->getModuleLink($this->module->name, 'payment', ['orderID' => $payPalOrderResponse->getId()]) ); /** @var LoggerInterface $logger */ $logger = $this->module->getService(LoggerInterface::class); $logger->info('Redirecting to 3DS verification: ' . $redirectUrl); $tools->redirect($redirectUrl); } } /** * @param int $cartId * @param string|null $captureId * @param string $payPalOrderStatus * * @return void */ private function redirectToOrderConfirmationPage(int $cartId, $captureId, string $payPalOrderStatus) { /** @var Tools $tools */ $tools = $this->module->getService(Tools::class); /** @var OrderRepository $orderRepository */ $orderRepository = $this->module->getService(OrderRepository::class); $orders = $orderRepository->getAllBy(['id_cart' => $cartId]); if (empty($orders)) { return; } if ($_SERVER['REQUEST_METHOD'] === 'GET') { $orderConfirmationUrl = $this->context->link->getPageLink( 'order-confirmation', true, $orders[0]->id_lang ?? null, [ 'paypal_status' => $payPalOrderStatus, 'paypal_order' => $this->paypalOrderId, 'paypal_transaction' => $captureId, 'id_cart' => $cartId, 'id_module' => (int) $this->module->id, 'id_order' => $orders[0]->id ?? null, 'key' => (new Cart($cartId))->secure_key, ] ); /** @var LoggerInterface $logger */ $logger = $this->module->getService(LoggerInterface::class); $logger->info('Redirecting to order confirmation: ' . $orderConfirmationUrl); $tools->redirect($orderConfirmationUrl); } } }