Files
2026-04-09 18:31:51 +02:00

370 lines
14 KiB
PHP

<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @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);
}
}
}