Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 89 additions & 109 deletions Controller/Api/Push.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<?php

/**
* Copyright © Klarna Bank AB (publ)
*
* For the full copyright and license information, please view the NOTICE
* and LICENSE files that were distributed with this source code.
*/

declare(strict_types=1);

namespace Klarna\Kco\Controller\Api;
Expand All @@ -21,12 +23,10 @@
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\DataObjectFactory;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Exception\LocalizedException;
use Klarna\Base\Controller\CsrfAbstract;
use Magento\Framework\DataObject;
use Magento\Quote\Model\CartLockedException;

/**
Expand All @@ -41,30 +41,32 @@ class Push extends CsrfAbstract implements HttpPostActionInterface
* @var LoggerInterface
*/
private $logger;

/**
* @var CheckoutOrder
*/
private $checkoutOrder;
/**
* @var DataObjectFactory
*/
private $dataObjectFactory;

/**
* @var Result
*/
private $result;

/**
* @var Logger
*/
private $apiLogger;

/**
* @var Container
*/
private $container;

/**
* @var WorkflowProvider
*/
private WorkflowProvider $workflowProvider;

/**
* @var RequestInterface
*/
Expand All @@ -79,12 +81,11 @@ class Push extends CsrfAbstract implements HttpPostActionInterface
* @param Container $container
* @param WorkflowProvider $workflowProvider
* @param RequestInterface $request
* @codeCoverageIgnore
*/
public function __construct(
LoggerInterface $logger,
CheckoutOrder $checkoutOrder,
DataObjectFactory $dataObjectFactory,
DataObjectFactory $dataObjectFactory, // To be removed as unused in next major release!
Result $result,
Logger $apiLogger,
Container $container,
Expand All @@ -93,7 +94,6 @@ public function __construct(
) {
$this->logger = $logger;
$this->checkoutOrder = $checkoutOrder;
$this->dataObjectFactory = $dataObjectFactory;
$this->result = $result;
$this->apiLogger = $apiLogger;
$this->container = $container;
Expand All @@ -104,141 +104,121 @@ public function __construct(
/**
* Performing the push action logic
*
* @return Json|ResultInterface
* @throws KlarnaException
* @throws LocalizedException
* phpcs:disable Commenting.EmptyCatchComment
* @inheritDoc
*/
public function execute()
{
$klarnaOrderId = $this->request->getParam('id');
$this->workflowProvider->setKlarnaOrderId($klarnaOrderId);

try {
$magentoOrder = $this->workflowProvider->getMagentoOrder();
// phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
} catch (KlarnaException $e) {
$this->logger->debug(
'No order is created because a payment method is selected with an external redirect.'
);
// We do nothing since when using for example the IDEAL payment no order is created at this point
}

$this->logger->debug('Push: klarna order id: ' . $klarnaOrderId);

try {
$this->checkoutOrder->updateOrderState($klarnaOrderId);
} catch (KlarnaException $e) {
return $this->createOrder($klarnaOrderId);
} catch (LocalizedException $e) {
$this->apiLogger->logCallbackException(
$this->container,
ApiInterface::ACTIONS['push'],
$this->request,
$e
);
return $this->cancelKlarnaOrder($klarnaOrderId, $e);
$createOrderStatus = $this->canCreateOrder() ? $this->createOrder($klarnaOrderId) : true;
if ($createOrderStatus instanceof Json) {
return $createOrderStatus;
}

$magentoOrder = $this->checkoutOrder->getMagentoOrder();
$this->container->setIncrementId($magentoOrder->getIncrementId());
$this->container->setService(ServiceInterface::SERVICE_KCO);
$this->apiLogger->logCallback($this->container, ApiInterface::ACTIONS['push'], $this->request, []);

return $this->getSuccessResponse();
return $this->updateOrderState($klarnaOrderId);
}

/**
* Canceling the Klarna order
*
* @param string $klarnaOrderId
* @param LocalizedException $e
* @return Json
* @throws KlarnaException
* @return bool
*/
private function cancelKlarnaOrder(string $klarnaOrderId, LocalizedException $e): Json
private function canCreateOrder(): bool
{
$this->logger->critical('Push: Cancelling order. Error occured: ' . $e->getMessage());
$responseCodeObject = $this->getFailureResponseObject(500);
$this->checkoutOrder->cancelKlarnaOrder($klarnaOrderId, $e->getMessage());

return $this->result->getJsonResult(
(int)$responseCodeObject->getResponseCode(),
['error' => $e->getMessage()]
);
}
// TODO: We shouldn't need to rely on exception + it can result in false positives, let's eventually add
// possibility to figure out existence of these instances by something more simplified

/**
* Getting back the success response
*
* @return Json
*/
private function getSuccessResponse(): Json
{
$this->logger->debug('Push: success');
return $this->result->getJsonResult(200);
}

/**
* Getting back the failure response object with the given response code
*
* @param int $responseCode
* @return DataObject
*/
private function getFailureResponseObject(int $responseCode): DataObject
{
$object = $this->dataObjectFactory->create();
$object->setResponseCode($responseCode);
try {
$this->workflowProvider->getMagentoOrder();
$this->workflowProvider->getKlarnaOrder();

return $object;
return false;
} catch (KlarnaException $exception) {
return true;
}
}

/**
* @param string $klarnaOrderId
* @param CartLockedException $e
*
* @return Json
* @return Json|true
*/
private function getCartLockedResponse(string $klarnaOrderId, CartLockedException $e): Json
private function createOrder(string $klarnaOrderId)
{
$this->logger->debug(
'Push: Retry order ' . $klarnaOrderId . ' - Exception: ' . $e->getMessage()
);
return $this->result->getJsonResult(
503,
['error' => $e->getMessage()]
);
}
$this->logger->debug('Push: Attempting to create order by id ' . $klarnaOrderId);

/**
* Create order in Magento if it doesn't currently exist.
*
* This is the case when the customer selected a payment gateway method (for example "iDeal").
*
* @param string $klarnaOrderId
* @return Json
* @throws KlarnaException
*/
private function createOrder(string $klarnaOrderId): Json
{
try {
$this->checkoutOrder->createMagentoOrder($klarnaOrderId);
$this->checkoutOrder->sendCustomerMail();
$this->checkoutOrder->updateOrderState($klarnaOrderId);
} catch (AlreadyExistsException $e) {
} catch (AlreadyExistsException $exception) {
$this->logger->debug('Push: Order already exists for this Klarna order id: ' . $klarnaOrderId);
} catch (CartLockedException $e) {
return $this->getCartLockedResponse($klarnaOrderId, $e);
} catch (CartLockedException $exception) {
$this->logger->debug('Push: Retry order ' . $klarnaOrderId . ' - Exception: ' . $exception->getMessage());

return $this->result->getJsonResult(
503,
['error' => $exception->getMessage()]
);
} catch (LocalizedException $e) {
// Before cancelling, check if a concurrent push already created the order successfully.
// If the Magento order exists, return success to avoid voiding a valid Klarna authorization.
$magentoOrder = $this->checkoutOrder->getMagentoOrder();
if ($magentoOrder !== null && $magentoOrder->getId()) {
$this->logger->debug('Push: Order already created by concurrent request: ' . $klarnaOrderId);
return $this->getSuccessResponse();

return true;
}
return $this->cancelKlarnaOrder($klarnaOrderId, $e);

$this->logger->debug('Push: Order creation failed: ' . $e->getMessage());

$this->apiLogger->logCallbackException(
$this->container,
ApiInterface::ACTIONS['push'],
$this->request,
$e
);

return $this->result->getJsonResult(
500,
['error' => 'Failed to create order']
);
}

$this->logger->debug('Push: Order created successfully by id ' . $klarnaOrderId);

return true;
}

/**
* @param string $klarnaOrderId
*
* @return Json
*/
private function updateOrderState(string $klarnaOrderId): Json
{
try {
$this->checkoutOrder->updateOrderState($klarnaOrderId);
} catch (LocalizedException $e) {
$this->apiLogger->logCallbackException(
$this->container,
ApiInterface::ACTIONS['push'],
$this->request,
$e
);

return $this->result->getJsonResult(
500,
['error' => 'Failed to update order state']
);
}
return $this->getSuccessResponse();

$magentoOrder = $this->checkoutOrder->getMagentoOrder();
$this->container->setIncrementId($magentoOrder->getIncrementId());
$this->container->setService(ServiceInterface::SERVICE_KCO);
$this->apiLogger->logCallback($this->container, ApiInterface::ACTIONS['push'], $this->request, []);

$this->logger->debug('Push: success');

return $this->result->getJsonResult(200);
}
}
Loading