From 08c775fb92153176afa0c43ff32887fba5bba7fc Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 9 Oct 2024 14:49:23 +0300 Subject: [PATCH 01/20] Added redirect to payment page after all capture retries fail --- controllers/front/payment.php | 30 ++++++++++++++++++- controllers/front/validate.php | 15 +++++++++- src/Checkout/CheckoutChecker.php | 4 +-- .../CheckoutEventSubscriber.php | 28 ++++++++--------- src/Exception/PsCheckoutException.php | 2 ++ .../CapturePayPalOrderCommandHandler.php | 4 +-- 6 files changed, 63 insertions(+), 20 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index 54e7e9402..9afaeb357 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -22,6 +22,9 @@ use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Command\CreateOrderCommand; +use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\PayPal\Card3DSecure; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Entity\PayPalOrder; @@ -87,6 +90,8 @@ public function postProcess() $commandBus = $this->module->getService('ps_checkout.bus.command'); /** @var Psr\SimpleCache\CacheInterface $payPalOrderCache */ $payPalOrderCache = $this->module->getService('ps_checkout.cache.paypal.order'); + /** @var OrderStateMapper $orderStateMapper */ + $orderStateMapper = $this->module->getService(OrderStateMapper::class); $payPalOrder = $payPalOrderRepository->getPayPalOrderById($this->paypalOrderId); @@ -94,10 +99,27 @@ public function postProcess() throw new Exception('PayPal order does not belong to this customer'); } + $payPalOrderCache->delete($this->paypalOrderId->getValue()); + $payPalOrderFromCache = $payPalOrderProvider->getById($payPalOrder->getId()->getValue()); if ($payPalOrderFromCache['status'] === 'COMPLETED') { - $this->createOrder($payPalOrderFromCache, $payPalOrder); + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', pSQL($payPalOrder->getIdCart())); + + if (!$orders->count()) { + $this->createOrder($payPalOrderFromCache, $payPalOrder); + } + + /** @var Order $order */ + $order = $orders->getFirst(); + $paidOrderStateId = (int) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED); + + if ($order->getCurrentOrderState()->id !== $paidOrderStateId) { + $commandBus->handle(new UpdateOrderStatusCommand($order->id, $paidOrderStateId)); + } + + $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]['id'], $payPalOrderFromCache['status']); } if ($payPalOrderFromCache['status'] === 'PAYER_ACTION_REQUIRED') { @@ -126,6 +148,12 @@ public function postProcess() break; } } + + if ($payPalOrderFromCache['status'] === 'APPROVED') { + $commandBus->handle(new CapturePayPalOrderCommand($this->paypalOrderId->getValue(), array_keys($payPalOrderFromCache['payment_source'])[0])); + $payPalOrderFromCache = $payPalOrderCache->get($this->paypalOrderId->getValue()); + $this->createOrder($payPalOrderFromCache, $payPalOrder); + } } catch (Exception $exception) { $this->context->smarty->assign('error', $exception->getMessage()); } diff --git a/controllers/front/validate.php b/controllers/front/validate.php index f6cb2fa30..4b6fef593 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -24,6 +24,7 @@ use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\Event\SymfonyEventDispatcherAdapter; +use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery; @@ -108,6 +109,18 @@ public function postProcess() )); $this->sendOkResponse($this->generateResponse()); + } catch (HttpTimeoutException $exception) { + $this->exitWithResponse([ + 'status' => false, + 'httpCode' => 408, + 'body' => [ + 'error' => [ + 'message' => $exception->getMessage(), + ], + ], + 'exceptionCode' => $exception->getCode(), + 'exceptionMessage' => $exception->getMessage(), + ]); } catch (Exception $exception) { $response = $this->generateResponse(); @@ -148,7 +161,7 @@ private function generateResponse() } $response = [ - 'status' => $psCheckoutCart->paypal_status, + 'status' => $psCheckoutCart->paypal_status, // Need to change this to get the status from PayPal Order instead? 'paypalOrderId' => $psCheckoutCart->paypal_order, 'transactionIdentifier' => $paypalOrder && isset($paypalOrder->getOrderPayPal()['purchase_units'][0]['payments']['captures'][0]) ? $paypalOrder->getOrderPayPal()['purchase_units'][0]['payments']['captures'][0]['id'] : null, ]; diff --git a/src/Checkout/CheckoutChecker.php b/src/Checkout/CheckoutChecker.php index 60c292ce4..206fb7574 100644 --- a/src/Checkout/CheckoutChecker.php +++ b/src/Checkout/CheckoutChecker.php @@ -51,10 +51,10 @@ public function __construct(LoggerInterface $logger) * * @throws PsCheckoutException */ - public function continueWithAuthorization($cartId, $orderPayPal) + public function continueWithAuthorization($cartId, $orderPayPal) { if ($orderPayPal['status'] === 'COMPLETED') { - throw new PsCheckoutException(sprintf('PayPal Order %s is already captured', $orderPayPal['id'])); + throw new PsCheckoutException(sprintf('PayPal Order %s is already captured', $orderPayPal['id']), PsCheckoutException::PAYPAL_ORDER_ALREADY_CAPTURED); } $paymentSource = isset($orderPayPal['payment_source']) ? key($orderPayPal['payment_source']) : ''; diff --git a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php index b393f1907..cdc9176be 100644 --- a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php +++ b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php @@ -118,22 +118,26 @@ public function updatePaymentMethodSelected(CheckoutCompletedEvent $event) * @throws PrestaShopDatabaseException * @throws PrestaShopException * @throws PsCheckoutException + * @throws HttpTimeoutException */ public function proceedToPayment(CheckoutCompletedEvent $event) { - try { - /** @var GetPayPalOrderForCheckoutCompletedQueryResult $getPayPalOrderForCheckoutCompletedQueryResult */ - $getPayPalOrderForCheckoutCompletedQueryResult = $this->commandBus->handle(new GetPayPalOrderForCheckoutCompletedQuery( - $event->getPayPalOrderId()->getValue() - )); - } catch (HttpTimeoutException $exception) { - $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + /** @var GetPayPalOrderForCheckoutCompletedQueryResult $getPayPalOrderForCheckoutCompletedQueryResult */ + $getPayPalOrderForCheckoutCompletedQueryResult = $this->commandBus->handle(new GetPayPalOrderForCheckoutCompletedQuery( + $event->getPayPalOrderId()->getValue() + )); - return; + try { + $this->checkoutChecker->continueWithAuthorization($event->getCartId()->getValue(), $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder()); + } catch (PsCheckoutException $exception) { + if ($exception->getCode() === PsCheckoutException::PAYPAL_ORDER_ALREADY_CAPTURED) { +// $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); +// return; + } else { + throw $exception; + } } - $this->checkoutChecker->continueWithAuthorization($event->getCartId()->getValue(), $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder()); - try { $this->commandBus->handle( new CapturePayPalOrderCommand( @@ -160,10 +164,6 @@ public function proceedToPayment(CheckoutCompletedEvent $event) } else { throw $exception; } - } catch (HttpTimeoutException $exception) { - $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); - - return; } } } diff --git a/src/Exception/PsCheckoutException.php b/src/Exception/PsCheckoutException.php index 627675825..1b5256976 100644 --- a/src/Exception/PsCheckoutException.php +++ b/src/Exception/PsCheckoutException.php @@ -89,4 +89,6 @@ class PsCheckoutException extends \Exception const CART_ADDRESS_DELIVERY_INVALID = 57; const CART_DELIVERY_OPTION_INVALID = 58; const PSCHECKOUT_HTTP_UNAUTHORIZED = 59; + + const PAYPAL_ORDER_ALREADY_CAPTURED = 60; } diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index eb42059fb..44094fe3a 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -26,6 +26,7 @@ use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext; use PrestaShop\Module\PrestashopCheckout\Customer\ValueObject\CustomerId; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Http\MaaslandHttpClient; use PrestaShop\Module\PrestashopCheckout\PayPal\Customer\ValueObject\PayPalCustomerId; @@ -90,8 +91,7 @@ public function __construct( public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) { - $context = Context::getContext(); - $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $context->shop->id); + $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $this->prestaShopContext->getShopId()); $payload = [ 'mode' => $capturePayPalOrderCommand->getFundingSource(), From ebb380e5ad94133aa73218afc81dc5fe206728c1 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Mon, 14 Oct 2024 15:06:09 +0300 Subject: [PATCH 02/20] added order creation --- controllers/front/payment.php | 23 +++++-------------- controllers/front/validate.php | 2 +- .../CheckoutEventSubscriber.php | 5 ++-- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index 9afaeb357..1f7404b43 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -22,13 +22,12 @@ use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Command\CreateOrderCommand; -use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; -use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; -use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\PayPal\Card3DSecure; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Entity\PayPalOrder; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQueryResult; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; use PrestaShop\Module\PrestashopCheckout\Repository\PaymentTokenRepository; @@ -90,8 +89,6 @@ public function postProcess() $commandBus = $this->module->getService('ps_checkout.bus.command'); /** @var Psr\SimpleCache\CacheInterface $payPalOrderCache */ $payPalOrderCache = $this->module->getService('ps_checkout.cache.paypal.order'); - /** @var OrderStateMapper $orderStateMapper */ - $orderStateMapper = $this->module->getService(OrderStateMapper::class); $payPalOrder = $payPalOrderRepository->getPayPalOrderById($this->paypalOrderId); @@ -99,26 +96,18 @@ public function postProcess() throw new Exception('PayPal order does not belong to this customer'); } - $payPalOrderCache->delete($this->paypalOrderId->getValue()); - - $payPalOrderFromCache = $payPalOrderProvider->getById($payPalOrder->getId()->getValue()); + /** @var GetPayPalOrderForOrderConfirmationQueryResult $payPalOrderQueryResult */ + $payPalOrderQueryResult = $commandBus->handle(new GetPayPalOrderForOrderConfirmationQuery($this->paypalOrderId->getValue())); + $payPalOrderFromCache = $payPalOrderQueryResult->getOrderPayPal(); if ($payPalOrderFromCache['status'] === 'COMPLETED') { $orders = new PrestaShopCollection(Order::class); - $orders->where('id_cart', '=', pSQL($payPalOrder->getIdCart())); + $orders->where('id_cart', '=', $payPalOrder->getIdCart()); if (!$orders->count()) { $this->createOrder($payPalOrderFromCache, $payPalOrder); } - /** @var Order $order */ - $order = $orders->getFirst(); - $paidOrderStateId = (int) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED); - - if ($order->getCurrentOrderState()->id !== $paidOrderStateId) { - $commandBus->handle(new UpdateOrderStatusCommand($order->id, $paidOrderStateId)); - } - $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]['id'], $payPalOrderFromCache['status']); } diff --git a/controllers/front/validate.php b/controllers/front/validate.php index 4b6fef593..22789575f 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -112,7 +112,7 @@ public function postProcess() } catch (HttpTimeoutException $exception) { $this->exitWithResponse([ 'status' => false, - 'httpCode' => 408, + 'httpCode' => 504, 'body' => [ 'error' => [ 'message' => $exception->getMessage(), diff --git a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php index cdc9176be..671a20787 100644 --- a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php +++ b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php @@ -131,8 +131,8 @@ public function proceedToPayment(CheckoutCompletedEvent $event) $this->checkoutChecker->continueWithAuthorization($event->getCartId()->getValue(), $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder()); } catch (PsCheckoutException $exception) { if ($exception->getCode() === PsCheckoutException::PAYPAL_ORDER_ALREADY_CAPTURED) { -// $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); -// return; + $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + return; } else { throw $exception; } @@ -160,6 +160,7 @@ public function proceedToPayment(CheckoutCompletedEvent $event) throw $exception; } elseif ($exception->getCode() === PayPalException::ORDER_ALREADY_CAPTURED) { + $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); return; } else { throw $exception; From 0e3a43ca898d6a630f955318991f0c28e2d6a699 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Mon, 14 Oct 2024 17:38:09 +0300 Subject: [PATCH 03/20] Added exception code for errors --- controllers/front/validate.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/controllers/front/validate.php b/controllers/front/validate.php index 22789575f..9fa669b26 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -24,7 +24,6 @@ use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\Event\SymfonyEventDispatcherAdapter; -use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery; @@ -109,18 +108,6 @@ public function postProcess() )); $this->sendOkResponse($this->generateResponse()); - } catch (HttpTimeoutException $exception) { - $this->exitWithResponse([ - 'status' => false, - 'httpCode' => 504, - 'body' => [ - 'error' => [ - 'message' => $exception->getMessage(), - ], - ], - 'exceptionCode' => $exception->getCode(), - 'exceptionMessage' => $exception->getMessage(), - ]); } catch (Exception $exception) { $response = $this->generateResponse(); @@ -451,6 +438,7 @@ private function handleException(Exception $exception) 'body' => [ 'error' => [ 'message' => $exceptionMessageForCustomer, + 'code' => $exception->getCode(), ], ], 'exceptionCode' => $exception->getCode(), From ce44782c1b651dea492058bd2279b50f0d6110e7 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 15 Oct 2024 17:21:22 +0300 Subject: [PATCH 04/20] Added retry in maasland http client --- src/Http/MaaslandHttpClient.php | 14 ++++- .../CapturePayPalOrderCommandHandler.php | 3 +- tests/Unit/Http/MaaslandHttpClientTest.php | 54 ++++++++++++++++++- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/Http/MaaslandHttpClient.php b/src/Http/MaaslandHttpClient.php index e68bbc29e..7b8b4374f 100644 --- a/src/Http/MaaslandHttpClient.php +++ b/src/Http/MaaslandHttpClient.php @@ -30,6 +30,7 @@ use PrestaShop\Module\PrestashopCheckout\PayPalError; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use function _PHPStan_f2258ada8\React\Async\await; class MaaslandHttpClient implements HttpClientInterface { @@ -110,9 +111,18 @@ public function fetchOrder(array $payload, array $options = []) * @throws PayPalException * @throws HttpTimeoutException */ - public function captureOrder(array $payload, array $options = []) + public function captureOrder(array $payload, array $options = [], $maxRetries = 0, $backOff = 1) { - return $this->sendRequest(new Request('POST', '/payments/order/capture', $options, json_encode($payload))); + try { + return $this->sendRequest(new Request('POST', '/payments/order/capture', $options, json_encode($payload))); + } catch (HttpTimeoutException $exception) { + if ($maxRetries > 0) { + sleep($backOff); + return $this->captureOrder($payload, $options, $maxRetries - 1, $backOff * 2); + } + + throw $exception; + } } /** diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index 44094fe3a..798fdff3f 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -91,6 +91,7 @@ public function __construct( public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) { + throw new HttpTimeoutException('PayPal timeout', 504); $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $this->prestaShopContext->getShopId()); $payload = [ @@ -105,7 +106,7 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) $payload['vault'] = true; } - $response = $this->maaslandHttpClient->captureOrder($payload); + $response = $this->maaslandHttpClient->captureOrder($payload, [], 3); $orderPayPal = json_decode($response->getBody(), true); diff --git a/tests/Unit/Http/MaaslandHttpClientTest.php b/tests/Unit/Http/MaaslandHttpClientTest.php index 9caedcf93..dd0a946e2 100644 --- a/tests/Unit/Http/MaaslandHttpClientTest.php +++ b/tests/Unit/Http/MaaslandHttpClientTest.php @@ -22,6 +22,7 @@ use Http\Client\Exception\HttpException; use PHPUnit\Framework\TestCase; +use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; use PrestaShop\Module\PrestashopCheckout\Http\HttpClientInterface; use PrestaShop\Module\PrestashopCheckout\Http\MaaslandHttpClient; @@ -111,6 +112,34 @@ public function testNotFoundErrorsCreateOrderLegacy($errorName, $errorCode) $this->handleTestErrorsCreateOrder(404, $errorName, $errorCode, true); } + /** + * @dataProvider notAuthorizedErrorsProvider + */ + public function testNotAuthorizedErrorsCaptureOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCaptureOrder(401, $errorName, $errorCode); + } + + public function testTimeoutCaptureOrder() + { + $streamMock = $this->createMock(StreamInterface::class); + $streamMock->method('__toString')->willReturn(json_encode([])); + $responseMock = $this->createMock(ResponseInterface::class); + $responseMock->method('getStatusCode')->willReturn(504); + $responseMock->method('getBody')->willReturn($streamMock); + $httpClient = $this->createMock(HttpClientInterface::class); + $httpClient->method('sendRequest')->willThrowException(new HttpTimeoutException()); + + $maaslandHttpClient = new MaaslandHttpClient($httpClient); + + $httpClient->expects($this->exactly(4)) + ->method('sendRequest'); + + $this->expectException(HttpTimeoutException::class); + + $maaslandHttpClient->captureOrder([], [], 3); + } + /** * @param int $statusCode * @param string $errorName @@ -132,8 +161,29 @@ private function handleTestErrorsCreateOrder($statusCode, $errorName, $errorCode $httpClient->method('sendRequest')->willThrowException(new HttpException('An error occurred', $requestMock, $responseMock)); $this->expectExceptionCode($errorCode); $this->expectException(PayPalException::class); - $checkoutHttpClient = new MaaslandHttpClient($httpClient); - $checkoutHttpClient->createOrder([]); + $maaslandHttpClient = new MaaslandHttpClient($httpClient); + $maaslandHttpClient->createOrder([]); + } + + private function handleTestErrorsCaptureOrder($statusCode, $errorName, $errorCode, $legacy = false) + { + $error = $this->getErrorResponse($statusCode, $errorName, $legacy); + $requestMock = $this->createMock(RequestInterface::class); + $streamMock = $this->createMock(StreamInterface::class); + $streamMock->method('__toString')->willReturn(json_encode($error)); + $responseMock = $this->createMock(ResponseInterface::class); + $responseMock->method('getStatusCode')->willReturn($statusCode); + $responseMock->method('getBody')->willReturn($streamMock); + $httpClient = $this->createMock(HttpClientInterface::class); + $httpClient->method('sendRequest')->willThrowException(new HttpException('An error occurred', $requestMock, $responseMock)); + $this->expectExceptionCode($errorCode); + $this->expectException(PayPalException::class); + $maaslandHttpClient = new MaaslandHttpClient($httpClient); + + $httpClient->expects($this->once()) + ->method('sendRequest'); + + $maaslandHttpClient->captureOrder([]); } private function getInvalidRequestError($issueError) From 4724aea6401d283fa25f02850a643e21e9691d86 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 16 Oct 2024 10:42:35 +0300 Subject: [PATCH 05/20] Removed wrong imports --- src/Checkout/CheckoutChecker.php | 2 +- src/Http/MaaslandHttpClient.php | 1 - .../Order/CommandHandler/CapturePayPalOrderCommandHandler.php | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Checkout/CheckoutChecker.php b/src/Checkout/CheckoutChecker.php index 206fb7574..df8bc537c 100644 --- a/src/Checkout/CheckoutChecker.php +++ b/src/Checkout/CheckoutChecker.php @@ -51,7 +51,7 @@ public function __construct(LoggerInterface $logger) * * @throws PsCheckoutException */ - public function continueWithAuthorization($cartId, $orderPayPal) + public function continueWithAuthorization($cartId, $orderPayPal) { if ($orderPayPal['status'] === 'COMPLETED') { throw new PsCheckoutException(sprintf('PayPal Order %s is already captured', $orderPayPal['id']), PsCheckoutException::PAYPAL_ORDER_ALREADY_CAPTURED); diff --git a/src/Http/MaaslandHttpClient.php b/src/Http/MaaslandHttpClient.php index 7b8b4374f..66d7db2ed 100644 --- a/src/Http/MaaslandHttpClient.php +++ b/src/Http/MaaslandHttpClient.php @@ -30,7 +30,6 @@ use PrestaShop\Module\PrestashopCheckout\PayPalError; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use function _PHPStan_f2258ada8\React\Async\await; class MaaslandHttpClient implements HttpClientInterface { diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index 798fdff3f..25d16964d 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -91,7 +91,6 @@ public function __construct( public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) { - throw new HttpTimeoutException('PayPal timeout', 504); $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $this->prestaShopContext->getShopId()); $payload = [ From 2782adf489133d893bcab9e419af1f81daca7372 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 23 Oct 2024 17:19:36 +0300 Subject: [PATCH 06/20] Removed retries from MaaslandHttpClient and added custom ttl for PayPal orders by status --- config/cache.yml | 2 +- src/Http/MaaslandHttpClient.php | 13 ++------- src/PayPal/Order/Cache/PayPalOrderCache.php | 27 ++++++++++++++++++ src/PayPal/Order/Cache/index.php | 28 +++++++++++++++++++ ...lOrderForCheckoutCompletedQueryHandler.php | 2 +- 5 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 src/PayPal/Order/Cache/PayPalOrderCache.php create mode 100644 src/PayPal/Order/Cache/index.php diff --git a/config/cache.yml b/config/cache.yml index 397b22943..d5b28e93c 100644 --- a/config/cache.yml +++ b/config/cache.yml @@ -18,7 +18,7 @@ services: - '@=service("PrestaShop\\ModuleLibCacheDirectoryProvider\\Cache\\CacheDirectoryProvider").getPath()' ps_checkout.cache.paypal.order: - class: 'Symfony\Component\Cache\Simple\ChainCache' + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\PayPalOrderCache' public: true arguments: - [ diff --git a/src/Http/MaaslandHttpClient.php b/src/Http/MaaslandHttpClient.php index 66d7db2ed..e68bbc29e 100644 --- a/src/Http/MaaslandHttpClient.php +++ b/src/Http/MaaslandHttpClient.php @@ -110,18 +110,9 @@ public function fetchOrder(array $payload, array $options = []) * @throws PayPalException * @throws HttpTimeoutException */ - public function captureOrder(array $payload, array $options = [], $maxRetries = 0, $backOff = 1) + public function captureOrder(array $payload, array $options = []) { - try { - return $this->sendRequest(new Request('POST', '/payments/order/capture', $options, json_encode($payload))); - } catch (HttpTimeoutException $exception) { - if ($maxRetries > 0) { - sleep($backOff); - return $this->captureOrder($payload, $options, $maxRetries - 1, $backOff * 2); - } - - throw $exception; - } + return $this->sendRequest(new Request('POST', '/payments/order/capture', $options, json_encode($payload))); } /** diff --git a/src/PayPal/Order/Cache/PayPalOrderCache.php b/src/PayPal/Order/Cache/PayPalOrderCache.php new file mode 100644 index 000000000..0a6be18b4 --- /dev/null +++ b/src/PayPal/Order/Cache/PayPalOrderCache.php @@ -0,0 +1,27 @@ + 600, + PsCheckoutCart::STATUS_PAYER_ACTION_REQUIRED => 600, + PsCheckoutCart::STATUS_APPROVED => 600, + PsCheckoutCart::STATUS_VOIDED => 3600, + PsCheckoutCart::STATUS_SAVED => 3600, + PsCheckoutCart::STATUS_CANCELED => 3600, + PsCheckoutCart::STATUS_COMPLETED => 3600, + ]; + public function set($key, $value, $ttl = null) + { + if (!$ttl && isset($value['status']) && isset(self::CACHE_TTL[$value['status']])) { + $ttl = self::CACHE_TTL[$value['status']]; + } + + return parent::set($key, $value, $ttl); + } +} diff --git a/src/PayPal/Order/Cache/index.php b/src/PayPal/Order/Cache/index.php new file mode 100644 index 000000000..296d682e8 --- /dev/null +++ b/src/PayPal/Order/Cache/index.php @@ -0,0 +1,28 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php index 6c4a439cc..5c5862591 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php @@ -49,7 +49,7 @@ public function handle(GetPayPalOrderForCheckoutCompletedQuery $getPayPalOrderQu /** @var array{id: string, status: string} $order */ $order = $this->orderPayPalCache->get($getPayPalOrderQuery->getOrderPayPalId()->getValue()); - if (!empty($order) && $order['status'] === 'APPROVED') { + if (!empty($order) && !in_array($order['status'], ['APPROVED', 'COMPLETED'])) { return new GetPayPalOrderForCheckoutCompletedQueryResult($order); } From 50d2bd25c1a9c2eb9dccf14158bbdbd152071e29 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 23 Oct 2024 17:30:06 +0300 Subject: [PATCH 07/20] CS fixes --- .../CheckoutEventSubscriber.php | 2 ++ src/PayPal/Order/Cache/PayPalOrderCache.php | 20 +++++++++++++++++++ .../CapturePayPalOrderCommandHandler.php | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php index 671a20787..1edbf115f 100644 --- a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php +++ b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php @@ -132,6 +132,7 @@ public function proceedToPayment(CheckoutCompletedEvent $event) } catch (PsCheckoutException $exception) { if ($exception->getCode() === PsCheckoutException::PAYPAL_ORDER_ALREADY_CAPTURED) { $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + return; } else { throw $exception; @@ -161,6 +162,7 @@ public function proceedToPayment(CheckoutCompletedEvent $event) throw $exception; } elseif ($exception->getCode() === PayPalException::ORDER_ALREADY_CAPTURED) { $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + return; } else { throw $exception; diff --git a/src/PayPal/Order/Cache/PayPalOrderCache.php b/src/PayPal/Order/Cache/PayPalOrderCache.php index 0a6be18b4..083737ed9 100644 --- a/src/PayPal/Order/Cache/PayPalOrderCache.php +++ b/src/PayPal/Order/Cache/PayPalOrderCache.php @@ -1,5 +1,24 @@ + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache; use PsCheckoutCart; @@ -16,6 +35,7 @@ class PayPalOrderCache extends ChainCache PsCheckoutCart::STATUS_CANCELED => 3600, PsCheckoutCart::STATUS_COMPLETED => 3600, ]; + public function set($key, $value, $ttl = null) { if (!$ttl && isset($value['status']) && isset(self::CACHE_TTL[$value['status']])) { diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index 25d16964d..44094fe3a 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -105,7 +105,7 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) $payload['vault'] = true; } - $response = $this->maaslandHttpClient->captureOrder($payload, [], 3); + $response = $this->maaslandHttpClient->captureOrder($payload); $orderPayPal = json_decode($response->getBody(), true); From a036267cf8ca34fec89a9a632daf7a36747ed346 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 23 Oct 2024 17:33:24 +0300 Subject: [PATCH 08/20] Removed redundant test --- .../CapturePayPalOrderCommandHandler.php | 2 -- tests/Unit/Http/MaaslandHttpClientTest.php | 21 ------------------- 2 files changed, 23 deletions(-) diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index 44094fe3a..b3fade10a 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -22,11 +22,9 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; use Configuration; -use Context; use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext; use PrestaShop\Module\PrestashopCheckout\Customer\ValueObject\CustomerId; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Http\MaaslandHttpClient; use PrestaShop\Module\PrestashopCheckout\PayPal\Customer\ValueObject\PayPalCustomerId; diff --git a/tests/Unit/Http/MaaslandHttpClientTest.php b/tests/Unit/Http/MaaslandHttpClientTest.php index dd0a946e2..212b8d261 100644 --- a/tests/Unit/Http/MaaslandHttpClientTest.php +++ b/tests/Unit/Http/MaaslandHttpClientTest.php @@ -22,7 +22,6 @@ use Http\Client\Exception\HttpException; use PHPUnit\Framework\TestCase; -use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; use PrestaShop\Module\PrestashopCheckout\Http\HttpClientInterface; use PrestaShop\Module\PrestashopCheckout\Http\MaaslandHttpClient; @@ -120,26 +119,6 @@ public function testNotAuthorizedErrorsCaptureOrderLegacy($errorName, $errorCode $this->handleTestErrorsCaptureOrder(401, $errorName, $errorCode); } - public function testTimeoutCaptureOrder() - { - $streamMock = $this->createMock(StreamInterface::class); - $streamMock->method('__toString')->willReturn(json_encode([])); - $responseMock = $this->createMock(ResponseInterface::class); - $responseMock->method('getStatusCode')->willReturn(504); - $responseMock->method('getBody')->willReturn($streamMock); - $httpClient = $this->createMock(HttpClientInterface::class); - $httpClient->method('sendRequest')->willThrowException(new HttpTimeoutException()); - - $maaslandHttpClient = new MaaslandHttpClient($httpClient); - - $httpClient->expects($this->exactly(4)) - ->method('sendRequest'); - - $this->expectException(HttpTimeoutException::class); - - $maaslandHttpClient->captureOrder([], [], 3); - } - /** * @param int $statusCode * @param string $errorName From d76bfdc4eb1745ccf1b30671ba076ef8a8f979dd Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Fri, 25 Oct 2024 13:55:12 +0300 Subject: [PATCH 09/20] Updated exception code handling for ajax requests --- controllers/front/validate.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/controllers/front/validate.php b/controllers/front/validate.php index 9fa669b26..31b2dba0e 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -438,7 +438,9 @@ private function handleException(Exception $exception) 'body' => [ 'error' => [ 'message' => $exceptionMessageForCustomer, - 'code' => $exception->getCode(), + 'code' => (int) $exception->getCode() < 400 && $exception->getPrevious() !== null + ? (int) $exception->getPrevious()->getCode() + : (int) $exception->getCode(), ], ], 'exceptionCode' => $exception->getCode(), From 3999f9e6a7ad097ec17301cd1fdd2a3484d66e1c Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 29 Oct 2024 11:15:23 +0200 Subject: [PATCH 10/20] Added translation for changed loader --- config.xml | 2 +- ps_checkout.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config.xml b/config.xml index 9b996e38d..bc5ab5542 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index 9b274a7ce..f02980802 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -116,7 +116,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.4.2.1'; + const VERSION = '8.4.2.2'; const INTEGRATION_DATE = '2024-04-01'; @@ -137,7 +137,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.4.2.1'; + $this->version = '8.4.2.2'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; @@ -1110,6 +1110,7 @@ public function hookActionFrontControllerSetMedia() 'checkout.form.error.label' => $this->l('There was an error during the payment. Please try again or contact the support.'), 'loader-component.label.header' => $this->l('Thanks for your purchase!'), 'loader-component.label.body' => $this->l('Please wait, we are processing your payment'), + 'loader-component.label.body.longer' => $this->l('This is taking longer than expected. Please wait...'), 'error.paypal-sdk.contingency.cancel' => $this->l('Card holder authentication canceled, please choose another payment method or try again.'), 'error.paypal-sdk.contingency.error' => $this->l('An error occurred on card holder authentication, please choose another payment method or try again.'), 'error.paypal-sdk.contingency.failure' => $this->l('Card holder authentication failed, please choose another payment method or try again.'), From 5e0341fdf5ec10babef0fb41840e63373b462d96 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 30 Oct 2024 15:15:06 +0200 Subject: [PATCH 11/20] Added order status check before fetch --- .../GetPayPalOrderForCheckoutCompletedQueryHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php index 5c5862591..5e209b4e1 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php @@ -49,7 +49,7 @@ public function handle(GetPayPalOrderForCheckoutCompletedQuery $getPayPalOrderQu /** @var array{id: string, status: string} $order */ $order = $this->orderPayPalCache->get($getPayPalOrderQuery->getOrderPayPalId()->getValue()); - if (!empty($order) && !in_array($order['status'], ['APPROVED', 'COMPLETED'])) { + if (!empty($order) && in_array($order['status'], ['COMPLETED', 'CANCELED'])) { return new GetPayPalOrderForCheckoutCompletedQueryResult($order); } From a7b2026f3653f39de5ac75f2430be797a508259c Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 30 Oct 2024 17:39:11 +0200 Subject: [PATCH 12/20] Added redirects from payment controller --- controllers/front/payment.php | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index 1f7404b43..b1bd1f6ea 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -29,7 +29,6 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQueryResult; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; -use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; use PrestaShop\Module\PrestashopCheckout\Repository\PaymentTokenRepository; use PrestaShop\Module\PrestashopCheckout\Repository\PayPalOrderRepository; @@ -83,8 +82,6 @@ public function postProcess() /** @var PayPalOrderRepository $payPalOrderRepository */ $payPalOrderRepository = $this->module->getService(PayPalOrderRepository::class); - /** @var PayPalOrderProvider $payPalOrderProvider */ - $payPalOrderProvider = $this->module->getService(PayPalOrderProvider::class); /** @var CommandBusInterface $commandBus */ $commandBus = $this->module->getService('ps_checkout.bus.command'); /** @var Psr\SimpleCache\CacheInterface $payPalOrderCache */ @@ -93,7 +90,14 @@ public function postProcess() $payPalOrder = $payPalOrderRepository->getPayPalOrderById($this->paypalOrderId); if ($payPalOrder->getIdCart() !== $this->context->cart->id) { - throw new Exception('PayPal order does not belong to this customer'); + $this->redirectToOrderPage(); + } + + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', $payPalOrder->getIdCart()); + + if ($orders->count()) { + $this->redirectToOrderHistoryPage(); } /** @var GetPayPalOrderForOrderConfirmationQueryResult $payPalOrderQueryResult */ @@ -101,22 +105,11 @@ public function postProcess() $payPalOrderFromCache = $payPalOrderQueryResult->getOrderPayPal(); if ($payPalOrderFromCache['status'] === 'COMPLETED') { - $orders = new PrestaShopCollection(Order::class); - $orders->where('id_cart', '=', $payPalOrder->getIdCart()); - - if (!$orders->count()) { - $this->createOrder($payPalOrderFromCache, $payPalOrder); - } - + $this->createOrder($payPalOrderFromCache, $payPalOrder); $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]['id'], $payPalOrderFromCache['status']); } if ($payPalOrderFromCache['status'] === 'PAYER_ACTION_REQUIRED') { - // Delete from cache so when user is redirected from 3DS authentication page the order is fetched from PayPal - if ($payPalOrderCache->has($this->paypalOrderId->getValue())) { - $payPalOrderCache->delete($this->paypalOrderId->getValue()); - } - $this->redirectTo3DSVerification($payPalOrderFromCache); } @@ -235,4 +228,9 @@ private function redirectToOrderConfirmationPage($cartId, $captureId, $payPalOrd )); } } + + private function redirectToOrderHistoryPage() + { + Tools::redirect($this->context->link->getPageLink('history')); + } } From 87f1481e7c96ff47d0fe8e9473503a11d4968078 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Thu, 31 Oct 2024 14:46:32 +0200 Subject: [PATCH 13/20] Fixed payment controller access and redirection --- controllers/front/payment.php | 10 +++++----- src/PayPal/Order/PaypalOrderDataProvider.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index b1bd1f6ea..d0d8050b2 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -51,7 +51,7 @@ class Ps_CheckoutPaymentModuleFrontController extends AbstractFrontController public function checkAccess() { - return $this->context->customer && $this->context->customer->isLogged() && $this->context->cart; + return $this->context->customer && $this->context->cart; } public function initContent() @@ -89,10 +89,6 @@ public function postProcess() $payPalOrder = $payPalOrderRepository->getPayPalOrderById($this->paypalOrderId); - if ($payPalOrder->getIdCart() !== $this->context->cart->id) { - $this->redirectToOrderPage(); - } - $orders = new PrestaShopCollection(Order::class); $orders->where('id_cart', '=', $payPalOrder->getIdCart()); @@ -100,6 +96,10 @@ public function postProcess() $this->redirectToOrderHistoryPage(); } + if ($payPalOrder->getIdCart() !== $this->context->cart->id) { + $this->redirectToOrderPage(); + } + /** @var GetPayPalOrderForOrderConfirmationQueryResult $payPalOrderQueryResult */ $payPalOrderQueryResult = $commandBus->handle(new GetPayPalOrderForOrderConfirmationQuery($this->paypalOrderId->getValue())); $payPalOrderFromCache = $payPalOrderQueryResult->getOrderPayPal(); diff --git a/src/PayPal/Order/PaypalOrderDataProvider.php b/src/PayPal/Order/PaypalOrderDataProvider.php index ed5c296ef..70cfd01ed 100644 --- a/src/PayPal/Order/PaypalOrderDataProvider.php +++ b/src/PayPal/Order/PaypalOrderDataProvider.php @@ -158,7 +158,7 @@ public function isTokenSaved() public function getPaymentTokenIdentifier() { - if ($this->payPalOrder) { + if ($this->payPalOrder && isset($this->payPalOrder->getPaymentSource()[$this->payPalOrder->getFundingSource()])) { $paymentSource = $this->payPalOrder->getPaymentSource()[$this->payPalOrder->getFundingSource()]; if ($this->payPalOrder->getFundingSource() === 'card') { From 065bee557fc04f7aab04168d2a5498fe3893a197 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Mon, 4 Nov 2024 14:06:13 +0200 Subject: [PATCH 14/20] Changed the wrong Query to correct one --- controllers/front/payment.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index d0d8050b2..ce806e4cc 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -26,8 +26,8 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Entity\PayPalOrder; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQueryResult; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQuery; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQueryResult; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; use PrestaShop\Module\PrestashopCheckout\Repository\PaymentTokenRepository; use PrestaShop\Module\PrestashopCheckout\Repository\PayPalOrderRepository; @@ -100,13 +100,12 @@ public function postProcess() $this->redirectToOrderPage(); } - /** @var GetPayPalOrderForOrderConfirmationQueryResult $payPalOrderQueryResult */ - $payPalOrderQueryResult = $commandBus->handle(new GetPayPalOrderForOrderConfirmationQuery($this->paypalOrderId->getValue())); - $payPalOrderFromCache = $payPalOrderQueryResult->getOrderPayPal(); + /** @var GetPayPalOrderForCheckoutCompletedQueryResult $payPalOrderQueryResult */ + $payPalOrderQueryResult = $commandBus->handle(new GetPayPalOrderForCheckoutCompletedQuery($this->paypalOrderId->getValue())); + $payPalOrderFromCache = $payPalOrderQueryResult->getPayPalOrder(); if ($payPalOrderFromCache['status'] === 'COMPLETED') { $this->createOrder($payPalOrderFromCache, $payPalOrder); - $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]['id'], $payPalOrderFromCache['status']); } if ($payPalOrderFromCache['status'] === 'PAYER_ACTION_REQUIRED') { From 66d463fb599c387d429328e26aace15a8e8e8f14 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Mon, 4 Nov 2024 16:57:31 +0200 Subject: [PATCH 15/20] Fixed incorrect order status issues --- .../CheckoutEventSubscriber.php | 19 ++++++++++++++++--- ...lOrderForCheckoutCompletedQueryHandler.php | 3 ++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php index 1edbf115f..cb3bc979f 100644 --- a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php +++ b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php @@ -127,11 +127,14 @@ public function proceedToPayment(CheckoutCompletedEvent $event) $event->getPayPalOrderId()->getValue() )); + $payPalOrder = $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder(); + try { - $this->checkoutChecker->continueWithAuthorization($event->getCartId()->getValue(), $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder()); + $this->checkoutChecker->continueWithAuthorization($event->getCartId()->getValue(), $payPalOrder); } catch (PsCheckoutException $exception) { if ($exception->getCode() === PsCheckoutException::PAYPAL_ORDER_ALREADY_CAPTURED) { - $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + $capture = $payPalOrder['purchase_units'][0]['payments']['captures'][0] ?? null; + $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue(), $capture)); return; } else { @@ -161,7 +164,17 @@ public function proceedToPayment(CheckoutCompletedEvent $event) throw $exception; } elseif ($exception->getCode() === PayPalException::ORDER_ALREADY_CAPTURED) { - $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + if (isset($payPalOrder['purchase_units'][0]['payments']['captures'][0])) { + $capture = $payPalOrder['purchase_units'][0]['payments']['captures'][0]; + } else { + /** @var GetPayPalOrderForCheckoutCompletedQueryResult $getPayPalOrderForCheckoutCompletedQueryResult */ + $getPayPalOrderForCheckoutCompletedQueryResult = $this->commandBus->handle(new GetPayPalOrderForCheckoutCompletedQuery( + $event->getPayPalOrderId()->getValue() + )); + $payPalOrder = $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder(); + $capture = $payPalOrder['purchase_units'][0]['payments']['captures'][0] ?? null; + } + $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue(), $capture)); return; } else { diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php index 5e209b4e1..61fd3aae6 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php @@ -55,7 +55,8 @@ public function handle(GetPayPalOrderForCheckoutCompletedQuery $getPayPalOrderQu try { $orderPayPal = new PaypalOrder($getPayPalOrderQuery->getOrderPayPalId()->getValue()); - $this->orderPayPalCache->set($getPayPalOrderQuery->getOrderPayPalId()->getValue(), $orderPayPal->getOrder()); + $orderToStoreInCache = !empty($order) ? array_replace_recursive($order, $orderPayPal->getOrder()) : $orderPayPal->getOrder(); + $this->orderPayPalCache->set($getPayPalOrderQuery->getOrderPayPalId()->getValue(), $orderToStoreInCache); } catch (HttpTimeoutException $exception) { throw $exception; } catch (Exception $exception) { From c4737982eaf893983a934a84f2f757f757f161e6 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 5 Nov 2024 09:44:53 +0200 Subject: [PATCH 16/20] Removed capture status check in payment controller --- controllers/front/payment.php | 16 +++++++--------- .../EventSubscriber/CheckoutEventSubscriber.php | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index ce806e4cc..758caddec 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -155,16 +155,14 @@ private function createOrder($payPalOrderFromCache, $payPalOrder) /** @var CommandBusInterface $commandBus */ $commandBus = $this->module->getService('ps_checkout.bus.command'); - $capture = $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]; - if ($capture['status'] === 'COMPLETED') { - $commandBus->handle(new CreateOrderCommand($payPalOrder->getId()->getValue(), $capture)); - if ($payPalOrder->getPaymentTokenId() && $payPalOrder->checkCustomerIntent(PayPalOrder::CUSTOMER_INTENT_FAVORITE)) { - /** @var PaymentTokenRepository $paymentTokenRepository */ - $paymentTokenRepository = $this->module->getService(PaymentTokenRepository::class); - $paymentTokenRepository->setTokenFavorite($payPalOrder->getPaymentTokenId()); - } - $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $capture['id'], $payPalOrderFromCache['status']); + $capture = $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0] ?? null; + $commandBus->handle(new CreateOrderCommand($payPalOrder->getId()->getValue(), $capture)); + if ($payPalOrder->getPaymentTokenId() && $payPalOrder->checkCustomerIntent(PayPalOrder::CUSTOMER_INTENT_FAVORITE)) { + /** @var PaymentTokenRepository $paymentTokenRepository */ + $paymentTokenRepository = $this->module->getService(PaymentTokenRepository::class); + $paymentTokenRepository->setTokenFavorite($payPalOrder->getPaymentTokenId()); } + $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $capture['id'], $payPalOrderFromCache['status']); } /** diff --git a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php index cb3bc979f..3ace90834 100644 --- a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php +++ b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php @@ -133,7 +133,7 @@ public function proceedToPayment(CheckoutCompletedEvent $event) $this->checkoutChecker->continueWithAuthorization($event->getCartId()->getValue(), $payPalOrder); } catch (PsCheckoutException $exception) { if ($exception->getCode() === PsCheckoutException::PAYPAL_ORDER_ALREADY_CAPTURED) { - $capture = $payPalOrder['purchase_units'][0]['payments']['captures'][0] ?? null; + $capture = isset($payPalOrder['purchase_units'][0]['payments']['captures'][0]) ? $payPalOrder['purchase_units'][0]['payments']['captures'][0] : null; $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue(), $capture)); return; @@ -172,7 +172,7 @@ public function proceedToPayment(CheckoutCompletedEvent $event) $event->getPayPalOrderId()->getValue() )); $payPalOrder = $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder(); - $capture = $payPalOrder['purchase_units'][0]['payments']['captures'][0] ?? null; + $capture = isset($payPalOrder['purchase_units'][0]['payments']['captures'][0]) ? $payPalOrder['purchase_units'][0]['payments']['captures'][0] : null; } $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue(), $capture)); From 84d9f104691daa49125c162d9c750c8f9fa5a324 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Fri, 8 Nov 2024 12:07:26 +0200 Subject: [PATCH 17/20] Fixed review notes --- controllers/front/payment.php | 12 ++++++----- controllers/front/validate.php | 2 +- .../CheckoutEventSubscriber.php | 20 ++++++++++--------- src/PayPal/Order/Cache/PayPalOrderCache.php | 10 ++++++++++ src/PayPal/Order/PaypalOrderDataProvider.php | 8 +++++--- ...lOrderForCheckoutCompletedQueryHandler.php | 12 ++++++----- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index 758caddec..bc25e2130 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -100,8 +100,10 @@ public function postProcess() $this->redirectToOrderPage(); } + $payPalOrderQuery = new GetPayPalOrderForCheckoutCompletedQuery($orderId); + /** @var GetPayPalOrderForCheckoutCompletedQueryResult $payPalOrderQueryResult */ - $payPalOrderQueryResult = $commandBus->handle(new GetPayPalOrderForCheckoutCompletedQuery($this->paypalOrderId->getValue())); + $payPalOrderQueryResult = $commandBus->handle($payPalOrderQuery); $payPalOrderFromCache = $payPalOrderQueryResult->getPayPalOrder(); if ($payPalOrderFromCache['status'] === 'COMPLETED') { @@ -120,8 +122,8 @@ public function postProcess() $this->redirectTo3DSVerification($payPalOrderFromCache); break; case Card3DSecure::PROCEED: - $commandBus->handle(new CapturePayPalOrderCommand($this->paypalOrderId->getValue(), array_keys($payPalOrderFromCache['payment_source'])[0])); - $payPalOrderFromCache = $payPalOrderCache->get($this->paypalOrderId->getValue()); + $commandBus->handle(new CapturePayPalOrderCommand($orderId, array_keys($payPalOrderFromCache['payment_source'])[0])); + $payPalOrderFromCache = $payPalOrderCache->get($orderId); $this->createOrder($payPalOrderFromCache, $payPalOrder); break; case Card3DSecure::NO_DECISION: @@ -131,8 +133,8 @@ public function postProcess() } if ($payPalOrderFromCache['status'] === 'APPROVED') { - $commandBus->handle(new CapturePayPalOrderCommand($this->paypalOrderId->getValue(), array_keys($payPalOrderFromCache['payment_source'])[0])); - $payPalOrderFromCache = $payPalOrderCache->get($this->paypalOrderId->getValue()); + $commandBus->handle(new CapturePayPalOrderCommand($orderId, array_keys($payPalOrderFromCache['payment_source'])[0])); + $payPalOrderFromCache = $payPalOrderCache->get($orderId); $this->createOrder($payPalOrderFromCache, $payPalOrder); } } catch (Exception $exception) { diff --git a/controllers/front/validate.php b/controllers/front/validate.php index 31b2dba0e..5f3f095ad 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -148,7 +148,7 @@ private function generateResponse() } $response = [ - 'status' => $psCheckoutCart->paypal_status, // Need to change this to get the status from PayPal Order instead? + 'status' => $psCheckoutCart->paypal_status, 'paypalOrderId' => $psCheckoutCart->paypal_order, 'transactionIdentifier' => $paypalOrder && isset($paypalOrder->getOrderPayPal()['purchase_units'][0]['payments']['captures'][0]) ? $paypalOrder->getOrderPayPal()['purchase_units'][0]['payments']['captures'][0]['id'] : null, ]; diff --git a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php index 3ace90834..e30561c4a 100644 --- a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php +++ b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php @@ -122,9 +122,11 @@ public function updatePaymentMethodSelected(CheckoutCompletedEvent $event) */ public function proceedToPayment(CheckoutCompletedEvent $event) { + $payPalOrderId = $event->getPayPalOrderId()->getValue(); + /** @var GetPayPalOrderForCheckoutCompletedQueryResult $getPayPalOrderForCheckoutCompletedQueryResult */ $getPayPalOrderForCheckoutCompletedQueryResult = $this->commandBus->handle(new GetPayPalOrderForCheckoutCompletedQuery( - $event->getPayPalOrderId()->getValue() + $payPalOrderId )); $payPalOrder = $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder(); @@ -134,7 +136,7 @@ public function proceedToPayment(CheckoutCompletedEvent $event) } catch (PsCheckoutException $exception) { if ($exception->getCode() === PsCheckoutException::PAYPAL_ORDER_ALREADY_CAPTURED) { $capture = isset($payPalOrder['purchase_units'][0]['payments']['captures'][0]) ? $payPalOrder['purchase_units'][0]['payments']['captures'][0] : null; - $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue(), $capture)); + $this->commandBus->handle(new CreateOrderCommand($payPalOrderId, $capture)); return; } else { @@ -145,17 +147,17 @@ public function proceedToPayment(CheckoutCompletedEvent $event) try { $this->commandBus->handle( new CapturePayPalOrderCommand( - $event->getPayPalOrderId()->getValue(), + $payPalOrderId, $event->getFundingSource() ) ); } catch (PayPalException $exception) { if ($exception->getCode() === PayPalException::ORDER_NOT_APPROVED) { - $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + $this->commandBus->handle(new CreateOrderCommand($payPalOrderId)); return; } elseif ($exception->getCode() === PayPalException::RESOURCE_NOT_FOUND) { - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($payPalOrderId); if (Validate::isLoadedObject($psCheckoutCart)) { $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; @@ -167,14 +169,14 @@ public function proceedToPayment(CheckoutCompletedEvent $event) if (isset($payPalOrder['purchase_units'][0]['payments']['captures'][0])) { $capture = $payPalOrder['purchase_units'][0]['payments']['captures'][0]; } else { + $payPalOrderQuery = new GetPayPalOrderForCheckoutCompletedQuery($payPalOrderId); + /** @var GetPayPalOrderForCheckoutCompletedQueryResult $getPayPalOrderForCheckoutCompletedQueryResult */ - $getPayPalOrderForCheckoutCompletedQueryResult = $this->commandBus->handle(new GetPayPalOrderForCheckoutCompletedQuery( - $event->getPayPalOrderId()->getValue() - )); + $getPayPalOrderForCheckoutCompletedQueryResult = $this->commandBus->handle($payPalOrderQuery); $payPalOrder = $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder(); $capture = isset($payPalOrder['purchase_units'][0]['payments']['captures'][0]) ? $payPalOrder['purchase_units'][0]['payments']['captures'][0] : null; } - $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue(), $capture)); + $this->commandBus->handle(new CreateOrderCommand($payPalOrderId, $capture)); return; } else { diff --git a/src/PayPal/Order/Cache/PayPalOrderCache.php b/src/PayPal/Order/Cache/PayPalOrderCache.php index 083737ed9..46ddcfd73 100644 --- a/src/PayPal/Order/Cache/PayPalOrderCache.php +++ b/src/PayPal/Order/Cache/PayPalOrderCache.php @@ -22,6 +22,7 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache; use PsCheckoutCart; +use Psr\SimpleCache\InvalidArgumentException; use Symfony\Component\Cache\Simple\ChainCache; class PayPalOrderCache extends ChainCache @@ -36,6 +37,15 @@ class PayPalOrderCache extends ChainCache PsCheckoutCart::STATUS_COMPLETED => 3600, ]; + /** + * @param string $key + * @param $value + * @param int $ttl Time To Live in seconds. Defines how long the value will be stored in the cache. + * + * @return bool + * + * @throws InvalidArgumentException + */ public function set($key, $value, $ttl = null) { if (!$ttl && isset($value['status']) && isset(self::CACHE_TTL[$value['status']])) { diff --git a/src/PayPal/Order/PaypalOrderDataProvider.php b/src/PayPal/Order/PaypalOrderDataProvider.php index 70cfd01ed..344d71fd3 100644 --- a/src/PayPal/Order/PaypalOrderDataProvider.php +++ b/src/PayPal/Order/PaypalOrderDataProvider.php @@ -158,10 +158,12 @@ public function isTokenSaved() public function getPaymentTokenIdentifier() { - if ($this->payPalOrder && isset($this->payPalOrder->getPaymentSource()[$this->payPalOrder->getFundingSource()])) { - $paymentSource = $this->payPalOrder->getPaymentSource()[$this->payPalOrder->getFundingSource()]; + $fundingSource = $this->payPalOrder->getFundingSource(); + + if ($this->payPalOrder && isset($this->payPalOrder->getPaymentSource()[$fundingSource])) { + $paymentSource = $this->payPalOrder->getPaymentSource()[$fundingSource]; - if ($this->payPalOrder->getFundingSource() === 'card') { + if ($fundingSource === 'card') { return (isset($paymentSource['brand']) ? $paymentSource['brand'] : '') . (isset($paymentSource['last_digits']) ? ' *' . $paymentSource['last_digits'] : ''); } else { return isset($paymentSource['email_address']) ? $paymentSource['email_address'] : ''; diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php index 61fd3aae6..53d96d1ca 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php @@ -46,25 +46,27 @@ public function __construct(CacheInterface $orderPayPalCache) public function handle(GetPayPalOrderForCheckoutCompletedQuery $getPayPalOrderQuery) { + $payPalOrderId = $getPayPalOrderQuery->getOrderPayPalId()->getValue(); + /** @var array{id: string, status: string} $order */ - $order = $this->orderPayPalCache->get($getPayPalOrderQuery->getOrderPayPalId()->getValue()); + $order = $this->orderPayPalCache->get($payPalOrderId); if (!empty($order) && in_array($order['status'], ['COMPLETED', 'CANCELED'])) { return new GetPayPalOrderForCheckoutCompletedQueryResult($order); } try { - $orderPayPal = new PaypalOrder($getPayPalOrderQuery->getOrderPayPalId()->getValue()); + $orderPayPal = new PaypalOrder($payPalOrderId); $orderToStoreInCache = !empty($order) ? array_replace_recursive($order, $orderPayPal->getOrder()) : $orderPayPal->getOrder(); - $this->orderPayPalCache->set($getPayPalOrderQuery->getOrderPayPalId()->getValue(), $orderToStoreInCache); + $this->orderPayPalCache->set($payPalOrderId, $orderToStoreInCache); } catch (HttpTimeoutException $exception) { throw $exception; } catch (Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order %s', $getPayPalOrderQuery->getOrderPayPalId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); + throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order %s', $payPalOrderId), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); } if (!$orderPayPal->isLoaded()) { - throw new PayPalOrderException(sprintf('No data for PayPal Order %s', $getPayPalOrderQuery->getOrderPayPalId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); + throw new PayPalOrderException(sprintf('No data for PayPal Order %s', $payPalOrderId), PayPalOrderException::EMPTY_ORDER_DATA); } return new GetPayPalOrderForCheckoutCompletedQueryResult($orderPayPal->getOrder()); From acc1a46b0ce203f2603278438b49cbcfa3dbe3ec Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 12 Nov 2024 11:25:17 +0200 Subject: [PATCH 18/20] CS fixes --- src/PayPal/Order/Cache/PayPalOrderCache.php | 7 +------ src/PayPal/Order/PaypalOrderDataProvider.php | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/PayPal/Order/Cache/PayPalOrderCache.php b/src/PayPal/Order/Cache/PayPalOrderCache.php index 46ddcfd73..031d3b3c4 100644 --- a/src/PayPal/Order/Cache/PayPalOrderCache.php +++ b/src/PayPal/Order/Cache/PayPalOrderCache.php @@ -22,7 +22,6 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache; use PsCheckoutCart; -use Psr\SimpleCache\InvalidArgumentException; use Symfony\Component\Cache\Simple\ChainCache; class PayPalOrderCache extends ChainCache @@ -38,13 +37,9 @@ class PayPalOrderCache extends ChainCache ]; /** - * @param string $key - * @param $value - * @param int $ttl Time To Live in seconds. Defines how long the value will be stored in the cache. + * {@inheritdoc} * * @return bool - * - * @throws InvalidArgumentException */ public function set($key, $value, $ttl = null) { diff --git a/src/PayPal/Order/PaypalOrderDataProvider.php b/src/PayPal/Order/PaypalOrderDataProvider.php index 344d71fd3..b2c0fc1aa 100644 --- a/src/PayPal/Order/PaypalOrderDataProvider.php +++ b/src/PayPal/Order/PaypalOrderDataProvider.php @@ -158,15 +158,16 @@ public function isTokenSaved() public function getPaymentTokenIdentifier() { - $fundingSource = $this->payPalOrder->getFundingSource(); - - if ($this->payPalOrder && isset($this->payPalOrder->getPaymentSource()[$fundingSource])) { - $paymentSource = $this->payPalOrder->getPaymentSource()[$fundingSource]; - - if ($fundingSource === 'card') { - return (isset($paymentSource['brand']) ? $paymentSource['brand'] : '') . (isset($paymentSource['last_digits']) ? ' *' . $paymentSource['last_digits'] : ''); - } else { - return isset($paymentSource['email_address']) ? $paymentSource['email_address'] : ''; + if ($this->payPalOrder) { + $fundingSource = $this->payPalOrder->getFundingSource(); + if (isset($this->payPalOrder->getPaymentSource()[$fundingSource])) { + $paymentSource = $this->payPalOrder->getPaymentSource()[$fundingSource]; + + if ($fundingSource === 'card') { + return (isset($paymentSource['brand']) ? $paymentSource['brand'] : '') . (isset($paymentSource['last_digits']) ? ' *' . $paymentSource['last_digits'] : ''); + } else { + return isset($paymentSource['email_address']) ? $paymentSource['email_address'] : ''; + } } } From 54102b4de7747647e226717b80723ff53af212e6 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 12 Nov 2024 15:16:08 +0200 Subject: [PATCH 19/20] Fixed redirect to guest customer --- controllers/front/payment.php | 50 ++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index bc25e2130..f2c8575b8 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -49,6 +49,11 @@ class Ps_CheckoutPaymentModuleFrontController extends AbstractFrontController */ private $paypalOrderId; + /** + * @var CommandBusInterface + */ + private $commandBus; + public function checkAccess() { return $this->context->customer && $this->context->cart; @@ -80,10 +85,10 @@ public function postProcess() try { $this->paypalOrderId = new PayPalOrderId($orderId); + $this->commandBus = $this->module->getService('ps_checkout.bus.command'); + /** @var PayPalOrderRepository $payPalOrderRepository */ $payPalOrderRepository = $this->module->getService(PayPalOrderRepository::class); - /** @var CommandBusInterface $commandBus */ - $commandBus = $this->module->getService('ps_checkout.bus.command'); /** @var Psr\SimpleCache\CacheInterface $payPalOrderCache */ $payPalOrderCache = $this->module->getService('ps_checkout.cache.paypal.order'); @@ -93,17 +98,21 @@ public function postProcess() $orders->where('id_cart', '=', $payPalOrder->getIdCart()); if ($orders->count()) { - $this->redirectToOrderHistoryPage(); + if ($this->context->customer->isLogged()) { + $this->redirectToOrderHistoryPage(); + } else { + $payPalOrderQueryResult = $this->getPayPalOrder($orderId); + $payPalOrderFromCache = $payPalOrderQueryResult->getPayPalOrder(); + + $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]['id'], $payPalOrderFromCache['status']); + } } if ($payPalOrder->getIdCart() !== $this->context->cart->id) { $this->redirectToOrderPage(); } - $payPalOrderQuery = new GetPayPalOrderForCheckoutCompletedQuery($orderId); - - /** @var GetPayPalOrderForCheckoutCompletedQueryResult $payPalOrderQueryResult */ - $payPalOrderQueryResult = $commandBus->handle($payPalOrderQuery); + $payPalOrderQueryResult = $this->getPayPalOrder($orderId); $payPalOrderFromCache = $payPalOrderQueryResult->getPayPalOrder(); if ($payPalOrderFromCache['status'] === 'COMPLETED') { @@ -122,7 +131,7 @@ public function postProcess() $this->redirectTo3DSVerification($payPalOrderFromCache); break; case Card3DSecure::PROCEED: - $commandBus->handle(new CapturePayPalOrderCommand($orderId, array_keys($payPalOrderFromCache['payment_source'])[0])); + $this->commandBus->handle(new CapturePayPalOrderCommand($orderId, array_keys($payPalOrderFromCache['payment_source'])[0])); $payPalOrderFromCache = $payPalOrderCache->get($orderId); $this->createOrder($payPalOrderFromCache, $payPalOrder); break; @@ -133,7 +142,7 @@ public function postProcess() } if ($payPalOrderFromCache['status'] === 'APPROVED') { - $commandBus->handle(new CapturePayPalOrderCommand($orderId, array_keys($payPalOrderFromCache['payment_source'])[0])); + $this->commandBus->handle(new CapturePayPalOrderCommand($orderId, array_keys($payPalOrderFromCache['payment_source'])[0])); $payPalOrderFromCache = $payPalOrderCache->get($orderId); $this->createOrder($payPalOrderFromCache, $payPalOrder); } @@ -154,17 +163,14 @@ public function postProcess() */ private function createOrder($payPalOrderFromCache, $payPalOrder) { - /** @var CommandBusInterface $commandBus */ - $commandBus = $this->module->getService('ps_checkout.bus.command'); - - $capture = $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0] ?? null; - $commandBus->handle(new CreateOrderCommand($payPalOrder->getId()->getValue(), $capture)); + $capture = isset($payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]) ? $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0] : null; + $this->commandBus->handle(new CreateOrderCommand($payPalOrder->getId()->getValue(), $capture)); if ($payPalOrder->getPaymentTokenId() && $payPalOrder->checkCustomerIntent(PayPalOrder::CUSTOMER_INTENT_FAVORITE)) { /** @var PaymentTokenRepository $paymentTokenRepository */ $paymentTokenRepository = $this->module->getService(PaymentTokenRepository::class); $paymentTokenRepository->setTokenFavorite($payPalOrder->getPaymentTokenId()); } - $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $capture['id'], $payPalOrderFromCache['status']); + $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $capture ? $capture['id'] : null, $payPalOrderFromCache['status']); } /** @@ -228,6 +234,20 @@ private function redirectToOrderConfirmationPage($cartId, $captureId, $payPalOrd } } + /** + * @param string $orderId + * + * @return GetPayPalOrderForCheckoutCompletedQueryResult + * + * @throws PayPalOrderException + */ + private function getPayPalOrder($orderId) + { + $payPalOrderQuery = new GetPayPalOrderForCheckoutCompletedQuery($orderId); + + return $this->commandBus->handle($payPalOrderQuery); + } + private function redirectToOrderHistoryPage() { Tools::redirect($this->context->link->getPageLink('history')); From b444d3627b4fd2a71819a29a25fe176d0231b04a Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 12 Nov 2024 17:00:43 +0200 Subject: [PATCH 20/20] Fix for edge case when PayPal order update fails --- .../Order/EventSubscriber/PayPalOrderEventSubscriber.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index cd03d5b44..4d5954955 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -308,10 +308,11 @@ public function updateCache(PayPalOrderEvent $event) $this->orderPayPalCache->set($event->getOrderPayPalId()->getValue(), $newOrderPayPal); } - public function updatePayPalOrder(PayPalOrderEvent $event) + public function updatePayPalOrder(PayPalOrderUpdatedEvent $event) { $this->commandBus->handle(new SavePayPalOrderCommand( - $event->getOrderPayPal() + $event->getOrderPayPal(), + $event->getCartId() )); }