From af9f9a9b4eab9ce41ff34120780646fd01bafb6a Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Fri, 13 Sep 2024 17:30:54 +0300 Subject: [PATCH 01/33] Added negative testing headers --- config/http-clients.yml | 1 + src/Http/MaaslandHttpClient.php | 58 +++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/config/http-clients.yml b/config/http-clients.yml index 2ff7954f2..9e6a8ad1e 100644 --- a/config/http-clients.yml +++ b/config/http-clients.yml @@ -4,6 +4,7 @@ services: public: true arguments: - "@ps_checkout.http.client" + - '@PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration' PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient: class: 'PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient' diff --git a/src/Http/MaaslandHttpClient.php b/src/Http/MaaslandHttpClient.php index 632fd93f8..5f1dee6bf 100644 --- a/src/Http/MaaslandHttpClient.php +++ b/src/Http/MaaslandHttpClient.php @@ -25,6 +25,7 @@ use Http\Client\Exception\NetworkException; use Http\Client\Exception\RequestException; use Http\Client\Exception\TransferException; +use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration; use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; use PrestaShop\Module\PrestashopCheckout\PayPalError; @@ -37,15 +38,20 @@ class MaaslandHttpClient implements HttpClientInterface * @var HttpClientInterface */ private $httpClient; + /** + * @var PrestaShopConfiguration + */ + private $configuration; - public function __construct(HttpClientInterface $httpClient) + public function __construct(HttpClientInterface $httpClient, PrestaShopConfiguration $configuration) { $this->httpClient = $httpClient; + $this->configuration = $configuration; } /** * @param array $payload - * @param array $options + * @param array $headers * * @return ResponseInterface * @@ -56,14 +62,18 @@ public function __construct(HttpClientInterface $httpClient) * @throws PayPalException * @throws HttpTimeoutException */ - public function createOrder(array $payload, array $options = []) + public function createOrder(array $payload, array $headers = []) { - return $this->sendRequest(new Request('POST', '/payments/order/create', $options, json_encode($payload))); + if ($ntHeader = $this->configuration->get('PS_CHECKOUT_NT_CREATE_ORDER')) { + $headers['NT-PayPalOrderCreate'] = $ntHeader; + } + + return $this->sendRequest(new Request('POST', '/payments/order/create', $headers, json_encode($payload))); } /** * @param array $payload - * @param array $options + * @param array $headers * * @return ResponseInterface * @@ -74,14 +84,18 @@ public function createOrder(array $payload, array $options = []) * @throws PayPalException * @throws HttpTimeoutException */ - public function updateOrder(array $payload, array $options = []) + public function updateOrder(array $payload, array $headers = []) { - return $this->sendRequest(new Request('POST', '/payments/order/update', $options, json_encode($payload))); + if ($ntHeader = $this->configuration->get('PS_CHECKOUT_NT_UPDATE_ORDER')) { + $headers['NT-PayPalOrderUpdate'] = $ntHeader; + } + + return $this->sendRequest(new Request('POST', '/payments/order/update', $headers, json_encode($payload))); } /** * @param array $payload - * @param array $options + * @param array $headers * * @return ResponseInterface * @@ -92,14 +106,18 @@ public function updateOrder(array $payload, array $options = []) * @throws PayPalException * @throws HttpTimeoutException */ - public function fetchOrder(array $payload, array $options = []) + public function fetchOrder(array $payload, array $headers = []) { - return $this->sendRequest(new Request('POST', '/payments/order/fetch', $options, json_encode($payload))); + if ($ntHeader = $this->configuration->get('PS_CHECKOUT_NT_FETCH_ORDER')) { + $headers['NT-PayPalOrderFetch'] = $ntHeader; + } + + return $this->sendRequest(new Request('POST', '/payments/order/fetch', $headers, json_encode($payload))); } /** * @param array $payload - * @param array $options + * @param array $headers * * @return ResponseInterface * @@ -110,14 +128,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 $headers = []) { - return $this->sendRequest(new Request('POST', '/payments/order/capture', $options, json_encode($payload))); + if ($ntHeader = $this->configuration->get('PS_CHECKOUT_NT_CAPTURE_ORDER')) { + $headers['NT-PayPalOrderCapture'] = $ntHeader; + } + + return $this->sendRequest(new Request('POST', '/payments/order/capture', $headers, json_encode($payload))); } /** * @param array $payload - * @param array $options + * @param array $headers * * @return ResponseInterface * @@ -128,9 +150,9 @@ public function captureOrder(array $payload, array $options = []) * @throws PayPalException * @throws HttpTimeoutException */ - public function refundOrder(array $payload, array $options = []) + public function refundOrder(array $payload, array $headers = []) { - return $this->sendRequest(new Request('POST', '/payments/order/refund', $options, json_encode($payload))); + return $this->sendRequest(new Request('POST', '/payments/order/refund', $headers, json_encode($payload))); } /** @@ -202,9 +224,9 @@ private function extractMessage(array $body) * * @return array */ - public function getShopSignature(array $payload, array $options = []) + public function getShopSignature(array $payload, array $headers = []) { - $response = $this->sendRequest(new Request('POST', '/payments/shop/verify_webhook_signature', $options, json_encode($payload))); + $response = $this->sendRequest(new Request('POST', '/payments/shop/verify_webhook_signature', $headers, json_encode($payload))); return json_decode($response->getBody(), true); } From e4ad9a99565e04399fcd6db23c3c8f2d0e987d80 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 9 Oct 2024 14:49:23 +0300 Subject: [PATCH 02/33] 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 70232f4e132c9d2de1449da9acb4090e1bd0c3e2 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Mon, 14 Oct 2024 15:06:09 +0300 Subject: [PATCH 03/33] 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 bbef44f19597259a490df821d8484e17e1223d7d Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Mon, 14 Oct 2024 17:38:09 +0300 Subject: [PATCH 04/33] 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 bc9da5a7e10a0ebfe5a81ea280ebe5c976ccf144 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 15 Oct 2024 17:21:22 +0300 Subject: [PATCH 05/33] 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 632fd93f8..a855e2378 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 ac7709011a957ccc707a75b970ba48446c8c86c8 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 16 Oct 2024 10:42:35 +0300 Subject: [PATCH 06/33] 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 a855e2378..f0d8f2682 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 84eec510b247b91621e952bb5253b5977cceea53 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 23 Oct 2024 17:19:36 +0300 Subject: [PATCH 07/33] 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 f0d8f2682..632fd93f8 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 fbe47b8dcf6ece19ffea838a0b23aa13567e70c9 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 23 Oct 2024 17:30:06 +0300 Subject: [PATCH 08/33] 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 5e911aa6ca7249cfc70e0cd3b8966827423c99a1 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 23 Oct 2024 17:33:24 +0300 Subject: [PATCH 09/33] 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 17d1d0d587230f9675c508d874a8da0b68eff9bf Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 23 Oct 2024 17:44:24 +0300 Subject: [PATCH 10/33] CS fix --- ps_checkout.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ps_checkout.php b/ps_checkout.php index 400dbab29..96a2cb171 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -46,7 +46,7 @@ class Ps_checkout extends PaymentModule 'actionObjectOrderPaymentUpdateAfter', 'displayPaymentReturn', 'displayOrderDetail', - 'moduleRoutes' + 'moduleRoutes', ]; /** @@ -1803,9 +1803,9 @@ public function hookModuleRoutes() 'params' => [ 'fc' => 'module', 'module' => 'ps_checkout', - 'action' => 'getDomainAssociation' + 'action' => 'getDomainAssociation', ], - ] + ], ]; } } From cc3d1ad491c69eedc30d6fbef78237c38614aaef Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Fri, 25 Oct 2024 13:55:12 +0300 Subject: [PATCH 11/33] 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 65b099531ca4a0bde463f326258f00ebb4472c05 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 29 Oct 2024 11:15:23 +0200 Subject: [PATCH 12/33] 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 d1014d29f..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 96a2cb171..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.0'; + 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.0'; + $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 059a4d3b932442a33b94035218901415ef3e238e Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 30 Oct 2024 11:19:22 +0200 Subject: [PATCH 13/33] Updated PayPal order fetching to check date_upd --- config/query-handlers.yml | 1 + ...yPalOrderForCheckoutCompletedQueryHandler.php | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/config/query-handlers.yml b/config/query-handlers.yml index 4984dceaa..b412c5eb7 100644 --- a/config/query-handlers.yml +++ b/config/query-handlers.yml @@ -60,6 +60,7 @@ services: public: true arguments: - "@ps_checkout.cache.paypal.order" + - '@PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository' PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForOrderConfirmationQueryHandler: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForOrderConfirmationQueryHandler' diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php index 5c5862591..534a86cfb 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php @@ -27,6 +27,8 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQuery; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQueryResult; use PrestaShop\Module\PrestashopCheckout\PaypalOrder; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PsCheckoutCart; use Psr\SimpleCache\CacheInterface; /** @@ -38,18 +40,28 @@ class GetPayPalOrderForCheckoutCompletedQueryHandler * @var CacheInterface */ private $orderPayPalCache; + /** + * @var PsCheckoutCartRepository + */ + private $psCheckoutCartRepository; - public function __construct(CacheInterface $orderPayPalCache) + public function __construct(CacheInterface $orderPayPalCache, PsCheckoutCartRepository $psCheckoutCartRepository) { $this->orderPayPalCache = $orderPayPalCache; + $this->psCheckoutCartRepository = $psCheckoutCartRepository; } public function handle(GetPayPalOrderForCheckoutCompletedQuery $getPayPalOrderQuery) { /** @var array{id: string, status: string} $order */ $order = $this->orderPayPalCache->get($getPayPalOrderQuery->getOrderPayPalId()->getValue()); + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($getPayPalOrderQuery->getOrderPayPalId()->getValue()); + + $psCheckoutCartDateUpdated = new \DateTime($psCheckoutCart->date_upd); + $currentDateTime = new \DateTime(); + $interval = $currentDateTime->getTimestamp() - $psCheckoutCartDateUpdated->getTimestamp(); - if (!empty($order) && !in_array($order['status'], ['APPROVED', 'COMPLETED'])) { + if (!empty($order) && ($psCheckoutCart->paypal_status === $order['status'] && $interval < 30 || in_array($order['status'], [PsCheckoutCart::STATUS_COMPLETED, PsCheckoutCart::STATUS_CANCELED]))) { return new GetPayPalOrderForCheckoutCompletedQueryResult($order); } From 08c775fb92153176afa0c43ff32887fba5bba7fc Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Wed, 9 Oct 2024 14:49:23 +0300 Subject: [PATCH 14/33] 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 15/33] 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 16/33] 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 17/33] 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 18/33] 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 19/33] 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 20/33] 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 21/33] 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 22/33] 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 23/33] 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 24/33] 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 25/33] 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 26/33] 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 27/33] 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 28/33] 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 29/33] 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 30/33] 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 31/33] 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 4dfa5cd8b3b72f7075a47b95963f770fce35d857 Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 12 Nov 2024 15:16:08 +0200 Subject: [PATCH 32/33] Fixed redirect to guest customer --- controllers/front/payment.php | 49 ++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/controllers/front/payment.php b/controllers/front/payment.php index bc25e2130..c29323baf 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,19 @@ 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 ee501e2e70c6d38299f23df0952bda4a317c90fd Mon Sep 17 00:00:00 2001 From: L3RAZ Date: Tue, 12 Nov 2024 16:58:03 +0200 Subject: [PATCH 33/33] Fix for strange behavior when PayPal order creation fails --- .../Order/EventSubscriber/PayPalOrderEventSubscriber.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index cd03d5b44..496964451 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -311,7 +311,8 @@ public function updateCache(PayPalOrderEvent $event) public function updatePayPalOrder(PayPalOrderEvent $event) { $this->commandBus->handle(new SavePayPalOrderCommand( - $event->getOrderPayPal() + $event->getOrderPayPal(), + $event->getCartId() )); }