diff --git a/Controller/Return/Index.php b/Controller/Return/Index.php
index c0a52bdf5..a679a6557 100755
--- a/Controller/Return/Index.php
+++ b/Controller/Return/Index.php
@@ -12,25 +12,19 @@
// phpcs:ignore
namespace Adyen\Payment\Controller\Return;
-use Adyen\AdyenException;
-use Adyen\Payment\Helper\Data;
-use Adyen\Payment\Helper\Idempotency;
+use Adyen\Payment\Helper\PaymentResponseHandler;
+use Adyen\Payment\Helper\PaymentsDetails;
use Adyen\Payment\Helper\Quote;
use Adyen\Payment\Helper\Config;
-use Adyen\Payment\Helper\StateData;
-use Adyen\Payment\Helper\Vault;
use Adyen\Payment\Logger\AdyenLogger;
-use Adyen\Payment\Model\Notification;
-use Adyen\Service\Validator\DataArrayValidator;
+use Exception;
use Magento\Checkout\Model\Session;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Sales\Model\Order;
-use Magento\Sales\Model\Order\Status\HistoryFactory;
use Magento\Sales\Model\OrderFactory;
-use Magento\Sales\Model\ResourceModel\Order as OrderResource;
use Magento\Store\Model\StoreManagerInterface;
class Index extends Action
@@ -59,73 +53,53 @@ class Index extends Action
protected OrderFactory $orderFactory;
protected Config $configHelper;
protected Order $order;
- protected HistoryFactory $orderHistoryFactory;
protected Session $session;
protected AdyenLogger $adyenLogger;
protected StoreManagerInterface $storeManager;
private Quote $quoteHelper;
private Order\Payment $payment;
- private Vault $vaultHelper;
- private OrderResource $orderResourceModel;
- private StateData $stateDataHelper;
- private Data $adyenDataHelper;
- private OrderRepositoryInterface $orderRepository;
- private Idempotency $idempotencyHelper;
+ private PaymentsDetails $paymentsDetailsHelper;
+ private PaymentResponseHandler $paymentResponseHandler;
public function __construct(
Context $context,
OrderFactory $orderFactory,
- HistoryFactory $orderHistoryFactory,
Session $session,
AdyenLogger $adyenLogger,
StoreManagerInterface $storeManager,
Quote $quoteHelper,
- Vault $vaultHelper,
- OrderResource $orderResourceModel,
- StateData $stateDataHelper,
- Data $adyenDataHelper,
- OrderRepositoryInterface $orderRepository,
- Idempotency $idempotencyHelper,
- Config $configHelper
+ Config $configHelper,
+ PaymentsDetails $paymentsDetailsHelper,
+ PaymentResponseHandler $paymentResponseHandler
) {
parent::__construct($context);
- $this->adyenDataHelper = $adyenDataHelper;
$this->orderFactory = $orderFactory;
- $this->orderHistoryFactory = $orderHistoryFactory;
$this->session = $session;
$this->adyenLogger = $adyenLogger;
$this->storeManager = $storeManager;
$this->quoteHelper = $quoteHelper;
- $this->vaultHelper = $vaultHelper;
- $this->orderResourceModel = $orderResourceModel;
- $this->stateDataHelper = $stateDataHelper;
- $this->orderRepository = $orderRepository;
$this->configHelper = $configHelper;
- $this->idempotencyHelper = $idempotencyHelper;
+ $this->paymentsDetailsHelper = $paymentsDetailsHelper;
+ $this->paymentResponseHandler = $paymentResponseHandler;
}
+ /**
+ * @throws NoSuchEntityException
+ * @throws LocalizedException
+ */
public function execute(): void
{
- $result = false;
-
// Receive all params as this could be a GET or POST request
- $response = $this->getRequest()->getParams();
+ $redirectResponse = $this->getRequest()->getParams();
- if ($response) {
- $result = $this->validateResponse($response);
- $order = $this->order;
- $additionalInformation = $order->getPayment()->getAdditionalInformation();
- $resultCode = isset($response['resultCode']) ? $response['resultCode'] : null;
- $paymentBrandCode = $additionalInformation['brand_code'] ?? null;
- if ($resultCode === 'cancelled' && $paymentBrandCode === 'svs') {
- $this->adyenDataHelper->cancelOrder($order);
- }
+ if ($redirectResponse) {
+ $result = $this->validateRedirectResponse($redirectResponse);
// Adjust the success path, fail path, and restore quote based on if it is a multishipping quote
if (
- !empty($response['merchantReference']) &&
- $this->quoteHelper->getIsQuoteMultiShippingWithMerchantReference($response['merchantReference'])
+ !empty($redirectResponse['merchantReference']) &&
+ $this->quoteHelper->getIsQuoteMultiShippingWithMerchantReference($redirectResponse['merchantReference'])
) {
$successPath = $failPath = 'multishipping/checkout/success';
$setQuoteAsActive = true;
@@ -135,240 +109,86 @@ public function execute(): void
$failPath = $this->configHelper->getAdyenAbstractConfigData('return_path');
$setQuoteAsActive = false;
}
- } else {
- $this->_redirect($this->configHelper->getAdyenAbstractConfigData('return_path'));
- }
-
- if ($result) {
- $session = $this->session;
- $session->getQuote()->setIsActive($setQuoteAsActive)->save();
- $paymentAction = $this->order->getPayment()->getAdditionalInformation('action');
- $brandCode = $this->order->getPayment()->getAdditionalInformation('brand_code');
- $resultCode = $this->order->getPayment()->getAdditionalInformation('resultCode');
-
- // Prevent action component to redirect page again after returning to the shop
- if (($brandCode == self::BRAND_CODE_DOTPAY && $resultCode == self::RESULT_CODE_RECEIVED) ||
- (isset($paymentAction) && $paymentAction['type'] === 'redirect')
- ) {
- $this->payment->unsAdditionalInformation('action');
- $this->order->save();
- }
+ if ($result) {
+ $this->session->getQuote()->setIsActive($setQuoteAsActive)->save();
- // Add OrderIncrementId to redirect parameters for headless support.
- $redirectParams = $this->configHelper->getAdyenAbstractConfigData('custom_success_redirect_path')
- ? ['_query' => ['utm_nooverride' => '1', 'order_increment_id' => $this->order->getIncrementId()]]
- : ['_query' => ['utm_nooverride' => '1']];
- $this->_redirect($successPath, $redirectParams);
- } else {
- $this->adyenLogger->addAdyenResult(
- sprintf(
- 'Payment for order %s was unsuccessful, ' .
- 'it will be cancelled when the OFFER_CLOSED notification has been processed.',
- $this->order->getIncrementId()
- )
- );
- $this->replaceCart($response);
- $this->_redirect($failPath, ['_query' => ['utm_nooverride' => '1']]);
- }
- }
+ // Add OrderIncrementId to redirect parameters for headless support.
+ $redirectParams = $this->configHelper->getAdyenAbstractConfigData('custom_success_redirect_path')
+ ? ['_query' => ['utm_nooverride' => '1', 'order_increment_id' => $this->order->getIncrementId()]]
+ : ['_query' => ['utm_nooverride' => '1']];
+ $this->_redirect($successPath, $redirectParams);
+ } else {
+ $this->adyenLogger->addAdyenResult(
+ sprintf(
+ 'Payment for order %s was unsuccessful, ' .
+ 'it will be cancelled when the OFFER_CLOSED notification has been processed.',
+ isset($this->order) ? $this->order->getIncrementId() :
+ ($redirectResponse['merchantReference'] ?? null)
+ )
+ );
- protected function replaceCart(array $response): void
- {
- $this->session->restoreQuote();
+ $this->session->restoreQuote();
+ $this->messageManager->addError(__('Your payment failed, Please try again later'));
- if (isset($response['authResult']) && $response['authResult'] == \Adyen\Payment\Model\Notification::CANCELLED) {
- $this->messageManager->addError(__('You have cancelled the order. Please try again'));
+ $this->_redirect($failPath, ['_query' => ['utm_nooverride' => '1']]);
+ }
} else {
- $this->messageManager->addError(__('Your payment failed, Please try again later'));
+ $this->_redirect($this->configHelper->getAdyenAbstractConfigData('return_path'));
}
}
- protected function validateResponse(array $response): bool
+ /**
+ * @throws LocalizedException
+ * @throws Exception
+ */
+ protected function validateRedirectResponse(array $redirectResponse): bool
{
- $this->adyenLogger->addAdyenResult('Processing ResultUrl');
+ $this->adyenLogger->addAdyenResult('Processing redirect response');
+ $order = $this->getOrder($redirectResponse['merchantReference'] ?? null);
- // send the payload verification payment\details request to validate the response
- $response = $this->validatePayloadAndReturnResponse($response);
-
- $order = $this->order;
-
- $this->_eventManager->dispatch(
- 'adyen_payment_process_resulturl_before',
- [
- 'order' => $order,
- 'adyen_response' => $response
- ]
- );
-
- // Save PSP reference from the response
- if (!empty($response['pspReference'])) {
- $this->payment->setAdditionalInformation('pspReference', $response['pspReference']);
- }
-
- // Handle recurring details
- $this->vaultHelper->handlePaymentResponseRecurringDetails($this->payment, $response);
-
- // Save donation token if available in the response
- if (!empty($response['donationToken'])) {
- $this->payment->setAdditionalInformation('donationToken', $response['donationToken']);
+ try {
+ // Make paymentsDetails call to validate the payment
+ $request["details"] = $redirectResponse;
+ $paymentsDetailsResponse = $this->paymentsDetailsHelper->initiatePaymentDetails($order, $request);
+ } catch (Exception $e) {
+ $paymentsDetailsResponse['error'] = $e->getMessage();
}
- // update the order
- $result = $this->validateUpdateOrder($order, $response);
-
- $this->_eventManager->dispatch(
- 'adyen_payment_process_resulturl_after',
- [
- 'order' => $order,
- 'adyen_response' => $response
- ]
- );
-
- return $result;
- }
-
- protected function validateUpdateOrder(Order $order, array $response): bool
- {
$result = false;
- if (!empty($response['authResult'])) {
- $authResult = $response['authResult'];
- } elseif (!empty($response['resultCode'])) {
- $authResult = $response['resultCode'];
- } else {
- // In case the result is unknown we log the request and don't update the history
- $this->adyenLogger->error("Unexpected result query parameter. Response: " . json_encode($response));
-
- return $result;
- }
-
- $this->adyenLogger->addAdyenResult('Updating the order');
-
- if (isset($response['paymentMethod']['brand'])) {
- $paymentMethod = $response['paymentMethod']['brand'];
- }
- elseif (isset($response['paymentMethod']['type'])) {
- $paymentMethod = $response['paymentMethod']['type'];
- }
- else {
- $paymentMethod = '';
- }
-
- $pspReference = isset($response['pspReference']) ? trim((string) $response['pspReference']) : '';
-
- $type = 'Adyen Result URL response:';
- $comment = __(
- '%1
authResult: %2
pspReference: %3
paymentMethod: %4',
- $type,
- $authResult,
- $pspReference,
- $paymentMethod
- );
-
- // needed because then we need to save $order objects
- $order->setAdyenResulturlEventCode($authResult);
-
- // Update the payment additional information with the new result code
- $orderPayment = $order->getPayment();
- $orderPayment->setAdditionalInformation('resultCode', $authResult);
- $this->orderResourceModel->save($order);
-
- switch (strtoupper((string) $authResult)) {
- case Notification::AUTHORISED:
- $result = true;
- $this->adyenLogger->addAdyenResult('Do nothing wait for the notification');
- break;
- case Notification::RECEIVED:
- $result = true;
- if (strpos((string) $paymentMethod, "alipay_hk") !== false) {
- $result = false;
- }
- $this->adyenLogger->addAdyenResult('Do nothing wait for the notification');
- break;
- case Notification::PENDING:
- // do nothing wait for the notification
- $result = true;
- if (strpos((string) $paymentMethod, "bankTransfer") !== false) {
- $comment .= "
Waiting for the customer to transfer the money.";
- } elseif ($paymentMethod == "sepadirectdebit") {
- $comment .= "
This request will be send to the bank at the end of the day.";
- } else {
- $comment .= "
The payment result is not confirmed (yet).
-
Once the payment is authorised, the order status will be updated accordingly.
-
If the order is stuck on this status, the payment can be seen as unsuccessful.
-
The order can be automatically cancelled based on the OFFER_CLOSED notification.
- Please contact Adyen Support to enable this.";
- }
- $this->adyenLogger->addAdyenResult('Do nothing wait for the notification');
- break;
- case Notification::CANCELLED:
- case Notification::ERROR:
- $this->adyenLogger->addAdyenResult('Cancel or Hold the order on OFFER_CLOSED notification');
- $result = false;
- break;
- case Notification::REFUSED:
- // if refused there will be a AUTHORIZATION : FALSE notification send only exception is idea
- $this->adyenLogger->addAdyenResult(
- 'Cancel or Hold the order on AUTHORISATION
- success = false notification'
+ // Compare the merchant references
+ $merchantReference = $paymentsDetailsResponse['merchantReference'] ?? null;
+ if ($merchantReference) {
+ if ($order->getIncrementId() === $merchantReference) {
+ $this->order = $order;
+ $this->payment = $order->getPayment();
+ $this->cleanUpRedirectAction();
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $order
);
- $result = false;
-
- if (!$order->canCancel()) {
- $order->setState(Order::STATE_NEW);
- $this->orderRepository->save($order);
- }
- $this->adyenDataHelper->cancelOrder($order);
-
- break;
- default:
- $this->adyenLogger->addAdyenResult('This event is not supported: ' . $authResult);
- $result = false;
- break;
- }
-
- $history = $this->orderHistoryFactory->create()
- ->setStatus($order->getStatus())
- ->setComment($comment)
- ->setEntityName('order')
- ->setOrder($order);
-
- $history->save();
-
- // Cleanup state data
- try {
- $this->stateDataHelper->cleanQuoteStateData($order->getQuoteId(), $authResult);
- } catch (\Exception $exception) {
- $this->adyenLogger->error(__('Error cleaning the payment state data: %s', $exception->getMessage()));
- }
-
-
- return $result;
- }
-
- protected function getOrder(string $incrementId = null): Order
- {
- if (!isset($this->order)) {
- if ($incrementId !== null) {
- //TODO Replace with order repository search for best practice
- $this->order = $this->orderFactory->create()->loadByIncrementId($incrementId);
} else {
- $this->order = $this->session->getLastRealOrder();
+ $this->adyenLogger->error("Wrong merchantReference was set in the query or in the session");
}
+ } else {
+ $this->adyenLogger->error("No merchantReference in the response");
}
- return $this->order;
+ return $result;
}
- protected function validatePayloadAndReturnResponse(array $result): array
+ /**
+ * @throws LocalizedException
+ */
+ private function getOrder(string $incrementId = null): Order
{
- $client = $this->adyenDataHelper->initializeAdyenClient($this->storeManager->getStore()->getId());
- $service = $this->adyenDataHelper->createAdyenCheckoutService($client);
-
- $order = $this->getOrder(
- !empty($result['merchantReference']) ? $result['merchantReference'] : null
- );
+ if ($incrementId !== null) {
+ $order = $this->orderFactory->create()->loadByIncrementId($incrementId);
+ } else {
+ $order = $this->session->getLastRealOrder();
+ }
if (!$order->getId()) {
throw new LocalizedException(
@@ -376,47 +196,25 @@ protected function validatePayloadAndReturnResponse(array $result): array
);
}
- $this->payment = $order->getPayment();
-
- $request = [];
-
- // filter details to match the keys
- $details = $result;
- // TODO build a validator class which also validates the type of the param
- $details = DataArrayValidator::getArrayOnlyWithApprovedKeys($details, self::DETAILS_ALLOWED_PARAM_KEYS);
-
- // Remove helper params in case left in the request
- $helperParams = ['isAjax', 'merchantReference'];
- foreach ($helperParams as $helperParam) {
- if (array_key_exists($helperParam, $details)) {
- unset($details[$helperParam]);
- }
- }
-
- $request["details"] = $details;
- $requestOptions['idempotencyKey'] = $this->idempotencyHelper->generateIdempotencyKey($request);
- $requestOptions['headers'] = $this->adyenDataHelper->buildRequestHeaders();
+ return $order;
+ }
- try {
- $response = $service->paymentsDetails($request, $requestOptions);
- $responseMerchantReference = !empty($response['merchantReference']) ? $response['merchantReference'] : null;
- $resultMerchantReference = !empty($result['merchantReference']) ? $result['merchantReference'] : null;
- $merchantReference = $responseMerchantReference ?: $resultMerchantReference;
- if ($merchantReference) {
- if ($order->getIncrementId() === $merchantReference) {
- $this->order = $order;
- } else {
- $this->adyenLogger->error("Wrong merchantReference was set in the query or in the session");
- $response['error'] = 'merchantReference mismatch';
- }
- } else {
- $this->adyenLogger->error("No merchantReference in the response");
- $response['error'] = 'merchantReference is missing from the response';
- }
- } catch (AdyenException $e) {
- $response['error'] = $e->getMessage();
+ /**
+ * @return void
+ * @throws Exception
+ */
+ private function cleanUpRedirectAction(): void
+ {
+ // Prevent action component to redirect page again after returning to the shop
+ $paymentAction = $this->order->getPayment()->getAdditionalInformation('action');
+ $brandCode = $this->order->getPayment()->getAdditionalInformation('brand_code');
+ $resultCode = $this->order->getPayment()->getAdditionalInformation('resultCode');
+
+ if (($brandCode == self::BRAND_CODE_DOTPAY && $resultCode == self::RESULT_CODE_RECEIVED) ||
+ (isset($paymentAction) && $paymentAction['type'] === 'redirect')
+ ) {
+ $this->payment->unsAdditionalInformation('action');
+ $this->order->save();
}
-
- return $response;
}
}
diff --git a/Helper/Order.php b/Helper/Order.php
index 0109f66ee..4608cdd5b 100644
--- a/Helper/Order.php
+++ b/Helper/Order.php
@@ -83,6 +83,8 @@ class Order extends AbstractHelper
/** @var AdyenCreditmemoHelper */
private $adyenCreditmemoHelper;
+ private MagentoOrder\StatusResolver $statusResolver;
+
public function __construct(
Context $context,
Builder $transactionBuilder,
@@ -100,7 +102,8 @@ public function __construct(
OrderPaymentCollectionFactory $adyenOrderPaymentCollectionFactory,
PaymentMethods $paymentMethodsHelper,
AdyenCreditMemoResourceModel $adyenCreditmemoResourceModel,
- AdyenCreditmemoHelper $adyenCreditmemoHelper
+ AdyenCreditmemoHelper $adyenCreditmemoHelper,
+ MagentoOrder\StatusResolver $statusResolver
) {
parent::__construct($context);
$this->transactionBuilder = $transactionBuilder;
@@ -119,6 +122,7 @@ public function __construct(
$this->paymentMethodsHelper = $paymentMethodsHelper;
$this->adyenCreditmemoResourceModel = $adyenCreditmemoResourceModel;
$this->adyenCreditmemoHelper = $adyenCreditmemoHelper;
+ $this->statusResolver = $statusResolver;
}
/**
@@ -370,6 +374,28 @@ public function setPrePaymentAuthorized(MagentoOrder $order): MagentoOrder
return $order;
}
+ public function setStatusOrderCreation(OrderInterface $order): OrderInterface
+ {
+ $paymentMethod = $order->getPayment()->getMethod();
+
+ // Fetch the default order status for order creation from the configuration.
+ $status = $this->configHelper->getConfigData(
+ 'order_status',
+ $paymentMethod,
+ $order->getStoreId()
+ );
+
+ if (is_null($status)) {
+ // If the configuration doesn't exist, use the default status.
+ $status = $this->statusResolver->getOrderStatusByState($order, MagentoOrder::STATE_NEW);
+ }
+
+ $order->setStatus($status);
+ $order->setState(MagentoOrder::STATE_NEW);
+
+ return $order;
+ }
+
/**
* @param MagentoOrder $order
* @param $ignoreHasInvoice
diff --git a/Helper/PaymentResponseHandler.php b/Helper/PaymentResponseHandler.php
index bd1997bc0..fe8cec13f 100644
--- a/Helper/PaymentResponseHandler.php
+++ b/Helper/PaymentResponseHandler.php
@@ -14,9 +14,11 @@
use Adyen\Payment\Logger\AdyenLogger;
use Exception;
use Magento\Framework\Exception\AlreadyExistsException;
-use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Sales\Api\Data\OrderInterface;
-use Magento\Sales\Model\Order\Payment;
+use Magento\Sales\Model\Order\Status\HistoryFactory;
+use Magento\Sales\Model\OrderRepository;
use Magento\Sales\Model\ResourceModel\Order;
class PaymentResponseHandler
@@ -35,7 +37,7 @@ class PaymentResponseHandler
const VAULT = 'Magento Vault';
const POS_SUCCESS = 'Success';
- public const ACTION_REQUIRED_STATUSES = [
+ const ACTION_REQUIRED_STATUSES = [
self::REDIRECT_SHOPPER,
self::IDENTIFY_SHOPPER,
self::CHALLENGE_SHOPPER,
@@ -46,46 +48,35 @@ class PaymentResponseHandler
* @var AdyenLogger
*/
private AdyenLogger $adyenLogger;
-
- /**
- * @var Vault
- */
private Vault $vaultHelper;
-
- /**
- * @var Order
- */
private Order $orderResourceModel;
-
- /**
- * @var Data
- */
private Data $dataHelper;
-
- /**
- * @var Quote
- */
private Quote $quoteHelper;
+ private \Adyen\Payment\Helper\Order $orderHelper;
+ private OrderRepository $orderRepository;
+ private HistoryFactory $orderHistoryFactory;
+ private StateData $stateDataHelper;
- /**
- * @param AdyenLogger $adyenLogger
- * @param Vault $vaultHelper
- * @param Order $orderResourceModel
- * @param Data $dataHelper
- * @param Quote $quoteHelper
- */
public function __construct(
AdyenLogger $adyenLogger,
Vault $vaultHelper,
Order $orderResourceModel,
Data $dataHelper,
- Quote $quoteHelper
+ Quote $quoteHelper,
+ \Adyen\Payment\Helper\Order $orderHelper,
+ OrderRepository $orderRepository,
+ HistoryFactory $orderHistoryFactory,
+ StateData $stateDataHelper
) {
$this->adyenLogger = $adyenLogger;
$this->vaultHelper = $vaultHelper;
$this->orderResourceModel = $orderResourceModel;
$this->dataHelper = $dataHelper;
$this->quoteHelper = $quoteHelper;
+ $this->orderHelper = $orderHelper;
+ $this->orderRepository = $orderRepository;
+ $this->orderHistoryFactory = $orderHistoryFactory;
+ $this->stateDataHelper = $stateDataHelper;
}
public function formatPaymentResponse(
@@ -132,86 +123,108 @@ public function formatPaymentResponse(
}
/**
- * @param array $paymentsResponse
- * @param Payment $payment
- * @param OrderInterface|null $order
+ * @param array $paymentsDetailsResponse
+ * @param OrderInterface $order
* @return bool
- * @throws LocalizedException
* @throws AlreadyExistsException
+ * @throws InputException
+ * @throws NoSuchEntityException
*/
- public function handlePaymentResponse(
- array $paymentsResponse,
- Payment $payment,
- OrderInterface $order = null
- ):bool {
- if (empty($paymentsResponse)) {
+ public function handlePaymentsDetailsResponse(
+ array $paymentsDetailsResponse,
+ OrderInterface $order
+ ): bool {
+ if (empty($paymentsDetailsResponse)) {
$this->adyenLogger->error("Payment details call failed, paymentsResponse is empty");
return false;
}
- if (!empty($paymentsResponse['resultCode'])) {
- $payment->setAdditionalInformation('resultCode', $paymentsResponse['resultCode']);
+ $this->adyenLogger->addAdyenResult('Updating the order');
+ $payment = $order->getPayment();
+
+ $authResult = $paymentsDetailsResponse['authResult'] ?? $paymentsDetailsResponse['resultCode'] ?? null;
+ if (is_null($authResult)) {
+ // In case the result is unknown we log the request and don't update the history
+ $this->adyenLogger->error(
+ "Unexpected result query parameter. Response: " . json_encode($paymentsDetailsResponse)
+ );
+
+ return false;
}
- if (!empty($paymentsResponse['action'])) {
- $payment->setAdditionalInformation('action', $paymentsResponse['action']);
+ $paymentMethod = $paymentsDetailsResponse['paymentMethod']['brand'] ??
+ $paymentsDetailsResponse['paymentMethod']['type'] ??
+ '';
+
+ $pspReference = isset($paymentsDetailsResponse['pspReference']) ?
+ trim((string) $paymentsDetailsResponse['pspReference']) :
+ '';
+
+ $type = 'Adyen paymentsDetails response:';
+ $comment = __(
+ '%1
authResult: %2
pspReference: %3
paymentMethod: %4',
+ $type,
+ $authResult,
+ $pspReference,
+ $paymentMethod
+ );
+
+ if (!empty($paymentsDetailsResponse['resultCode'])) {
+ $payment->setAdditionalInformation('resultCode', $paymentsDetailsResponse['resultCode']);
}
- if (!empty($paymentsResponse['additionalData'])) {
- $payment->setAdditionalInformation('additionalData', $paymentsResponse['additionalData']);
+ if (!empty($paymentsDetailsResponse['action'])) {
+ $payment->setAdditionalInformation('action', $paymentsDetailsResponse['action']);
}
- if (!empty($paymentsResponse['pspReference'])) {
- $payment->setAdditionalInformation('pspReference', $paymentsResponse['pspReference']);
+ if (!empty($paymentsDetailsResponse['additionalData'])) {
+ $payment->setAdditionalInformation('additionalData', $paymentsDetailsResponse['additionalData']);
}
- if (!empty($paymentsResponse['details'])) {
- $payment->setAdditionalInformation('details', $paymentsResponse['details']);
+ if (!empty($paymentsDetailsResponse['pspReference'])) {
+ $payment->setAdditionalInformation('pspReference', $paymentsDetailsResponse['pspReference']);
}
- switch ($paymentsResponse['resultCode']) {
- case self::PRESENT_TO_SHOPPER:
- case self::PENDING:
- case self::RECEIVED:
- case self::IDENTIFY_SHOPPER:
- case self::CHALLENGE_SHOPPER:
- break;
- //We don't need to handle these resultCodes
- case self::REDIRECT_SHOPPER:
- $this->adyenLogger->addAdyenResult("Customer was redirected.");
- if ($order) {
- $order->addStatusHistoryComment(
- __(
- 'Customer was redirected to an external payment page. (In case of card payments the shopper is redirected to the bank for 3D-secure validation.) Once the shopper is authenticated,
- the order status will be updated accordingly.
-
Make sure that your notifications are being processed!
-
If the order is stuck on this status, the shopper abandoned the session.
- The payment can be seen as unsuccessful.
-
The order can be automatically cancelled based on the OFFER_CLOSED notification.
- Please contact Adyen Support to enable this.'
- ),
- $order->getStatus()
- )->save();
- }
- break;
+ if (!empty($paymentsDetailsResponse['details'])) {
+ $payment->setAdditionalInformation('details', $paymentsDetailsResponse['details']);
+ }
+
+ if (!empty($paymentsDetailsResponse['donationToken'])) {
+ $payment->setAdditionalInformation('donationToken', $paymentsDetailsResponse['donationToken']);
+ }
+
+ // Handle recurring details
+ $this->vaultHelper->handlePaymentResponseRecurringDetails($payment, $paymentsDetailsResponse);
+
+ // If the response is valid, update the order status.
+ if (!in_array($paymentsDetailsResponse['resultCode'], PaymentResponseHandler::ACTION_REQUIRED_STATUSES)) {
+ /*
+ * Change order state from pending_payment to new and expect authorisation webhook
+ * if no additional action is required according to /paymentsDetails response.
+ * Otherwise keep the order state as pending_payment.
+ */
+ $order = $this->orderHelper->setStatusOrderCreation($order);
+ $this->orderRepository->save($order);
+ }
+
+ // Cleanup state data if exists.
+ try {
+ $this->stateDataHelper->cleanQuoteStateData($order->getQuoteId(), $authResult);
+ } catch (Exception $exception) {
+ $this->adyenLogger->error(__('Error cleaning the payment state data: %s', $exception->getMessage()));
+ }
+
+ switch ($paymentsDetailsResponse['resultCode']) {
case self::AUTHORISED:
- if (!empty($paymentsResponse['pspReference'])) {
+ if (!empty($paymentsDetailsResponse['pspReference'])) {
// set pspReference as transactionId
- $payment->setCcTransId($paymentsResponse['pspReference']);
- $payment->setLastTransId($paymentsResponse['pspReference']);
+ $payment->setCcTransId($paymentsDetailsResponse['pspReference']);
+ $payment->setLastTransId($paymentsDetailsResponse['pspReference']);
// set transaction
- $payment->setTransactionId($paymentsResponse['pspReference']);
- }
-
- // Handle recurring details
- $this->vaultHelper->handlePaymentResponseRecurringDetails($payment, $paymentsResponse);
-
- if (!empty($paymentsResponse['donationToken'])) {
- $payment->setAdditionalInformation('donationToken', $paymentsResponse['donationToken']);
+ $payment->setTransactionId($paymentsDetailsResponse['pspReference']);
}
- $this->orderResourceModel->save($order);
try {
$this->quoteHelper->disableQuote($order->getQuoteId());
} catch (Exception $e) {
@@ -219,8 +232,47 @@ public function handlePaymentResponse(
'quoteId' => $order->getQuoteId()
]);
}
+
+ $result = true;
+ break;
+ case self::PENDING:
+ /* Change order state from pending_payment to new and expect authorisation webhook
+ * if no additional action is required according to /paymentDetails response. */
+ $order = $this->orderHelper->setStatusOrderCreation($order);
+ $this->orderRepository->save($order);
+
+ // do nothing wait for the notification
+ if (strpos((string) $paymentMethod, "bankTransfer") !== false) {
+ $comment .= "
Waiting for the customer to transfer the money.";
+ } elseif ($paymentMethod == "sepadirectdebit") {
+ $comment .= "
This request will be send to the bank at the end of the day.";
+ } else {
+ $comment .= "
The payment result is not confirmed (yet).
+
Once the payment is authorised, the order status will be updated accordingly.
+
If the order is stuck on this status, the payment can be seen as unsuccessful.
+
The order can be automatically cancelled based on the OFFER_CLOSED notification.
+ Please contact Adyen Support to enable this.";
+ }
+ $this->adyenLogger->addAdyenResult('Do nothing wait for the notification');
+
+ $result = true;
+ break;
+ case self::PRESENT_TO_SHOPPER:
+ case self::IDENTIFY_SHOPPER:
+ case self::CHALLENGE_SHOPPER:
+ case self::REDIRECT_SHOPPER:
+ $this->adyenLogger->addAdyenResult("Additional action is required for the payment.");
+ $result = true;
+ break;
+ case self::RECEIVED:
+ $result = true;
+ if (str_contains((string)$paymentMethod, "alipay_hk")) {
+ $result = false;
+ }
+ $this->adyenLogger->addAdyenResult('Do nothing wait for the notification');
break;
case self::REFUSED:
+ case self::CANCELLED:
// Cancel order in case result is refused
if (null !== $order) {
// Set order to new so it can be cancelled
@@ -229,17 +281,32 @@ public function handlePaymentResponse(
$order->setActionFlag(\Magento\Sales\Model\Order::ACTION_FLAG_CANCEL, true);
$this->dataHelper->cancelOrder($order);
}
- return false;
- case self::ERROR:
+ $result = false;
+ break;
default:
$this->adyenLogger->error(
- sprintf("Payment details call failed for action, resultCode is %s Raw API responds: %s",
- $paymentsResponse['resultCode'],
- json_encode($paymentsResponse)
+ sprintf("Payment details call failed for action, resultCode is %s Raw API responds: %s.
+ Cancel or Hold the order on OFFER_CLOSED notification.",
+ $paymentsDetailsResponse['resultCode'],
+ json_encode($paymentsDetailsResponse)
));
- return false;
+ $result = false;
+ break;
}
- return true;
+
+ $history = $this->orderHistoryFactory->create()
+ ->setStatus($order->getStatus())
+ ->setComment($comment)
+ ->setEntityName('order')
+ ->setOrder($order);
+
+ $history->save();
+
+ // needed because then we need to save $order objects
+ $order->setAdyenResulturlEventCode($authResult);
+ $this->orderResourceModel->save($order);
+
+ return $result;
}
}
diff --git a/Helper/PaymentsDetails.php b/Helper/PaymentsDetails.php
index 117d77298..279708403 100644
--- a/Helper/PaymentsDetails.php
+++ b/Helper/PaymentsDetails.php
@@ -12,9 +12,11 @@
namespace Adyen\Payment\Helper;
use Adyen\AdyenException;
+use Adyen\Payment\Helper\Util\DataArrayValidator;
use Adyen\Payment\Logger\AdyenLogger;
-use Adyen\Service\Validator\DataArrayValidator;
use Magento\Checkout\Model\Session;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\ValidatorException;
use Magento\Sales\Api\Data\OrderInterface;
@@ -26,87 +28,68 @@ class PaymentsDetails
'threeDSAuthenticationOnly'
];
+ const REQUEST_HELPER_PARAMETERS = [
+ 'isAjax',
+ 'merchantReference'
+ ];
+
private Session $checkoutSession;
private Data $adyenHelper;
private AdyenLogger $adyenLogger;
- private PaymentResponseHandler $paymentResponseHandler;
private Idempotency $idempotencyHelper;
public function __construct(
Session $checkoutSession,
Data $adyenHelper,
AdyenLogger $adyenLogger,
- PaymentResponseHandler $paymentResponseHandler,
Idempotency $idempotencyHelper
) {
$this->checkoutSession = $checkoutSession;
$this->adyenHelper = $adyenHelper;
$this->adyenLogger = $adyenLogger;
- $this->paymentResponseHandler = $paymentResponseHandler;
$this->idempotencyHelper = $idempotencyHelper;
}
- public function initiatePaymentDetails(OrderInterface $order, string $payload): string
+ /**
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
+ * @throws ValidatorException
+ */
+ public function initiatePaymentDetails(OrderInterface $order, array $payload): array
{
- // Decode payload from frontend
- $payload = json_decode($payload, true);
-
- // Validate JSON that has just been parsed if it was in a valid format
- if (json_last_error() !== JSON_ERROR_NONE) {
- throw new ValidatorException(__('Payment details call failed because the request was not a valid JSON'));
- }
-
- $payment = $order->getPayment();
- $apiPayload = DataArrayValidator::getArrayOnlyWithApprovedKeys($payload, self::PAYMENTS_DETAILS_KEYS);
+ $request = $this->cleanUpPaymentDetailsPayload($payload);
- // Send the request
try {
$client = $this->adyenHelper->initializeAdyenClient($order->getStoreId());
$service = $this->adyenHelper->createAdyenCheckoutService($client);
- $requestOptions['idempotencyKey'] = $this->idempotencyHelper->generateIdempotencyKey($apiPayload);
+ $requestOptions['idempotencyKey'] = $this->idempotencyHelper->generateIdempotencyKey($request);
$requestOptions['headers'] = $this->adyenHelper->buildRequestHeaders();
- $paymentDetails = $service->paymentsDetails($apiPayload, $requestOptions);
+ $response = $service->paymentsDetails($request, $requestOptions);
} catch (AdyenException $e) {
$this->adyenLogger->error("Payment details call failed: " . $e->getMessage());
$this->checkoutSession->restoreQuote();
- // accept cancellation request, restore quote
- if (!empty($payload['cancelled'])) {
- throw $this->createCancelledException();
- } else {
- throw new ValidatorException(__('Payment details call failed'));
- }
+ throw new ValidatorException(__('Payment details call failed'));
}
- // Handle response
- if (!$this->paymentResponseHandler->handlePaymentResponse($paymentDetails, $payment, $order)) {
- $this->checkoutSession->restoreQuote();
- throw new ValidatorException(__('The payment is REFUSED.'));
- }
+ return $response;
+ }
- $action = null;
- if (!empty($paymentDetails['action'])) {
- $action = $paymentDetails['action'];
- }
+ private function cleanUpPaymentDetailsPayload(array $payload): array
+ {
+ $payload = DataArrayValidator::getArrayOnlyWithApprovedKeys(
+ $payload,
+ self::PAYMENTS_DETAILS_KEYS
+ );
- $additionalData = null;
- if (!empty($paymentDetails['additionalData'])) {
- $additionalData = $paymentDetails['additionalData'];
+ foreach (self::REQUEST_HELPER_PARAMETERS as $helperParam) {
+ if (array_key_exists($helperParam, $payload['details'])) {
+ unset($payload['details'][$helperParam]);
+ }
}
- return json_encode(
- $this->paymentResponseHandler->formatPaymentResponse(
- $paymentDetails['resultCode'],
- $action,
- $additionalData
- )
- );
- }
-
- private function createCancelledException(): ValidatorException
- {
- return new ValidatorException(__('Payment has been cancelled'));
+ return $payload;
}
}
diff --git a/Model/Api/AdyenPaymentsDetails.php b/Model/Api/AdyenPaymentsDetails.php
index 4e2d4c58b..5691a359a 100644
--- a/Model/Api/AdyenPaymentsDetails.php
+++ b/Model/Api/AdyenPaymentsDetails.php
@@ -12,24 +12,32 @@
namespace Adyen\Payment\Model\Api;
use Adyen\Payment\Api\AdyenPaymentsDetailsInterface;
+use Adyen\Payment\Helper\PaymentResponseHandler;
use Adyen\Payment\Helper\PaymentsDetails;
+use Magento\Checkout\Model\Session;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Exception\ValidatorException;
use Magento\Sales\Api\OrderRepositoryInterface;
class AdyenPaymentsDetails implements AdyenPaymentsDetailsInterface
{
private OrderRepositoryInterface $orderRepository;
-
private PaymentsDetails $paymentsDetails;
+ private PaymentResponseHandler $paymentResponseHandler;
+ private Session $checkoutSession;
public function __construct(
OrderRepositoryInterface $orderRepository,
- PaymentsDetails $paymentsDetails
+ PaymentsDetails $paymentsDetails,
+ PaymentResponseHandler $paymentResponseHandler,
+ Session $checkoutSession
) {
$this->orderRepository = $orderRepository;
$this->paymentsDetails = $paymentsDetails;
+ $this->paymentResponseHandler = $paymentResponseHandler;
+ $this->checkoutSession = $checkoutSession;
}
/**
@@ -45,6 +53,30 @@ public function initiate(string $payload, string $orderId): string
{
$order = $this->orderRepository->get(intval($orderId));
- return $this->paymentsDetails->initiatePaymentDetails($order, $payload);
+ // Decode payload from frontend
+ $payload = json_decode($payload, true);
+
+ // Validate JSON that has just been parsed if it was in a valid format
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new ValidatorException(
+ __('Payment details call failed because the request was not a valid JSON')
+ );
+ }
+
+ $response = $this->paymentsDetails->initiatePaymentDetails($order, $payload);
+
+ // Handle response
+ if (!$this->paymentResponseHandler->handlePaymentsDetailsResponse($response, $order)) {
+ $this->checkoutSession->restoreQuote();
+ throw new ValidatorException(__('The payment is REFUSED.'));
+ }
+
+ return json_encode(
+ $this->paymentResponseHandler->formatPaymentResponse(
+ $response['resultCode'],
+ $response['action'] ?? null,
+ $response['additionalData'] ?? null
+ )
+ );
}
}
diff --git a/Model/Api/GuestAdyenPaymentsDetails.php b/Model/Api/GuestAdyenPaymentsDetails.php
index def9472b6..da426e647 100644
--- a/Model/Api/GuestAdyenPaymentsDetails.php
+++ b/Model/Api/GuestAdyenPaymentsDetails.php
@@ -12,7 +12,6 @@
namespace Adyen\Payment\Model\Api;
use Adyen\Payment\Api\GuestAdyenPaymentsDetailsInterface;
-use Adyen\Payment\Helper\PaymentsDetails;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
@@ -23,19 +22,17 @@
class GuestAdyenPaymentsDetails implements GuestAdyenPaymentsDetailsInterface
{
private OrderRepositoryInterface $orderRepository;
-
private QuoteIdMaskFactory $quoteIdMaskFactory;
-
- private PaymentsDetails $paymentsDetails;
+ private AdyenPaymentsDetails $adyenPaymentsDetails;
public function __construct(
OrderRepositoryInterface $orderRepository,
QuoteIdMaskFactory $quoteIdMaskFactory,
- PaymentsDetails $paymentsDetails
+ AdyenPaymentsDetails $adyenPaymentsDetails
) {
$this->orderRepository = $orderRepository;
$this->quoteIdMaskFactory = $quoteIdMaskFactory;
- $this->paymentsDetails = $paymentsDetails;
+ $this->adyenPaymentsDetails = $adyenPaymentsDetails;
}
/**
@@ -61,6 +58,6 @@ public function initiate(string $payload, string $orderId, string $cartId): stri
);
}
- return $this->paymentsDetails->initiatePaymentDetails($order, $payload);
+ return $this->adyenPaymentsDetails->initiate($payload, $orderId);
}
}
diff --git a/Observer/SetOrderStateAfterPaymentObserver.php b/Observer/SetOrderStateAfterPaymentObserver.php
new file mode 100644
index 000000000..cd7509244
--- /dev/null
+++ b/Observer/SetOrderStateAfterPaymentObserver.php
@@ -0,0 +1,77 @@
+
+ */
+
+namespace Adyen\Payment\Observer;
+
+use Adyen\Payment\Helper\PaymentResponseHandler;
+use Adyen\Payment\Model\Method\Adapter;
+use Exception;
+use Magento\Framework\Event\Observer;
+use Magento\Framework\Event\ObserverInterface;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Payment;
+use Magento\Sales\Model\Order\StatusResolver;
+
+class SetOrderStateAfterPaymentObserver implements ObserverInterface
+{
+ private StatusResolver $statusResolver;
+
+ public function __construct(
+ StatusResolver $statusResolver
+ ) {
+ $this->statusResolver = $statusResolver;
+ }
+
+ /**
+ * @throws LocalizedException
+ * @throws Exception
+ */
+ public function execute(Observer $observer)
+ {
+ /** @var Payment $payment */
+ $payment = $observer->getData('payment');
+ $methodInstance = $payment->getMethodInstance();
+
+ if ($methodInstance instanceof Adapter) {
+ $order = $payment->getOrder();
+ $resultCode = $payment->getAdditionalInformation('resultCode');
+ $action = $payment->getAdditionalInformation('action');
+
+ /*
+ * Set order status and state to pending_payment if an addition action is required.
+ * This status will be changed when the shopper completes the action or returns from a redirection.
+ */
+ if (in_array($resultCode, PaymentResponseHandler::ACTION_REQUIRED_STATUSES) &&
+ !is_null($action)
+ ) {
+ $actionType = $action['type'];
+
+ $status = $this->statusResolver->getOrderStatusByState(
+ $payment->getOrder(),
+ Order::STATE_PENDING_PAYMENT
+ );
+ $order->setState(Order::STATE_PENDING_PAYMENT);
+ $order->setStatus($status);
+
+ $message = sprintf(
+ __("%s action is required to complete the payment.
Result code: %s"),
+ ucfirst($actionType),
+ $resultCode
+ );
+
+ $order->addCommentToStatusHistory($message, $status);
+ $order->save();
+ }
+ }
+ }
+}
diff --git a/Test/Unit/Controller/Return/IndexTest.php b/Test/Unit/Controller/Return/IndexTest.php
new file mode 100644
index 000000000..bdad75ca4
--- /dev/null
+++ b/Test/Unit/Controller/Return/IndexTest.php
@@ -0,0 +1,267 @@
+
+ */
+
+namespace Adyen\Payment\Test\Unit\Controller\Return;
+
+use Adyen\Payment\Controller\Return\Index;
+use Adyen\Payment\Helper\Config;
+use Adyen\Payment\Helper\PaymentResponseHandler;
+use Adyen\Payment\Helper\PaymentsDetails;
+use Adyen\Payment\Helper\Quote;
+use Adyen\Payment\Logger\AdyenLogger;
+use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
+use Exception;
+use Magento\Checkout\Model\Session;
+use Magento\Framework\App\Action\Context;
+use Magento\Framework\App\RequestInterface;
+use Magento\Framework\App\Response\RedirectInterface;
+use Magento\Framework\App\ResponseInterface;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Message\ManagerInterface as MessageManagerInterface;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Payment;
+use Magento\Sales\Model\OrderFactory;
+use Magento\Store\Model\StoreManagerInterface;
+
+class IndexTest extends AbstractAdyenTestCase
+{
+ private $indexControllerMock;
+ private $controllerRequestMock;
+ private $messageManagerMock;
+ private $redirectMock;
+ private $contextResponseMock;
+ private $quoteMock;
+ private $orderEntityMock;
+ private $paymentEntityMock;
+
+ private $contextMock;
+ private $orderFactoryMock;
+ private $sessionMock;
+ private $adyenLoggerMock;
+ private $storeManagerMock;
+ private $quoteHelperMock;
+ private $configHelperMock;
+ private $paymentsDetailsHelperMock;
+ private $paymentResponseHandlerMock;
+
+ protected function setUp(): void
+ {
+ // Constructor argument mocks
+ $this->contextMock = $this->createMock(Context::class);
+ $this->orderFactoryMock = $this->createGeneratedMock(OrderFactory::class, ['create']);
+ $this->sessionMock = $this->createMock(Session::class);
+ $this->adyenLoggerMock = $this->createMock(AdyenLogger::class);
+ $this->storeManagerMock = $this->createMock(StoreManagerInterface::class);
+ $this->quoteHelperMock = $this->createMock(Quote::class);
+ $this->configHelperMock = $this->createMock(Config::class);
+ $this->paymentsDetailsHelperMock = $this->createMock(PaymentsDetails::class);
+ $this->paymentResponseHandlerMock = $this->createMock(PaymentResponseHandler::class);
+
+ // Extra mock objects and methods
+ $this->messageManagerMock = $this->createMock(MessageManagerInterface::class);
+ $this->redirectMock = $this->createMock(RedirectInterface::class);
+ $this->contextResponseMock = $this->createMock(ResponseInterface::class);
+ $this->quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
+ $this->paymentEntityMock = $this->createMock(Payment::class);
+ $this->paymentEntityMock->method('getAdditionalInformation')->will(
+ $this->returnValueMap([
+ ['action', ['type' => 'redirect']],
+ ['brand_code', Index::BRAND_CODE_DOTPAY],
+ ['resultCode', Index::RESULT_CODE_RECEIVED]
+ ])
+ );
+ $this->orderEntityMock = $this->createMock(Order::class);
+ $this->orderEntityMock->method('getPayment')->willReturn($this->paymentEntityMock);
+ $this->controllerRequestMock = $this->createMock(RequestInterface::class);
+ $this->orderFactoryMock->method('create')->willReturn($this->orderEntityMock);
+ $this->orderEntityMock->method('loadByIncrementId')->willReturnSelf();
+ $this->quoteMock->method('setIsActive')->willReturnSelf();
+ $this->sessionMock->method('getLastRealOrder')->willReturn($this->orderEntityMock);
+ $this->sessionMock->method('getQuote')->willReturn($this->quoteMock);
+ $this->contextMock->method('getRedirect')->willReturn($this->redirectMock);
+ $this->contextMock->method('getRequest')->willReturn($this->controllerRequestMock);
+ $this->contextMock->method('getMessageManager')->willReturn($this->messageManagerMock);
+ $this->contextMock->method('getResponse')->willReturn($this->contextResponseMock);
+ $this->configHelperMock->method('getAdyenAbstractConfigData')->will(
+ $this->returnValueMap([
+ ['return_path', null, '/checkout/cart'],
+ ['custom_success_redirect_path', null, null]
+ ])
+ );
+
+ $this->indexControllerMock = new Index(
+ $this->contextMock,
+ $this->orderFactoryMock,
+ $this->sessionMock,
+ $this->adyenLoggerMock,
+ $this->storeManagerMock,
+ $this->quoteHelperMock,
+ $this->configHelperMock,
+ $this->paymentsDetailsHelperMock,
+ $this->paymentResponseHandlerMock
+ );
+ }
+
+ private static function testDataProvider(): array
+ {
+ return [
+ [
+ 'redirectResponse' => [
+ 'merchantReference' => PHP_INT_MAX,
+ 'redirectResult' => 'ABCDEFG123456789'
+ ],
+ 'paymentsDetailsResponse' => [
+ 'merchantReference' => PHP_INT_MAX,
+ 'resultCode' => 'Authorised'
+ ],
+ 'responseHandlerResult' => true,
+ 'returnPath' => 'checkout/onepage/success',
+ 'orderId' => PHP_INT_MAX,
+ 'expectedException' => null
+ ],
+ [
+ 'redirectResponse' => [
+ 'merchantReference' => PHP_INT_MAX,
+ 'redirectResult' => 'ABCDEFG123456789'
+ ],
+ 'paymentsDetailsResponse' => [
+ 'merchantReference' => PHP_INT_MAX,
+ 'resultCode' => 'Authorised'
+ ],
+ 'responseHandlerResult' => true,
+ 'returnPath' => 'multishipping/checkout/success',
+ 'orderId' => PHP_INT_MAX,
+ 'expectedException' => null,
+ 'multishipping' => true
+ ],
+ [
+ 'redirectResponse' => [
+ 'redirectResult' => 'ABCDEFG123456789'
+ ],
+ 'paymentsDetailsResponse' => [
+ 'merchantReference' => PHP_INT_MAX,
+ 'resultCode' => 'Authorised'
+ ],
+ 'responseHandlerResult' => true,
+ 'returnPath' => 'checkout/onepage/success',
+ 'orderId' => PHP_INT_MAX,
+ 'expectedException' => null
+ ],
+ [
+ 'redirectResponse' => [
+ 'merchantReference' => PHP_INT_MIN,
+ 'redirectResult' => 'ABCDEFG123456789'
+ ],
+ 'paymentsDetailsResponse' => [
+ 'merchantReference' => PHP_INT_MAX,
+ 'resultCode' => 'Authorised'
+ ],
+ 'responseHandlerResult' => false,
+ 'returnPath' => null,
+ 'orderId' => null,
+ 'expectedException' => LocalizedException::class
+ ],
+ [
+ 'redirectResponse' => [
+ 'merchantReference' => PHP_INT_MAX,
+ 'redirectResult' => 'ABCDEFG123456789'
+ ],
+ 'paymentsDetailsResponse' => [],
+ 'responseHandlerResult' => false,
+ 'returnPath' => '/checkout/cart',
+ 'orderId' => PHP_INT_MAX,
+ 'expectedException' => null
+ ],
+ [
+ 'redirectResponse' => [
+ 'merchantReference' => PHP_INT_MIN,
+ 'redirectResult' => 'ABCDEFG123456789'
+ ],
+ 'paymentsDetailsResponse' => [
+ 'merchantReference' => PHP_INT_MAX,
+ 'resultCode' => 'Authorised'
+ ],
+ 'responseHandlerResult' => false,
+ 'returnPath' => '/checkout/cart',
+ 'orderId' => PHP_INT_MIN,
+ 'expectedException' => null
+ ],
+ [
+ 'redirectResponse' => [
+ 'merchantReference' => PHP_INT_MIN,
+ 'redirectResult' => 'ABCDEFG123456789'
+ ],
+ 'paymentsDetailsResponse' => [
+ 'merchantReference' => null,
+ 'resultCode' => null
+ ],
+ 'responseHandlerResult' => false,
+ 'returnPath' => '/checkout/cart',
+ 'orderId' => PHP_INT_MIN,
+ 'expectedException' => null
+ ],
+ [
+ 'redirectResponse' => null,
+ 'paymentsDetailsResponse' => [
+ 'merchantReference' => null,
+ 'resultCode' => null
+ ],
+ 'responseHandlerResult' => false,
+ 'returnPath' => '/checkout/cart',
+ 'orderId' => PHP_INT_MIN,
+ 'expectedException' => null
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider testDataProvider
+ */
+ public function testExecute(
+ $redirectResponse,
+ $paymentsDetailsResponse,
+ $responseHandlerResult,
+ $returnPath,
+ $orderId,
+ $expectedException,
+ $multishipping = false
+ ) {
+ if ($expectedException) {
+ $this->expectException($expectedException);
+ } else {
+ $this->redirectMock->expects($this->once())->method('redirect')->with(
+ $this->contextResponseMock,
+ $returnPath,
+ $redirectResponse ? ['_query' => ['utm_nooverride' => '1']] : []
+ );
+ }
+
+ if ($multishipping) {
+ $this->quoteHelperMock->method('getIsQuoteMultiShippingWithMerchantReference')
+ ->willReturn(true);
+ }
+
+ if (empty($paymentsDetailsResponse)) {
+ $this->paymentsDetailsHelperMock->method('initiatePaymentDetails')
+ ->willThrowException(new Exception);
+ }
+
+ $this->controllerRequestMock->method('getParams')->willReturn($redirectResponse);
+ $this->orderEntityMock->method('getId')->willReturn($orderId);
+ $this->orderEntityMock->method('getIncrementId')->willReturn($orderId);
+ $this->paymentResponseHandlerMock->method('handlePaymentsDetailsResponse')
+ ->willReturn($responseHandlerResult);
+ $this->paymentsDetailsHelperMock->method('initiatePaymentDetails')
+ ->willReturn($paymentsDetailsResponse);
+
+ $this->indexControllerMock->execute();
+ }
+}
diff --git a/Test/Unit/Helper/OrderTest.php b/Test/Unit/Helper/OrderTest.php
index abce98e16..35527dca3 100644
--- a/Test/Unit/Helper/OrderTest.php
+++ b/Test/Unit/Helper/OrderTest.php
@@ -30,6 +30,7 @@
use Magento\Framework\App\Helper\Context;
use Magento\Framework\DB\TransactionFactory;
use Magento\Framework\Notification\NotifierPool;
+use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Model\Order as MagentoOrder;
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
use Magento\Sales\Model\Order\Payment\Transaction\Builder;
@@ -638,6 +639,50 @@ public function testSetPrePaymentAuthorizedNoStatus()
$this->assertEquals('new', $result->getState());
}
+ public function testSetStatusOrderCreation()
+ {
+ $paymentMethodCode = 'adyen_cc';
+ $storeId = 1;
+ $assignedStatusForStateNew = 'pending';
+
+ $paymentMock = $this->createMock(MagentoOrder\Payment::class);
+ $paymentMock->method('getMethod')->willReturn($paymentMethodCode);
+
+ $orderMock = $this->createMock(OrderInterface::class);
+ $orderMock->method('getPayment')->willReturn($paymentMock);
+ $orderMock->method('getStoreId')->willReturn($storeId);
+
+ $configHelper = $this->createMock(Config::class);
+ $configHelper->method('getConfigData')->with('order_status', $paymentMethodCode, $storeId)
+ ->willReturn(\Magento\Sales\Model\Order::STATE_NEW);
+
+ $statusResolverMock = $this->createMock(MagentoOrder\StatusResolver::class);
+ $statusResolverMock->method('getOrderStatusByState')->willReturn($assignedStatusForStateNew);
+
+ $dataHelper = $this->createOrderHelper(
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ $statusResolverMock
+ );
+
+ $result = $dataHelper->setStatusOrderCreation($orderMock);
+
+ $this->assertInstanceOf(OrderInterface::class, $result);
+ }
protected function createOrderHelper(
$orderStatusCollectionFactory = null,
@@ -655,7 +700,8 @@ protected function createOrderHelper(
$notifierPool = null,
$paymentMethodsHelper = null,
$adyenCreditmemoResourceModel = null,
- $adyenCreditmemoHelper = null
+ $adyenCreditmemoHelper = null,
+ $statusResolver = null
): Order
{
$context = $this->createMock(Context::class);
@@ -724,6 +770,10 @@ protected function createOrderHelper(
$adyenCreditmemoHelper = $this->createMock(AdyenCreditmemoHelper::class);
}
+ if (is_null($statusResolver)) {
+ $statusResolver = $this->createMock(MagentoOrder\StatusResolver::class);
+ }
+
return new Order(
$context,
$builder,
@@ -741,7 +791,8 @@ protected function createOrderHelper(
$orderPaymentCollectionFactory,
$paymentMethodsHelper,
$adyenCreditmemoResourceModel,
- $adyenCreditmemoHelper
+ $adyenCreditmemoHelper,
+ $statusResolver
);
}
}
diff --git a/Test/Unit/Helper/PaymentDetailsTest.php b/Test/Unit/Helper/PaymentDetailsTest.php
index eef777514..7406661b4 100644
--- a/Test/Unit/Helper/PaymentDetailsTest.php
+++ b/Test/Unit/Helper/PaymentDetailsTest.php
@@ -10,13 +10,14 @@
*/
namespace Adyen\Payment\Test\Unit\Helper;
+use Adyen\AdyenException;
use Adyen\Payment\Helper\PaymentsDetails;
use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
+use Magento\Framework\Exception\ValidatorException;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Model\Order\Payment;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Logger\AdyenLogger;
-use Adyen\Payment\Helper\PaymentResponseHandler;
use Adyen\Payment\Helper\Idempotency;
use Magento\Checkout\Model\Session;
use Adyen\Service\Checkout;
@@ -27,72 +28,95 @@ class PaymentDetailsTest extends AbstractAdyenTestCase
private $checkoutSessionMock;
private $adyenHelperMock;
private $adyenLoggerMock;
- private $paymentResponseHandlerMock;
private $idempotencyHelperMock;
private $paymentDetails;
+ private $orderMock;
+ private $paymentMock;
+ private $checkoutServiceMock;
+ private $adyenClientMock;
+
protected function setUp(): void
{
$this->checkoutSessionMock = $this->createMock(Session::class);
$this->adyenHelperMock = $this->createMock(Data::class);
$this->adyenLoggerMock = $this->createMock(AdyenLogger::class);
- $this->paymentResponseHandlerMock = $this->createMock(PaymentResponseHandler::class);
$this->idempotencyHelperMock = $this->createMock(Idempotency::class);
+ $this->orderMock = $this->createMock(OrderInterface::class);
+ $this->paymentMock = $this->createMock(Payment::class);
+ $this->checkoutServiceMock = $this->createMock(Checkout::class);
+ $this->adyenClientMock = $this->createMock(Client::class);
+
+ $this->orderMock->method('getPayment')->willReturn($this->paymentMock);
+ $this->orderMock->method('getStoreId')->willReturn(1);
+ $this->paymentMock->method('getOrder')->willReturn($this->orderMock);
+
+ $this->adyenHelperMock->method('initializeAdyenClient')->willReturn($this->adyenClientMock);
+ $this->adyenHelperMock->method('createAdyenCheckoutService')->willReturn($this->checkoutServiceMock);
+
$this->paymentDetails = new PaymentsDetails(
$this->checkoutSessionMock,
$this->adyenHelperMock,
$this->adyenLoggerMock,
- $this->paymentResponseHandlerMock,
$this->idempotencyHelperMock
- );
+ );
}
- public function testRequestHeadersAreAddedToRequest()
+ public function testInitiatePaymentDetailsSuccessfully()
{
- $orderMock = $this->createMock(OrderInterface::class);
- $paymentMock = $this->createMock(Payment::class);
- $checkoutServiceMock = $this->createMock(Checkout::class);
- $adyenClientMock = $this->createMock(Client::class);
- $storeId = 1;
- $payload = json_encode([
- 'details' => 'some_details',
+ $payload = [
+ 'details' => [
+ 'detail_key1' => 'some-details',
+ 'merchantReference' => '00000000001'
+ ],
'paymentData' => 'some_payment_data',
- 'threeDSAuthenticationOnly' => true
- ]);
+ 'threeDSAuthenticationOnly' => true,
+ ];
+
$requestOptions = [
- 'idempotencyKey' => 'some_idempotency_key',
- 'headers' => ['headerKey' => 'headerValue']
+ 'idempotencyKey' => 'some_idempotency_key',
+ 'headers' => ['headerKey' => 'headerValue']
];
- $paymentDetailsResult = ['resultCode' => 'Authorised', 'action' => null, 'additionalData' => null];
- $orderMock->method('getPayment')->willReturn($paymentMock);
- $orderMock->method('getStoreId')->willReturn($storeId);
- $paymentMock->method('getOrder')->willReturn($orderMock);
+ $paymentDetailsResult = ['resultCode' => 'Authorised', 'action' => null, 'additionalData' => null];
- $this->adyenHelperMock->method('initializeAdyenClient')->willReturn($adyenClientMock);
- $this->adyenHelperMock->method('createAdyenCheckoutService')->willReturn($checkoutServiceMock);
$this->adyenHelperMock->method('buildRequestHeaders')->willReturn($requestOptions['headers']);
$this->idempotencyHelperMock->method('generateIdempotencyKey')->willReturn($requestOptions['idempotencyKey']);
- $checkoutServiceMock->expects($this->once())
+ // testing cleanUpPaymentDetailsPayload() method
+ $apiPayload = $payload;
+ unset($apiPayload['details']['merchantReference']);
+
+ $this->checkoutServiceMock->expects($this->once())
->method('paymentsDetails')
- ->with(
- $this->equalTo([
- 'details' => 'some_details',
- 'paymentData' => 'some_payment_data',
- 'threeDSAuthenticationOnly' => true
- ]),
- $this->equalTo($requestOptions)
- )
+ ->with($apiPayload, $requestOptions)
->willReturn($paymentDetailsResult);
- $this->paymentResponseHandlerMock->method('handlePaymentResponse')->willReturn(true);
- $this->paymentResponseHandlerMock->method('formatPaymentResponse')->willReturn($paymentDetailsResult);
+ $result = $this->paymentDetails->initiatePaymentDetails($this->orderMock, $payload);
+
+ $this->assertIsArray($result);
+ $this->assertEquals($paymentDetailsResult, $result);
+ }
+
+ public function testInitiatePaymentDetailsFailure()
+ {
+ $this->expectException(ValidatorException::class);
+
+ $payload = [
+ 'details' => [
+ 'detail_key1' => 'some-details',
+ 'merchantReference' => '00000000001'
+ ],
+ 'paymentData' => 'some_payment_data',
+ 'threeDSAuthenticationOnly' => true,
+ ];
+
+ $this->checkoutServiceMock->method('paymentsDetails')->willThrowException(new AdyenException());
- $result = $this->paymentDetails->initiatePaymentDetails($orderMock, $payload);
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error');
+ $this->checkoutSessionMock->expects($this->atLeastOnce())->method('restoreQuote');
- $this->assertJson($result);
- $this->assertEquals(json_encode($paymentDetailsResult), $result);
+ $this->paymentDetails->initiatePaymentDetails($this->orderMock, $payload);
}
}
diff --git a/Test/Unit/Helper/PaymentResponseHandlerTest.php b/Test/Unit/Helper/PaymentResponseHandlerTest.php
new file mode 100644
index 000000000..8a5b0fc76
--- /dev/null
+++ b/Test/Unit/Helper/PaymentResponseHandlerTest.php
@@ -0,0 +1,434 @@
+
+ */
+namespace Adyen\Payment\Test\Unit\Helper;
+
+namespace Adyen\Payment\Test\Unit\Helper;
+
+use Adyen\Payment\Helper\PaymentResponseHandler;
+use Adyen\Payment\Logger\AdyenLogger;
+use Adyen\Payment\Helper\Vault;
+use Adyen\Payment\Helper\Data;
+use Adyen\Payment\Helper\Quote;
+use Adyen\Payment\Helper\Order as OrderHelper;
+use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
+use Exception;
+use Magento\Framework\Exception\AlreadyExistsException;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Sales\Model\Order\Payment;
+use Magento\Sales\Model\Order\Status\History;
+use Magento\Sales\Model\ResourceModel\Order;
+use Magento\Sales\Model\OrderRepository;
+use Magento\Sales\Model\Order\Status\HistoryFactory;
+use Adyen\Payment\Helper\StateData;
+
+class PaymentResponseHandlerTest extends AbstractAdyenTestCase
+{
+ private $paymentMock;
+ private $orderMock;
+ private $adyenLoggerMock;
+ private $vaultHelperMock;
+ private $orderResourceModelMock;
+ private $dataHelperMock;
+ private $quoteHelperMock;
+ private $orderHelperMock;
+ private $orderRepositoryMock;
+ private $orderHistoryFactoryMock;
+ private $stateDataHelperMock;
+
+ private $paymentResponseHandler;
+
+ protected function setUp(): void
+ {
+ $this->paymentMock = $this->createMock(Payment::class);
+ $this->orderMock = $this->createMock(\Magento\Sales\Model\Order::class);
+ $this->adyenLoggerMock = $this->createMock(AdyenLogger::class);
+ $this->vaultHelperMock = $this->createMock(Vault::class);
+ $this->orderResourceModelMock = $this->createMock(Order::class);
+ $this->dataHelperMock = $this->createMock(Data::class);
+ $this->quoteHelperMock = $this->createMock(Quote::class);
+ $this->orderHelperMock = $this->createMock(OrderHelper::class);
+ $this->orderRepositoryMock = $this->createMock(OrderRepository::class);
+ $this->orderHistoryFactoryMock = $this->createGeneratedMock(HistoryFactory::class, [
+ 'create'
+ ]);
+ $this->stateDataHelperMock = $this->createMock(StateData::class);
+
+ $orderHistory = $this->createMock(History::class);
+ $orderHistory->method('setStatus')->willReturnSelf();
+ $orderHistory->method('setComment')->willReturnSelf();
+ $orderHistory->method('setEntityName')->willReturnSelf();
+ $orderHistory->method('setOrder')->willReturnSelf();
+
+ $this->orderHistoryFactoryMock->method('create')->willReturn($orderHistory);
+ $this->orderMock->method('getQuoteId')->willReturn(1);
+ $this->orderMock->method('getPayment')->willReturn($this->paymentMock);
+ $this->orderMock->method('getStatus')->willReturn('pending');
+
+ $this->orderHelperMock->method('setStatusOrderCreation')->willReturn( $this->orderMock);
+
+ $this->paymentResponseHandler = new PaymentResponseHandler(
+ $this->adyenLoggerMock,
+ $this->vaultHelperMock,
+ $this->orderResourceModelMock,
+ $this->dataHelperMock,
+ $this->quoteHelperMock,
+ $this->orderHelperMock,
+ $this->orderRepositoryMock,
+ $this->orderHistoryFactoryMock,
+ $this->stateDataHelperMock
+ );
+ }
+
+ private static function dataSourceForFormatPaymentResponseFinalResultCodes(): array
+ {
+ return [
+ ['resultCode' => PaymentResponseHandler::AUTHORISED],
+ ['resultCode' => PaymentResponseHandler::REFUSED],
+ ['resultCode' => PaymentResponseHandler::ERROR],
+ ['resultCode' => PaymentResponseHandler::POS_SUCCESS]
+ ];
+ }
+
+ /**
+ * @param $resultCode
+ * @return void
+ * @dataProvider dataSourceForFormatPaymentResponseFinalResultCodes
+ */
+ public function testFormatPaymentResponseForFinalResultCodes($resultCode)
+ {
+ $expectedResult = [
+ "isFinal" => true,
+ "resultCode" => $resultCode
+ ];
+
+ // Execute method of the tested class
+ $result = $this->paymentResponseHandler->formatPaymentResponse($resultCode);
+
+ // Assert conditions
+ $this->assertEquals($expectedResult, $result);
+ }
+
+ private static function dataSourceForFormatPaymentResponseActionRequredPayments(): array
+ {
+ return [
+ ['resultCode' => PaymentResponseHandler::REDIRECT_SHOPPER, 'action' => ['type' => 'qrCode']],
+ ['resultCode' => PaymentResponseHandler::IDENTIFY_SHOPPER, 'action' => ['type' => 'qrCode']],
+ ['resultCode' => PaymentResponseHandler::CHALLENGE_SHOPPER, 'action' => ['type' => 'qrCode']],
+ ['resultCode' => PaymentResponseHandler::PENDING, 'action' => ['type' => 'qrCode']],
+ ];
+ }
+
+ /**
+ * @param $resultCode
+ * @param $action
+ * @return void
+ * @dataProvider dataSourceForFormatPaymentResponseActionRequredPayments
+ */
+ public function testFormatPaymentResponseForActionRequiredPayments($resultCode, $action)
+ {
+ $expectedResult = [
+ "isFinal" => false,
+ "resultCode" => $resultCode,
+ "action" => $action
+ ];
+
+ // Execute method of the tested class
+ $result = $this->paymentResponseHandler->formatPaymentResponse($resultCode, $action);
+
+ // Assert conditions
+ $this->assertEquals($expectedResult, $result);
+ }
+
+ /**
+ * @return void
+ */
+ public function testFormatPaymentResponseForVoucherPayments()
+ {
+ $resultCode = PaymentResponseHandler::PRESENT_TO_SHOPPER;
+ $action = ['type' => 'voucher'];
+
+ $expectedResult = [
+ "isFinal" => true,
+ "resultCode" => $resultCode,
+ "action" => $action
+ ];
+
+ // Execute method of the tested class
+ $result = $this->paymentResponseHandler->formatPaymentResponse($resultCode, $action);
+
+ // Assert conditions
+ $this->assertEquals($expectedResult, $result);
+ }
+
+ /**
+ * @return void
+ */
+ public function testFormatPaymentResponseForOfflinePayments()
+ {
+ $resultCode = PaymentResponseHandler::RECEIVED;
+ $additionalData = ['action' => ['voucher']];
+
+ $expectedResult = [
+ "isFinal" => true,
+ "resultCode" => $resultCode,
+ "additionalData" => $additionalData
+ ];
+
+ // Execute method of the tested class
+ $result = $this->paymentResponseHandler->formatPaymentResponse($resultCode, null, $additionalData);
+
+ // Assert conditions
+ $this->assertEquals($expectedResult, $result);
+ }
+
+ /**
+ * @return void
+ */
+ public function testFormatPaymentResponseForUnknownResults()
+ {
+ $resultCode = 'UNRECOGNISED_RESULT_CODE';
+
+ $expectedResult = [
+ "isFinal" => true,
+ "resultCode" => PaymentResponseHandler::ERROR
+ ];
+
+ // Execute method of the tested class
+ $result = $this->paymentResponseHandler->formatPaymentResponse($resultCode);
+
+ // Assert conditions
+ $this->assertEquals($expectedResult, $result);
+ }
+
+ public function testHandlePaymentsDetailsResponseWithNullResultCode()
+ {
+ $orderMock = $this->createMock(\Magento\Sales\Model\Order::class);
+
+ $paymentsDetailsResponse = [
+ 'randomData' => 'someRandomValue'
+ ];
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $orderMock
+ );
+
+ $this->assertFalse($result);
+ }
+
+ public function testHandlePaymentsDetailsResponseAuthorised()
+ {
+ $paymentsDetailsResponse = [
+ 'resultCode' => PaymentResponseHandler::AUTHORISED,
+ 'pspReference' => 'ABC123456789',
+ 'paymentMethod' => [
+ 'brand' => 'ideal'
+ ],
+ 'additionalData' => [
+ 'someData' => 'someValue'
+ ],
+ 'details' => [
+ 'someData' => 'someValue'
+ ],
+ 'donationToken' => 'XYZ123456789'
+ ];
+
+ $this->quoteHelperMock->method('disableQuote')->willThrowException(new Exception());
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error');
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $this->orderMock
+ );
+
+ $this->assertTrue($result);
+ }
+
+
+ private static function handlePaymentsDetailsPendingProvider(): array
+ {
+ return [
+ ['paymentMethodCode' => 'bankTransfer'],
+ ['paymentMethodCode' => 'sepadirectdebit'],
+ ['paymentMethodCode' => 'multibanco'],
+ ];
+ }
+
+ /**
+ * @return void
+ * @throws AlreadyExistsException
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @dataProvider handlePaymentsDetailsPendingProvider
+ */
+ public function testHandlePaymentsDetailsResponsePending($paymentMethodCode)
+ {
+ $this->stateDataHelperMock->method('cleanQuoteStateData')
+ ->willThrowException(new Exception);
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error');
+
+ $paymentsDetailsResponse = [
+ 'resultCode' => PaymentResponseHandler::PENDING,
+ 'pspReference' => 'ABC123456789',
+ 'paymentMethod' => [
+ 'brand' => $paymentMethodCode
+ ]
+ ];
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $this->orderMock
+ );
+
+ $this->assertTrue($result);
+ }
+
+ private static function handlePaymentsDetailsPendingReceived(): array
+ {
+ return [
+ ['paymentMethodCode' => 'alipay_hk', 'expectedResult' => false],
+ ['paymentMethodCode' => 'multibanco', 'expectedResult' => true]
+ ];
+ }
+
+ /**
+ * @return void
+ * @throws AlreadyExistsException
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @dataProvider handlePaymentsDetailsPendingReceived
+ */
+ public function testHandlePaymentsDetailsResponseReceived($paymentMethodCode, $expectedResult)
+ {
+ $paymentsDetailsResponse = [
+ 'resultCode' => PaymentResponseHandler::RECEIVED,
+ 'pspReference' => 'ABC123456789',
+ 'paymentMethod' => [
+ 'brand' => $paymentMethodCode
+ ]
+ ];
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $this->orderMock
+ );
+
+ $this->assertEquals($expectedResult, $result);
+ }
+
+ private static function handlePaymentsDetailsActionRequiredProvider(): array
+ {
+ return [
+ ['resultCode' => PaymentResponseHandler::PRESENT_TO_SHOPPER],
+ ['resultCode' => PaymentResponseHandler::IDENTIFY_SHOPPER],
+ ['resultCode' => PaymentResponseHandler::CHALLENGE_SHOPPER],
+ ['resultCode' => PaymentResponseHandler::REDIRECT_SHOPPER]
+ ];
+ }
+
+ /**
+ * @return void
+ * @throws AlreadyExistsException
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @dataProvider handlePaymentsDetailsActionRequiredProvider
+ */
+ public function testHandlePaymentsDetailsResponseActionRequired($resultCode)
+ {
+ $paymentsDetailsResponse = [
+ 'resultCode' => $resultCode,
+ 'pspReference' => 'ABC123456789',
+ 'paymentMethod' => [
+ 'brand' => 'ideal'
+ ],
+ 'action' => [
+ 'actionData' => 'actionValue'
+ ]
+ ];
+
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('addAdyenResult');
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $this->orderMock
+ );
+
+ $this->assertTrue($result);
+ }
+
+ private static function handlePaymentsDetailsActionCancelledOrRefusedProvider(): array
+ {
+ return [
+ ['resultCode' => PaymentResponseHandler::REFUSED],
+ ['resultCode' => PaymentResponseHandler::CANCELLED]
+ ];
+ }
+
+ /**
+ * @return void
+ * @throws AlreadyExistsException
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @dataProvider handlePaymentsDetailsActionCancelledOrRefusedProvider
+ */
+ public function testHandlePaymentsDetailsResponseCancelOrRefused($resultCode)
+ {
+ $paymentsDetailsResponse = [
+ 'resultCode' => $resultCode,
+ 'pspReference' => 'ABC123456789',
+ 'paymentMethod' => [
+ 'brand' => 'ideal'
+ ],
+ 'action' => [
+ 'actionData' => 'actionValue'
+ ]
+ ];
+
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('addAdyenResult');
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $this->orderMock
+ );
+
+ $this->assertFalse($result);
+ }
+
+ public function testHandlePaymentsDetailsResponseInvalid()
+ {
+ $paymentsDetailsResponse = [
+ 'resultCode' => 'UNRECOGNISED_RESULT_CODE'
+ ];
+
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error');
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $this->orderMock
+ );
+
+ $this->assertFalse($result);
+ }
+
+ public function testHandlePaymentsDetailsEmptyResponse()
+ {
+ $paymentsDetailsResponse = [];
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error');
+
+ $result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
+ $paymentsDetailsResponse,
+ $this->orderMock
+ );
+
+ $this->assertFalse($result);
+ }
+}
diff --git a/Test/Unit/Model/Api/AdyenPaymentsDetailsTest.php b/Test/Unit/Model/Api/AdyenPaymentsDetailsTest.php
new file mode 100644
index 000000000..65b195b9c
--- /dev/null
+++ b/Test/Unit/Model/Api/AdyenPaymentsDetailsTest.php
@@ -0,0 +1,106 @@
+
+ */
+
+namespace Adyen\Payment\Test\Unit\Model\Api;
+
+use Adyen\Payment\Helper\PaymentResponseHandler;
+use Adyen\Payment\Helper\PaymentsDetails;
+use Adyen\Payment\Model\Api\AdyenPaymentsDetails;
+use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
+use Magento\Framework\Exception\ValidatorException;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+
+class AdyenPaymentsDetailsTest extends AbstractAdyenTestCase
+{
+ private $adyenPaymentsDetails;
+ private $orderRepositoryMock;
+ private $paymentsDetailsHelperMock;
+ private $paymentResponseHandlerHelperMock;
+
+ protected function setUp(): void
+ {
+ $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class);
+ $this->paymentsDetailsHelperMock = $this->createMock(PaymentsDetails::class);
+ $this->paymentResponseHandlerHelperMock = $this->createPartialMock(
+ PaymentResponseHandler::class,
+ ['handlePaymentsDetailsResponse']
+ );
+
+ $objectManager = new ObjectManager($this);
+ $this->adyenPaymentsDetails = $objectManager->getObject(AdyenPaymentsDetails::class, [
+ 'orderRepository' => $this->orderRepositoryMock,
+ 'paymentsDetails' => $this->paymentsDetailsHelperMock,
+ 'paymentResponseHandler' => $this->paymentResponseHandlerHelperMock
+ ]);
+ }
+
+ public function testSuccessfulCall()
+ {
+ $payload = '{"someData":"someValue"}';
+ $result = ['resultCode' => 'Authorised'];
+ $orderId = 1;
+
+ $this->orderRepositoryMock
+ ->method('get')
+ ->willReturn($this->createMock(OrderInterface::class));
+
+ $this->paymentsDetailsHelperMock
+ ->method('initiatePaymentDetails')
+ ->willReturn($result);
+
+ $this->paymentResponseHandlerHelperMock
+ ->method('handlePaymentsDetailsResponse')
+ ->willReturn(true);
+
+ $response = $this->adyenPaymentsDetails->initiate($payload, $orderId);
+
+ $this->assertJson($response);
+ $this->assertArrayHasKey('isFinal', json_decode($response, true));
+ $this->assertArrayHasKey('resultCode', json_decode($response, true));
+ }
+
+ public function testFailingJson()
+ {
+ $this->expectException(ValidatorException::class);
+
+ $payload = '{"someData":"someValue"';
+ $orderId = 1;
+
+ $this->adyenPaymentsDetails->initiate($payload, $orderId);
+ }
+
+ public function testInvalidDetailsCall()
+ {
+ $this->expectException(ValidatorException::class);
+
+ $payload = '{"someData":"someValue"}';
+ $result = ['resultCode' => 'Authorised'];
+ $orderId = 1;
+
+ $this->orderRepositoryMock
+ ->method('get')
+ ->willReturn($this->createMock(OrderInterface::class));
+
+ $this->paymentsDetailsHelperMock
+ ->method('initiatePaymentDetails')
+ ->willReturn($result);
+
+ $this->paymentResponseHandlerHelperMock
+ ->method('handlePaymentsDetailsResponse')
+ ->willReturn(false);
+
+ $this->adyenPaymentsDetails->initiate($payload, $orderId);
+ }
+
+
+}
diff --git a/Test/Unit/Model/Api/GuestAdyenPaymentsDetailsTest.php b/Test/Unit/Model/Api/GuestAdyenPaymentsDetailsTest.php
new file mode 100644
index 000000000..76d7873d8
--- /dev/null
+++ b/Test/Unit/Model/Api/GuestAdyenPaymentsDetailsTest.php
@@ -0,0 +1,110 @@
+
+ */
+
+namespace Adyen\Payment\Test\Unit\Model\Api;
+
+use Adyen\Payment\Model\Api\AdyenPaymentsDetails;
+use Adyen\Payment\Model\Api\GuestAdyenPaymentsDetails;
+use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
+use Magento\Framework\Exception\NotFoundException;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Quote\Model\QuoteIdMask;
+use Magento\Quote\Model\QuoteIdMaskFactory;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+
+class GuestAdyenPaymentsDetailsTest extends AbstractAdyenTestCase
+{
+ private $guestAdyenPaymentsDetails;
+ private $orderRepositoryMock;
+ private $quoteIdMaskFactoryMask;
+ private $adyenPaymentsDetailsMock;
+
+ protected function setUp(): void
+ {
+ $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class);
+ $this->adyenPaymentsDetailsMock = $this->createMock(AdyenPaymentsDetails::class);
+ $this->quoteIdMaskFactoryMask = $this->createGeneratedMock(QuoteIdMaskFactory::class, [
+ 'create'
+ ]);
+
+ $objectManager = new ObjectManager($this);
+ $this->guestAdyenPaymentsDetails = $objectManager->getObject(GuestAdyenPaymentsDetails::class, [
+ 'orderRepository' => $this->orderRepositoryMock,
+ 'adyenPaymentsDetails' => $this->adyenPaymentsDetailsMock,
+ 'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMask
+ ]);
+ }
+
+ public function testSuccessfulCall()
+ {
+ $payload = '{"someData":"someValue"}';
+ $result = '{"resultCode": "Authorised", "isFinal": "true"}';
+ $orderId = 1;
+ $maskedCartId = 'abcdef123456';
+ $cartId = 99;
+ $orderQuoteId = 99;
+
+ $quoteIdMaskMock = $this->createGeneratedMock(QuoteIdMask::class, [
+ 'load',
+ 'getQuoteId'
+ ]);
+ $quoteIdMaskMock->method('load')->willReturn($quoteIdMaskMock);
+ $quoteIdMaskMock->method('getQuoteId')->willReturn($cartId);
+
+ $this->quoteIdMaskFactoryMask->method('create')
+ ->willReturn($quoteIdMaskMock);
+
+ $orderMock = $this->createMock(OrderInterface::class);
+ $orderMock->method('getQuoteId')->willReturn($orderQuoteId);
+
+ $this->orderRepositoryMock->method('get')
+ ->willReturn($orderMock);
+
+ $this->adyenPaymentsDetailsMock->method('initiate')
+ ->willReturn($result);
+
+ $response = $this->guestAdyenPaymentsDetails->initiate($payload, $orderId, $maskedCartId);
+
+ $this->assertJson($response);
+ $this->assertArrayHasKey('isFinal', json_decode($response, true));
+ $this->assertArrayHasKey('resultCode', json_decode($response, true));
+ }
+
+ public function testWrongCartId()
+ {
+ $this->expectException(NotFoundException::class);
+
+ $payload = '{"someData":"someValue"}';
+ $orderId = 1;
+ $maskedCartId = 'abcdef123456';
+ $cartId = 99;
+ $orderQuoteId = 200;
+
+ $quoteIdMaskMock = $this->createGeneratedMock(QuoteIdMask::class, [
+ 'load',
+ 'getQuoteId'
+ ]);
+ $quoteIdMaskMock->method('load')->willReturn($quoteIdMaskMock);
+ $quoteIdMaskMock->method('getQuoteId')->willReturn($cartId);
+
+ $this->quoteIdMaskFactoryMask->method('create')
+ ->willReturn($quoteIdMaskMock);
+
+ $orderMock = $this->createMock(OrderInterface::class);
+ $orderMock->method('getQuoteId')->willReturn($orderQuoteId);
+
+ $this->orderRepositoryMock->method('get')
+ ->willReturn($orderMock);
+
+ $this->guestAdyenPaymentsDetails->initiate($payload, $orderId, $maskedCartId);
+ }
+}
diff --git a/Test/Unit/Observer/SetOrderStateAfterPaymentObserverTest.php b/Test/Unit/Observer/SetOrderStateAfterPaymentObserverTest.php
new file mode 100644
index 000000000..785f64804
--- /dev/null
+++ b/Test/Unit/Observer/SetOrderStateAfterPaymentObserverTest.php
@@ -0,0 +1,102 @@
+
+ */
+
+namespace Adyen\Payment\Test\Unit\Observer;
+
+use Adyen\Payment\Helper\PaymentResponseHandler;
+use Adyen\Payment\Model\Method\Adapter;
+use Adyen\Payment\Observer\SetOrderStateAfterPaymentObserver;
+use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
+use Magento\Framework\Event\Observer;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Payment;
+
+class SetOrderStateAfterPaymentObserverTest extends AbstractAdyenTestCase
+{
+ private $setOrderStateAfterPaymentObserver;
+ private $observerMock;
+ private $paymentMock;
+ private $orderMock;
+ private $statusResolverMock;
+
+ public function setUp(): void
+ {
+ $this->observerMock = $this->createMock(Observer::class);
+ $this->paymentMock = $this->createMock(Payment::class);
+ $this->orderMock = $this->createMock(Order::class);
+ $this->statusResolverMock = $this->createMock(Order\StatusResolver::class);
+
+ $paymentMethodInstanceMock = $this->createMock(Adapter::class);
+ $this->paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock);
+ $this->paymentMock->method('getOrder')->willReturn($this->orderMock);
+ $this->observerMock->method('getData')->with('payment')->willReturn($this->paymentMock);
+ $this->statusResolverMock->method('getOrderStatusByState')
+ ->willReturn(Order::STATE_PENDING_PAYMENT);
+
+ $this->setOrderStateAfterPaymentObserver = new SetOrderStateAfterPaymentObserver(
+ $this->statusResolverMock
+ );
+ }
+
+ private static function resultCodeProvider(): array
+ {
+ return [
+ [
+ 'resultCode' => PaymentResponseHandler::REDIRECT_SHOPPER,
+ 'action' => ['type' => 'TYPE_PLACEHOLDER']
+ ],
+ [
+ 'resultCode' => PaymentResponseHandler::CHALLENGE_SHOPPER,
+ 'action' => ['type' => 'TYPE_PLACEHOLDER']
+ ],
+ [
+ 'resultCode' => PaymentResponseHandler::PENDING,
+ 'action' => ['type' => 'TYPE_PLACEHOLDER']
+ ],
+ [
+ 'resultCode' => PaymentResponseHandler::IDENTIFY_SHOPPER,
+ 'action' => ['type' => 'TYPE_PLACEHOLDER']
+ ],
+ [
+ 'resultCode' => PaymentResponseHandler::AUTHORISED,
+ 'action' => null,
+ 'changeStatus' => false
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider resultCodeProvider
+ * @return void
+ * @throws LocalizedException
+ */
+ public function testExecute($resultCode, $action, $changeStatus = true)
+ {
+ $this->paymentMock->method('getAdditionalInformation')->will(
+ $this->returnValueMap([
+ ['resultCode', $resultCode],
+ ['action', $action]
+ ])
+ );
+
+ if ($changeStatus) {
+ $this->orderMock->expects($this->once())->method('setState');
+ $this->orderMock->expects($this->once())->method('save');
+ } else {
+ $this->orderMock->expects($this->never())->method('setState');
+ $this->orderMock->expects($this->never())->method('save');
+ }
+
+ $this->setOrderStateAfterPaymentObserver->execute($this->observerMock);
+
+ }
+}
diff --git a/composer.json b/composer.json
index 9f89055ce..f787681ac 100755
--- a/composer.json
+++ b/composer.json
@@ -2,7 +2,7 @@
"name": "adyen/module-payment",
"description": "Official Magento2 Plugin to connect to Payment Service Provider Adyen.",
"type": "magento2-module",
- "version": "9.1.0",
+ "version": "9.2.0",
"license": "MIT",
"repositories": [
{
diff --git a/etc/config.xml b/etc/config.xml
index ed4da4b4b..24b0ddf34 100755
--- a/etc/config.xml
+++ b/etc/config.xml
@@ -118,7 +118,7 @@